diff --git a/build.gradle b/build.gradle
index f9dc563482..f7dcc19354 100644
--- a/build.gradle
+++ b/build.gradle
@@ -199,8 +199,8 @@ dependencies {
implementation "com.github.ybq:Android-SpinKit:1.4.0"
}
-def canonicalVersionCode = 35
-def canonicalVersionName = "1.0.1"
+def canonicalVersionCode = 36
+def canonicalVersionName = "1.0.2"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
diff --git a/res/drawable/icon_crown.xml b/res/drawable/ic_crown.xml
similarity index 100%
rename from res/drawable/icon_crown.xml
rename to res/drawable/ic_crown.xml
diff --git a/res/drawable/scroll_to_bottom_button_background.xml b/res/drawable/scroll_to_bottom_button_background.xml
index 68e512346c..387c75be90 100644
--- a/res/drawable/scroll_to_bottom_button_background.xml
+++ b/res/drawable/scroll_to_bottom_button_background.xml
@@ -1,5 +1,8 @@
-
+
+
\ No newline at end of file
diff --git a/res/layout-sw400dp/activity_display_name_v2.xml b/res/layout-sw400dp/activity_display_name.xml
similarity index 100%
rename from res/layout-sw400dp/activity_display_name_v2.xml
rename to res/layout-sw400dp/activity_display_name.xml
diff --git a/res/layout-sw400dp/activity_seed_v2.xml b/res/layout-sw400dp/activity_seed.xml
similarity index 100%
rename from res/layout-sw400dp/activity_seed_v2.xml
rename to res/layout-sw400dp/activity_seed.xml
diff --git a/res/layout-sw400dp/fragment_enter_public_key.xml b/res/layout-sw400dp/fragment_enter_public_key.xml
index a2e53ce92d..bb85cef9c5 100644
--- a/res/layout-sw400dp/fragment_enter_public_key.xml
+++ b/res/layout-sw400dp/fragment_enter_public_key.xml
@@ -30,7 +30,7 @@
android:textAlignment="center"
android:text="Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code." />
-
-
+ android:layout_height="match_parent"
+ android:background="@drawable/default_session_background"
+ android:orientation="vertical">
-
+
+
+ android:layout_marginLeft="@dimen/very_large_spacing"
+ android:layout_marginRight="@dimen/very_large_spacing"
+ android:textSize="@dimen/large_font_size"
+ android:textStyle="bold"
+ android:textColor="@color/text"
+ android:text="Pick your display name" />
-
+
-
+
-
+
-
+
-
-
-
+
\ No newline at end of file
diff --git a/res/layout/activity_display_name_v2.xml b/res/layout/activity_display_name_v2.xml
deleted file mode 100644
index 443f6c23e0..0000000000
--- a/res/layout/activity_display_name_v2.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/layout/activity_seed.xml b/res/layout/activity_seed.xml
index cb77523182..a93318893d 100644
--- a/res/layout/activity_seed.xml
+++ b/res/layout/activity_seed.xml
@@ -1,158 +1,77 @@
-
+ android:background="@drawable/default_session_background"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
-
+
+
+
+
+ android:layout_marginLeft="@dimen/very_large_spacing"
+ android:layout_marginRight="@dimen/very_large_spacing"
+ android:textSize="@dimen/large_font_size"
+ android:textStyle="bold"
+ android:textColor="@color/text"
+ android:text="Meet your recovery phrase" />
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/res/layout/activity_seed_v2.xml b/res/layout/activity_seed_v2.xml
deleted file mode 100644
index a93318893d..0000000000
--- a/res/layout/activity_seed_v2.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/layout/activity_settings.xml b/res/layout/activity_settings.xml
index b4b811d415..ab8e81d6c9 100644
--- a/res/layout/activity_settings.xml
+++ b/res/layout/activity_settings.xml
@@ -107,7 +107,7 @@
-
diff --git a/res/layout/fragment_enter_public_key.xml b/res/layout/fragment_enter_public_key.xml
index 99952ed601..7fba98a41a 100644
--- a/res/layout/fragment_enter_public_key.xml
+++ b/res/layout/fragment_enter_public_key.xml
@@ -30,7 +30,7 @@
android:textAlignment="center"
android:text="Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code." />
-
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d5d65e2d57..348b88b0f5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1618,13 +1618,13 @@
Accept
Decline
- %1$s sent you a message request
- You\'ve accepted %1$s\'s message request
- You\'ve declined %1$s\'s message request
- %1$s\'s message request has expired
- You\'ve sent %1$s a message request
- %1$s accepted your message request
- Your message request to %1$s has expired
+ %1$s sent you a session request
+ You\'ve accepted %1$s\'s session request
+ You\'ve declined %1$s\'s session request
+ %1$s\'s session request has expired
+ You\'ve sent %1$s a session request
+ %1$s accepted your session request
+ Your session request to %1$s has expired
Pending Friend Request…
New Message
diff --git a/res/xml/network_security_configuration.xml b/res/xml/network_security_configuration.xml
index df97928889..c62b3d962e 100644
--- a/res/xml/network_security_configuration.xml
+++ b/res/xml/network_security_configuration.xml
@@ -6,6 +6,7 @@
storage.seed1.loki.network
storage.seed2.loki.network
public.loki.foundation:22023
+ file-dev.lokinet.org
127.0.0.1
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
index 7f099f4617..611fe53b73 100644
--- a/src/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -96,14 +96,13 @@ import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol;
-import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
+import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
import org.whispersystems.signalservice.loki.api.LokiLongPoller;
import org.whispersystems.signalservice.loki.api.LokiP2PAPI;
import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate;
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
import org.whispersystems.signalservice.loki.api.LokiRSSFeed;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import java.security.Security;
import java.util.ArrayList;
@@ -115,7 +114,6 @@ import java.util.concurrent.TimeUnit;
import dagger.ObjectGraph;
import kotlin.Unit;
import network.loki.messenger.BuildConfig;
-import okhttp3.Cache;
import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant;
import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
@@ -184,18 +182,19 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
// Loki - Set up P2P API if needed
setUpP2PAPI();
- // Loki - Set the cache
- LokiDotNetAPI.setCache(new Cache(this.getCacheDir(), OK_HTTP_CACHE_SIZE));
// Loki - Update device mappings
if (setUpStorageAPIIfNeeded()) {
- LokiStorageAPI.Companion.getShared().updateUserDeviceMappings();
- if (TextSecurePreferences.needsRevocationCheck(this)) {
- checkNeedsRevocation();
+ String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
+ if (userHexEncodedPublicKey != null) {
+ LokiFileServerAPI.Companion.getShared().getDeviceLinks(userHexEncodedPublicKey, true);
+ if (TextSecurePreferences.getNeedsIsRevokedSlaveDeviceCheck(this)) {
+ MultiDeviceUtilities.checkIsRevokedSlaveDevice(this);
+ }
}
}
// Loki - Set up public chat manager
lokiPublicChatManager = new LokiPublicChatManager(this);
- updatePublicChatProfileAvatarIfNeeded();
+ updatePublicChatProfilePictureIfNeeded();
}
@Override
@@ -206,6 +205,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
KeyCachingService.onAppForegrounded(this);
// Loki - Start long polling if needed
startLongPollingIfNeeded();
+ // Loki - Start open group polling if needed
lokiPublicChatManager.startPollersIfNeeded();
}
@@ -466,7 +466,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
boolean isDebugMode = BuildConfig.DEBUG;
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabaseProtocol database = DatabaseFactory.getLokiAPIDatabase(this);
- LokiStorageAPI.Companion.configure(isDebugMode, userHexEncodedPublicKey, userPrivateKey, database);
+ LokiFileServerAPI.Companion.configure(isDebugMode, userHexEncodedPublicKey, userPrivateKey, database);
return true;
}
return false;
@@ -534,7 +534,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
public void createRSSFeedsIfNeeded() {
ArrayList feeds = new ArrayList<>();
- feeds.add(lokiNewsFeed());
+// feeds.add(lokiNewsFeed());
feeds.add(lokiMessengerUpdatesFeed());
for (LokiRSSFeed feed : feeds) {
boolean isFeedSetUp = TextSecurePreferences.isChatSetUp(this, feed.getId());
@@ -590,7 +590,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
if (lokiMessengerUpdatesFeedPoller != null) lokiMessengerUpdatesFeedPoller.startIfNeeded();
}
- public void updatePublicChatProfileAvatarIfNeeded() {
+ public void updatePublicChatProfilePictureIfNeeded() {
AsyncTask.execute(() -> {
LokiPublicChatAPI publicChatAPI = null;
try {
@@ -616,11 +616,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
});
}
- // endregion
-
- public void checkNeedsRevocation() {
- MultiDeviceUtilities.checkForRevocation(this);
- }
public void checkNeedsDatabaseReset() {
if (TextSecurePreferences.resetDatabase(this)) {
@@ -646,4 +641,5 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
this.startActivity(mainIntent);
Runtime.getRuntime().exit(0);
}
+ // endregion
}
diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java
index b2eb3f32e8..b42dafc29a 100644
--- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java
+++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java
@@ -59,7 +59,7 @@ import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
+import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -407,7 +407,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
//Loki - Upload the profile photo here
if (avatar != null) {
Log.d("Loki", "Start uploading profile photo");
- LokiStorageAPI storageAPI = LokiStorageAPI.shared;
+ LokiFileServerAPI storageAPI = LokiFileServerAPI.shared;
LokiDotNetAPI.UploadResult result = storageAPI.uploadProfilePicture(storageAPI.getServer(), profileKey, avatar);
Log.d("Loki", "Profile photo uploaded, the url is " + result.getUrl());
TextSecurePreferences.setProfileAvatarUrl(context, result.getUrl());
@@ -422,7 +422,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
ProfileKeyUtil.setEncodedProfileKey(context, newProfileKey);
// Update profile key on the public chat server
- ApplicationContext.getInstance(context).updatePublicChatProfileAvatarIfNeeded();
+ ApplicationContext.getInstance(context).updatePublicChatProfilePictureIfNeeded();
} catch (Exception e) {
Log.d("Loki", "Failed to upload profile photo: " + e);
return false;
diff --git a/src/org/thoughtcrime/securesms/DeviceListFragment.java b/src/org/thoughtcrime/securesms/DeviceListFragment.java
index 91381c4835..d725522e3d 100644
--- a/src/org/thoughtcrime/securesms/DeviceListFragment.java
+++ b/src/org/thoughtcrime/securesms/DeviceListFragment.java
@@ -190,7 +190,7 @@ public class DeviceListFragment extends ListFragment
private void updateAddDeviceButtonVisibility() {
if (addDeviceButton != null) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
- boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(getContext()).getPairingAuthorisations(userHexEncodedPublicKey).isEmpty();
+ boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(getContext()).getDeviceLinks(userHexEncodedPublicKey).isEmpty();
addDeviceButton.setVisibility(isDeviceLinkingEnabled ? View.VISIBLE : View.INVISIBLE);
}
}
diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java
index 927b158e42..1c851247ea 100644
--- a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java
+++ b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java
@@ -9,14 +9,13 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
-import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Util;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
+import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
+import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
import java.util.HashMap;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.TimeUnit;
import kotlin.Unit;
@@ -83,7 +82,7 @@ public class TypingStatusSender {
}
private void sendTyping(long threadId, boolean typingStarted) {
- LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared();
+ LokiFileServerAPI storageAPI = LokiFileServerAPI.Companion.getShared();
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Recipient recipient = threadDatabase.getRecipientForThreadId(threadId);
@@ -91,7 +90,7 @@ public class TypingStatusSender {
ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted));
return;
}
- LokiStorageAPI.shared.getAllDevicePublicKeys(recipient.getAddress().serialize()).success(devices -> {
+ LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(recipient.getAddress().serialize()).success(devices -> {
for (String device : devices) {
Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false);
long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient);
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index c180b5f054..04a96108a5 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -232,10 +232,10 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.thoughtcrime.securesms.util.views.Stub;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.loki.api.DeviceLink;
import org.whispersystems.signalservice.loki.api.LokiAPI;
+import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
-import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.Mention;
@@ -331,7 +331,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private MenuItem searchViewItem;
private ProgressBar messageStatusProgressBar;
private ImageView muteIndicatorImageView;
- private TextView actionBarSubtitleTextView;
+ private TextView subtitleTextView;
private AttachmentTypeSelector attachmentTypeSelector;
private AttachmentManager attachmentManager;
@@ -362,6 +362,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private final DynamicNoActionBarTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
+ // Message Status Bar
private ArrayList broadcastReceivers = new ArrayList<>();
private String messageStatus = null;
@@ -404,6 +405,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
registerMessageStatusObserver("sendingMessage");
registerMessageStatusObserver("messageSent");
registerMessageStatusObserver("messageFailed");
+ BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Toast.makeText(ConversationActivity.this, "Your clock is out of sync with the service node network.", Toast.LENGTH_LONG).show();
+ }
+ };
+ broadcastReceivers.add(broadcastReceiver);
+ LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter("clockOutOfSync"));
initializeReceivers();
initializeActionBar();
@@ -543,7 +553,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
initializeIdentityRecords();
composeText.setTransport(sendButton.getSelectedTransport());
- updateTitleTextView(glideRequests, recipient);
+ updateTitleTextView(recipient);
updateSubtitleTextView();
setActionBarColor(recipient.getColor());
setBlockedUserState(recipient, isSecureText, isDefaultSms);
@@ -636,7 +646,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
case GROUP_EDIT:
recipient = Recipient.from(this, data.getParcelableExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA), true);
recipient.addListener(this);
- updateTitleTextView(glideRequests, recipient);
+ updateTitleTextView(recipient);
updateSubtitleTextView();
NotificationChannels.updateContactChannelName(this, recipient);
setBlockedUserState(recipient, isSecureText, isDefaultSms);
@@ -740,9 +750,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
MenuInflater inflater = this.getMenuInflater();
menu.clear();
- boolean isLokiGroupChat = recipient.getAddress().isPublicChat() || recipient.getAddress().isRSSFeed();
+ boolean isOpenGroupOrRSSFeed = recipient.getAddress().isPublicChat() || recipient.getAddress().isRSSFeed();
- if (isSecureText && !isLokiGroupChat) {
+ if (isSecureText && !isOpenGroupOrRSSFeed) {
if (recipient.getExpireMessages() > 0) {
inflater.inflate(R.menu.conversation_expiring_on, menu);
@@ -762,7 +772,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (isSecureText) inflater.inflate(R.menu.conversation_callable_secure, menu);
else inflater.inflate(R.menu.conversation_callable_insecure, menu);
*/
- } else if (isGroupConversation() && !isLokiGroupChat) {
+ } else if (isGroupConversation() && !isOpenGroupOrRSSFeed) {
inflater.inflate(R.menu.conversation_group_options, menu);
if (!isPushGroupConversation()) {
@@ -1679,7 +1689,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sessionRestoreBannerView = ViewUtil.findById(this, R.id.sessionRestoreBannerView);
messageStatusProgressBar = ViewUtil.findById(this, R.id.messageStatusProgressBar);
muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView);
- actionBarSubtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView);
+ subtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView);
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
@@ -1870,7 +1880,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Log.i(TAG, "onModified(" + recipient.getAddress().serialize() + ")");
Util.runOnMain(() -> {
Log.i(TAG, "onModifiedRun(): " + recipient.getRegistered());
- updateTitleTextView(glideRequests, recipient);
+ updateTitleTextView(recipient);
updateSubtitleTextView();
// titleView.setVerified(identityRecords.isVerified());
setBlockedUserState(recipient, isSecureText, isDefaultSms);
@@ -2290,7 +2300,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (threadID != this.threadId) {
Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID);
if (threadRecipient != null && !threadRecipient.isGroupRecipient()) {
- LokiStorageAPI.shared.getAllDevicePublicKeys(threadRecipient.getAddress().serialize()).success(devices -> {
+ LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(threadRecipient.getAddress().serialize()).success(devices -> {
// We should update our input if this thread is a part of the other threads device
if (devices.contains(recipient.getAddress().serialize())) {
this.updateInputPanel();
@@ -2313,33 +2323,29 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void updateInputPanel() {
/*
- isFriendsWithAnyDevice caches whether we are friends with any of the other users device.
+ isFriendsWithAnyDevice reflects whether we are friends with any of the other user's devices.
- This stops the case where the input panel disables and enables rapidly.
- - This can occur when we are not friends with the current thread BUT multi-device tells us that we are friends with another one of their devices.
+ This fixes the case where the input panel disables and enables rapidly, which can occur when we are
+ not friends with the current thread BUT multi device tells us that we are friends with another one of their devices.
*/
- if (recipient.isGroupRecipient() || isNoteToSelf() || isFriendsWithAnyDevice) {
- setInputPanelEnabled(true);
- return;
- }
+ if (recipient.isGroupRecipient() || isNoteToSelf() || isFriendsWithAnyDevice) { setInputPanelEnabled(true); return; }
- // It could take a while before our promise resolves, so we assume the best case
+ // Disable the input panel if a friend request is pending
LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId);
boolean isPending = friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED;
setInputPanelEnabled(!isPending);
- // We should always have the input panel enabled if we are friends with the current user
- isFriendsWithAnyDevice = friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
+ // Always enable the input panel if we are friends with the current user
+ isFriendsWithAnyDevice = (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS);
- // Multi-device input logic
if (!isFriendsWithAnyDevice) {
- // We should enable the input if we don't have any pending friend requests OR we are friends with a linked device
- MultiDeviceUtilities.hasPendingFriendRequestWithAnyLinkedDevice(this, recipient).success(hasPendingRequests -> {
+ // Enable the input panel if we don't have any pending friend requests OR we are friends with one of the user's linked devices
+ MultiDeviceUtilities.hasPendingFriendRequestWithAnyLinkedDevice(this, recipient).success( hasPendingRequests -> {
if (!hasPendingRequests) {
setInputPanelEnabled(true);
} else {
- MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient).success(isFriends -> {
- // If we are friend with any of the other devices then we want to make sure the input panel is always enabled for the duration of this conversation
+ MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient).success( isFriends -> {
+ // Enable the input panel if we're friends with any of the user's devices
isFriendsWithAnyDevice = isFriends;
setInputPanelEnabled(isFriends);
return Unit.INSTANCE;
@@ -2353,7 +2359,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void setInputPanelEnabled(boolean enabled) {
Util.runOnMain(() -> {
updateToggleButtonState();
- String hint = enabled ? "Message" : "Pending message request";
+ String hint = enabled ? "Message" : "Pending session request";
inputPanel.setHint(hint);
inputPanel.setEnabled(enabled);
if (enabled) {
@@ -2407,13 +2413,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendTextMessage(forceSms, expiresIn, subscriptionId, initiating);
}
} catch (RecipientFormattingException ex) {
- Toast.makeText(ConversationActivity.this,
- R.string.ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation,
- Toast.LENGTH_LONG).show();
Log.w(TAG, ex);
} catch (InvalidMessageException ex) {
- // Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_message_is_empty_exclamation,
- // Toast.LENGTH_SHORT).show();
Log.w(TAG, ex);
}
}
@@ -2565,7 +2566,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void updateToggleButtonState() {
- // Don't allow attachments if we're not friends with any device
+ // Don't allow attachments if we're not friends with any of the user's devices
if (!isNoteToSelf() && !recipient.isGroupRecipient() && !isFriendsWithAnyDevice) {
buttonToggle.display(sendButton);
quickAttachmentToggle.hide();
@@ -3160,13 +3161,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
// region Loki
- private void updateTitleTextView(GlideRequests glide, Recipient recipient) {
+ private void updateTitleTextView(Recipient recipient) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
- List deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getPairingAuthorisations(userHexEncodedPublicKey);
+ Set deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userHexEncodedPublicKey);
HashSet userLinkedDeviceHexEncodedPublicKeys = new HashSet<>();
- for (PairingAuthorisation deviceLink : deviceLinks) {
- userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getPrimaryDevicePublicKey().toLowerCase());
- userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getSecondaryDevicePublicKey().toLowerCase());
+ for (DeviceLink deviceLink : deviceLinks) {
+ userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getMasterHexEncodedPublicKey().toLowerCase());
+ userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getSlaveHexEncodedPublicKey().toLowerCase());
}
userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey.toLowerCase());
if (recipient == null) {
@@ -3174,45 +3175,46 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} else if (userLinkedDeviceHexEncodedPublicKeys.contains(recipient.getAddress().toString().toLowerCase())) {
titleTextView.setText("Note to Self");
} else {
- titleTextView.setText((recipient.getName() == null || recipient.getName().isEmpty()) ? recipient.getAddress().toString() : recipient.getName());
+ boolean hasName = (recipient.getName() != null && !recipient.getName().isEmpty());
+ titleTextView.setText(hasName ? recipient.getName() : recipient.getAddress().toString());
}
}
private void updateSubtitleTextView() {
muteIndicatorImageView.setVisibility(View.GONE);
- actionBarSubtitleTextView.setVisibility(View.VISIBLE);
+ subtitleTextView.setVisibility(View.VISIBLE);
if (messageStatus != null) {
switch (messageStatus) {
- case "calculatingPoW": actionBarSubtitleTextView.setText("Encrypting message"); break;
- case "contactingNetwork": actionBarSubtitleTextView.setText("Tracing a path"); break;
- case "sendingMessage": actionBarSubtitleTextView.setText("Sending message"); break;
- case "messageSent": actionBarSubtitleTextView.setText("Message sent securely"); break;
- case "messageFailed": actionBarSubtitleTextView.setText("Message failed to send"); break;
+ case "calculatingPoW": subtitleTextView.setText("Encrypting message"); break;
+ case "contactingNetwork": subtitleTextView.setText("Tracing a path"); break;
+ case "sendingMessage": subtitleTextView.setText("Sending message"); break;
+ case "messageSent": subtitleTextView.setText("Message sent securely"); break;
+ case "messageFailed": subtitleTextView.setText("Message failed to send"); break;
}
} else if (recipient.isMuted()) {
muteIndicatorImageView.setVisibility(View.VISIBLE);
- actionBarSubtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()));
+ subtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()));
} else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) {
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null) {
Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer());
if (userCount == null) { userCount = 0; }
if (userCount >= 200) {
- actionBarSubtitleTextView.setText("200+ members");
+ subtitleTextView.setText("200+ members");
} else {
- actionBarSubtitleTextView.setText(userCount + " members");
+ subtitleTextView.setText(userCount + " members");
}
} else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) {
- actionBarSubtitleTextView.setText(recipient.getAddress().toString());
+ subtitleTextView.setText(recipient.getAddress().toString());
} else {
- actionBarSubtitleTextView.setVisibility(View.GONE);
+ subtitleTextView.setVisibility(View.GONE);
}
} else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) {
- actionBarSubtitleTextView.setText(recipient.getAddress().toString());
+ subtitleTextView.setText(recipient.getAddress().toString());
} else {
- actionBarSubtitleTextView.setVisibility(View.GONE);
+ subtitleTextView.setVisibility(View.GONE);
}
- titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension((actionBarSubtitleTextView.getVisibility() == View.GONE) ? R.dimen.very_large_font_size : R.dimen.large_font_size));
+ titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension((subtitleTextView.getVisibility() == View.GONE) ? R.dimen.very_large_font_size : R.dimen.large_font_size));
}
private void setMessageStatusProgressAnimatedIfPossible(int progress) {
@@ -3284,18 +3286,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void acceptFriendRequest(@NotNull MessageRecord friendRequest) {
- // Send the accept to the original friend request thread id
+ // Send the accept to the original friend request thread ID
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(this);
long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id);
- long threadId = originalThreadID < 0 ? this.threadId : originalThreadID;
-
- Recipient contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId);
+ long threadID = originalThreadID < 0 ? this.threadId : originalThreadID;
+ Recipient contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID);
Address address = contact.getAddress();
- String contactPubKey = address.serialize();
- DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS);
+ String contactHexEncodedPublicKey = address.serialize();
+ DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
DatabaseFactory.getRecipientDatabase(this).setProfileSharing(contact, true);
- MessageSender.sendBackgroundMessageToAllDevices(this, contactPubKey);
+ MessageSender.sendBackgroundMessageToAllDevices(this, contactHexEncodedPublicKey);
MessageSender.syncContact(this, address);
updateInputPanel();
}
@@ -3304,10 +3305,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void rejectFriendRequest(@NotNull MessageRecord friendRequest) {
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(this);
long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id);
- long threadId = originalThreadID < 0 ? this.threadId : originalThreadID;
-
- DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.NONE);
- String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString();
+ long threadID = originalThreadID < 0 ? this.threadId : originalThreadID;
+ DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.NONE);
+ String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID).getAddress().toString();
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(contactID);
updateInputPanel();
}
@@ -3315,20 +3315,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public boolean isNoteToSelf() {
return TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize());
}
- // endregion
public void restoreSession() {
- // Loki - User clicked restore session
if (recipient.isGroupRecipient()) { return; }
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(this);
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(this);
Set 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);
+ long messageID = smsDatabase.insertMessageOutbox(threadId, new OutgoingTextMessage(recipient,"", 0, 0), false, System.currentTimeMillis(), null);
+ if (messageID > -1) {
+ smsDatabase.markAsLokiSessionRestoreSent(messageID);
}
lokiThreadDatabase.removeAllSessionRestoreDevices(threadId);
updateSessionRestoreBanner();
}
+ // endregion
}
diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java
index 38237484f5..0e67b89b42 100644
--- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java
+++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java
@@ -10,11 +10,11 @@ import org.thoughtcrime.securesms.logging.Log;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.state.SessionRecord;
-import org.whispersystems.signalservice.loki.messaging.LokiSessionDatabaseProtocol;
+import org.whispersystems.libsignal.state.SessionStore;
import java.util.List;
-public class TextSecureSessionStore implements LokiSessionDatabaseProtocol {
+public class TextSecureSessionStore implements SessionStore {
private static final String TAG = TextSecureSessionStore.class.getSimpleName();
diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
index cfa396d474..5d06fb1bc6 100644
--- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
+++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
@@ -135,7 +135,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.getCreateGroupChatAuthTokenTableCommand());
db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand());
db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand());
- db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
+ db.execSQL(LokiAPIDatabase.getCreateDeviceLinkTableCommand());
db.execSQL(LokiAPIDatabase.getCreateUserCountTableCommand());
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
@@ -518,7 +518,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
}
if (oldVersion < lokiV3) {
- db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
+ db.execSQL(LokiAPIDatabase.getCreateDeviceLinkTableCommand());
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
db.execSQL("ALTER TABLE groups ADD COLUMN avatar_url TEXT");
diff --git a/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java
index 031eb62a4d..7a7a67d535 100644
--- a/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java
+++ b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java
@@ -11,13 +11,14 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities;
import org.thoughtcrime.securesms.util.AsyncLoader;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
+import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
import java.io.File;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Set;
public class DeviceListLoader extends AsyncLoader> {
@@ -33,7 +34,7 @@ public class DeviceListLoader extends AsyncLoader> {
public List loadInBackground() {
try {
String ourPublicKey = TextSecurePreferences.getLocalNumber(getContext());
- List secondaryDevicePublicKeys = LokiStorageAPI.shared.getSecondaryDevicePublicKeys(ourPublicKey).get();
+ Set secondaryDevicePublicKeys = LokiDeviceLinkUtilities.INSTANCE.getSlaveHexEncodedPublicKeys(ourPublicKey).get();
List devices = Stream.of(secondaryDevicePublicKeys).map(this::mapToDevice).toList();
Collections.sort(devices, new DeviceComparator());
return devices;
diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
index b57185dd24..c59224d481 100644
--- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
+++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
@@ -7,7 +7,6 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.CreateProfileActivity;
import org.thoughtcrime.securesms.DeviceListFragment;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
-import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.gcm.FcmService;
@@ -48,6 +47,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation;
import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
@@ -160,7 +160,7 @@ public class SignalCommunicationModule {
DatabaseFactory.getLokiThreadDatabase(context),
DatabaseFactory.getLokiMessageDatabase(context),
DatabaseFactory.getLokiPreKeyBundleDatabase(context),
- new TextSecureSessionStore(context),
+ new LokiSessionResetImplementation(context),
DatabaseFactory.getLokiUserDatabase(context),
((ApplicationContext)context.getApplicationContext()).broadcaster);
} else {
diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
index 9f216e2fad..d1f37ce390 100644
--- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
+++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
@@ -36,7 +36,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
+import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.util.Collections;
@@ -318,7 +318,7 @@ public class GroupMessageProcessor {
try {
String masterHexEncodedPublicKey = hexEncodedPublicKey.equalsIgnoreCase(ourPublicKey)
? TextSecurePreferences.getMasterHexEncodedPublicKey(context)
- : PromiseUtil.timeout(LokiStorageAPI.shared.getPrimaryDevicePublicKey(hexEncodedPublicKey), 5000).get();
+ : PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey), 5000).get();
return masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : hexEncodedPublicKey;
} catch (Exception e) {
return hexEncodedPublicKey;
@@ -329,7 +329,7 @@ public class GroupMessageProcessor {
String ourNumber = TextSecurePreferences.getLocalNumber(context);
for (String member : members) {
// Make sure we have session with all of the members secondary devices
- LokiStorageAPI.shared.getAllDevicePublicKeys(member).success(devices -> {
+ LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(member).success(devices -> {
if (devices.contains(ourNumber)) { return Unit.INSTANCE; }
for (String device : devices) {
SignalProtocolAddress protocolAddress = new SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID);
diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
index dce85f83e6..3bb54e43d9 100644
--- a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
@@ -27,7 +27,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
+import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
import java.io.IOException;
import java.io.InputStream;
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 6282120598..49163e4f2f 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -68,13 +68,13 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.FriendRequestHandler;
-import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities;
import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
-import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyBundleDatabase;
-import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyRecordDatabase;
+import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation;
import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity;
+import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities;
+import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyBundleDatabase;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
@@ -101,6 +101,8 @@ import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
+import org.whispersystems.libsignal.loki.LokiSessionResetProtocol;
+import org.whispersystems.libsignal.loki.LokiSessionResetStatus;
import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -129,15 +131,14 @@ import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOper
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.loki.api.DeviceLink;
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession;
import org.whispersystems.signalservice.loki.api.LokiAPI;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
-import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
+import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
-import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.InputStream;
@@ -270,9 +271,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
- LokiPreKeyRecordDatabase lokiPreKeyRecordDatabase = DatabaseFactory.getLokiPreKeyRecordDatabase(context);
+ LokiSessionResetProtocol lokiSessionResetProtocol = new LokiSessionResetImplementation(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
- LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiThreadDatabase, lokiPreKeyRecordDatabase, UnidentifiedAccessUtil.getCertificateValidator());
+ LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiSessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
SignalServiceContent content = cipher.decrypt(envelope);
@@ -282,21 +283,21 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return;
}
-
if (shouldIgnore(content)) {
Log.i(TAG, "Ignoring message.");
return;
}
// Loki - Handle friend request acceptance if needed
- acceptFriendRequestIfNeeded(content);
+ if (!content.isFriendRequest() && !isGroupChatMessage(content)) {
+ becomeFriendsWithContactIfNeeded(content.getSender(), true, false);
+ }
- // Loki - Session requests
+ // Loki - Handle session request if needed
handleSessionRequestIfNeeded(content);
- // Loki - Store pre key bundle
- // We shouldn't store it if it's a pairing message
- if (!content.getPairingAuthorisation().isPresent()) {
+ // Loki - Store pre key bundle if needed
+ if (!content.getDeviceLink().isPresent()) {
storePreKeyBundleIfNeeded(content);
}
@@ -310,35 +311,35 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
// Loki - Store the sender display name if needed
Optional rawSenderDisplayName = content.senderDisplayName;
if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) {
- // If we got a name from our primary device then we set our profile name to match it
- String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
- if (ourPrimaryDevice != null && content.getSender().equals(ourPrimaryDevice)) {
+ // If we got a name from our master device then set our display name to match
+ String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
+ if (ourMasterDevice != null && content.getSender().equals(ourMasterDevice)) {
TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get());
}
// If we receive a message from our device then don't set the display name in the database (as we probably have a alias set for them)
- MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success(isOneOfOurDevice -> {
- if (!isOneOfOurDevice) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); }
+ MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success( isOneOfOurDevices -> {
+ if (!isOneOfOurDevices) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); }
return Unit.INSTANCE;
});
}
- if (content.getPairingAuthorisation().isPresent()) {
- handlePairingMessage(content.getPairingAuthorisation().get(), content);
+ if (content.getDeviceLink().isPresent()) {
+ handleDeviceLinkMessage(content.getDeviceLink().get(), content);
} else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
- if (!content.isFriendRequest() && message.isUnpairingRequest()) {
- // Make sure we got the request from our primary device
- String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
- if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) {
+ if (!content.isFriendRequest() && message.isUnlinkingRequest()) {
+ // Make sure we got the request from our master device
+ String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
+ if (ourMasterDevice != null && ourMasterDevice.equals(content.getSender())) {
TextSecurePreferences.setDatabaseResetFromUnpair(context, true);
- MultiDeviceUtilities.checkForRevocation(context);
+ MultiDeviceUtilities.checkIsRevokedSlaveDevice(context);
}
} else {
// Loki - Don't process session restore message any further
- if (message.isSessionRestore() || message.isSessionRequest()) { return; }
+ if (message.isSessionRestorationRequest() || message.isSessionRequest()) { return; }
if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
@@ -362,6 +363,18 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
handleNeedsDeliveryReceipt(content, message);
}
+ // If we received a friend request, but we were already friends with the user, reset the session
+ if (content.isFriendRequest() && !message.isGroupMessage()) {
+ Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
+ ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
+ long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
+ if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
+ resetSession(content.getSender());
+ // Let our other devices know that we have reset the session
+ MessageSender.syncContact(context, sender.getAddress());
+ }
+ }
+
// Loki - Handle friend request logic if needed
updateFriendRequestStatusIfNeeded(content, message);
}
@@ -375,7 +388,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp());
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
- else if (syncMessage.getContacts().isPresent()) handleSynchronizeContactMessage(syncMessage.getContacts().get());
+ else if (syncMessage.getContacts().isPresent()) handleContactSyncMessage(syncMessage.getContacts().get());
else Log.w(TAG, "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) {
Log.i(TAG, "Got call message...");
@@ -402,11 +415,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (envelope.isPreKeySignalMessage()) {
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob());
}
-
- // Loki - Handle session reset logic
- if (!content.isFriendRequest()) {
- cipher.handleSessionResetRequestIfNeeded(content, cipher.getSessionStatus(content));
- }
} catch (ProtocolInvalidVersionException e) {
Log.w(TAG, e);
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
@@ -539,19 +547,19 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
if (threadId != null) {
- resetSession(content.getSender(), threadId);
+ resetSession(content.getSender());
MessageNotifier.updateNotification(context, threadId);
}
}
- private void resetSession(String hexEncodedPublicKey, long threadId) {
+ private void resetSession(String hexEncodedPublicKey) {
TextSecureSessionStore sessionStore = new TextSecureSessionStore(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
Log.d("Loki", "Received a session reset request from: " + hexEncodedPublicKey + "; archiving the session.");
sessionStore.archiveAllSessions(hexEncodedPublicKey);
- lokiThreadDatabase.setSessionResetStatus(threadId, LokiThreadSessionResetStatus.REQUEST_RECEIVED);
+ lokiThreadDatabase.setSessionResetStatus(hexEncodedPublicKey, LokiSessionResetStatus.REQUEST_RECEIVED);
Log.d("Loki", "Sending a ping back to " + hexEncodedPublicKey + ".");
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
@@ -593,7 +601,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{
GroupMessageProcessor.process(context, content, message, false);
- if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(content, message).getExpireMessages()) {
+ if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getRecipientForMessage(content, message).getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent());
}
@@ -619,7 +627,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{
try {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
- Recipient recipient = getMessageDestination(content, message);
+ Recipient recipient = getRecipientForMessage(content, message);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromSerialized(content.getSender()),
message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, true,
@@ -670,45 +678,44 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
- private void handleSynchronizeContactMessage(@NonNull ContactsMessage contactsMessage) {
- if (contactsMessage.getContactsStream().isStream()) {
- Log.d("Loki", "Received contact sync message");
+ private void handleContactSyncMessage(@NonNull ContactsMessage contactsMessage) {
+ if (!contactsMessage.getContactsStream().isStream()) { return; }
+ Log.d("Loki", "Received contact sync message.");
- try {
- InputStream in = contactsMessage.getContactsStream().asStream().getInputStream();
- DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in);
- List devices = contactsInputStream.readAll();
- for (DeviceContact deviceContact : devices) {
- // Check if we have the contact as a friend and that we're not trying to sync our own device
- String pubKey = deviceContact.getNumber();
- Address address = Address.fromSerialized(pubKey);
- if (!address.isPhone() || address.toPhoneString().equals(TextSecurePreferences.getLocalNumber(context))) { continue; }
+ try {
+ InputStream in = contactsMessage.getContactsStream().asStream().getInputStream();
+ DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in);
+ List deviceContacts = contactsInputStream.readAll();
+ for (DeviceContact deviceContact : deviceContacts) {
+ // Check if we have the contact as a friend and that we're not trying to sync our own device
+ String hexEncodedPublicKey = deviceContact.getNumber();
+ Address address = Address.fromSerialized(hexEncodedPublicKey);
+ if (!address.isPhone() || address.toPhoneString().equals(TextSecurePreferences.getLocalNumber(context))) { continue; }
- /*
- If we're not friends with the contact we received or our friend request expired then we should send them a friend request
- otherwise if we have received a friend request with from them then we should automatically accept the friend request
- */
- Recipient recipient = Recipient.from(context, address, false);
- long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
- LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId);
- if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
- MessageSender.sendBackgroundFriendRequest(context, pubKey, "Please accept to enable messages to be synced across devices");
- Log.d("Loki", "Sent friend request to " + pubKey);
- } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
- // Accept the incoming friend request
- becomeFriendsWithContact(pubKey, false, false);
- // Send them an accept message back
- MessageSender.sendBackgroundMessage(context, pubKey);
- Log.d("Loki", "Became friends with " + deviceContact.getNumber());
- }
-
- // TODO: Handle blocked - If user is not blocked then we should do the friend request logic otherwise add them to our block list
- // TODO: Handle expiration timer - Update expiration timer?
- // TODO: Handle avatar - Download and set avatar?
+ /*
+ If we're not friends with the contact we received or our friend request expired then we should send them a friend request.
+ Otherwise, if we have received a friend request from them, automatically accept the friend request.
+ */
+ Recipient recipient = Recipient.from(context, address, false);
+ long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
+ LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
+ if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
+ MessageSender.sendBackgroundFriendRequest(context, hexEncodedPublicKey, "Please accept to enable messages to be synced across devices");
+ Log.d("Loki", "Sent friend request to " + hexEncodedPublicKey);
+ } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
+ // Accept the incoming friend request
+ becomeFriendsWithContactIfNeeded(hexEncodedPublicKey, false, false);
+ // Send them an accept message back
+ MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
+ Log.d("Loki", "Became friends with " + deviceContact.getNumber());
}
- } catch (Exception e) {
- Log.d("Loki", "Failed to sync contact: " + e);
+
+ // TODO: Handle blocked - If user is not blocked then we should do the friend request logic otherwise add them to our block list
+ // TODO: Handle expiration timer - Update expiration timer?
+ // TODO: Handle avatar - Download and set avatar?
}
+ } catch (Exception e) {
+ Log.d("Loki", "Failed to sync contact: " + e + ".");
}
}
@@ -751,7 +758,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
}
- // Loki - If we received a sync message from our master device then we need to extract the avatar url
+ // Loki - If we received a sync message from our master device then we need to extract the profile picture url
if (isSenderMasterDevice) {
handleProfileKey(content, message.getMessage());
}
@@ -841,27 +848,27 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
@NonNull Optional messageServerIDOrNull)
throws StorageFailedException
{
- Recipient originalRecipient = getMessageDestination(content, message);
- Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
+ Recipient originalRecipient = getRecipientForMessage(content, message);
+ Recipient masterRecipient = getMasterRecipientForMessage(content, message);
- notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice());
+ notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice());
Optional quote = getValidatedQuote(message.getQuote());
Optional> sharedContacts = getContacts(message.getSharedContacts());
Optional> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
Optional sticker = getStickerAttachment(message.getSticker());
- Address sender = primaryDeviceRecipient.getAddress();
+ Address sender = masterRecipient.getAddress();
// If message is from group then we need to map it to get the sender of the message
if (message.isGroupMessage()) {
- sender = getPrimaryDeviceRecipient(content.getSender()).getAddress();
+ sender = getMasterRecipient(content.getSender()).getAddress();
}
// Ignore messages from ourselves
if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; }
- IncomingMediaMessage mediaMessage = new IncomingMediaMessage(sender, message.getTimestamp(), -1,
+ IncomingMediaMessage mediaMessage = new IncomingMediaMessage(sender, message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
quote, sharedContacts, linkPreviews, sticker);
@@ -905,12 +912,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
- // Loki - Run db updates in the background, we should look into fixing this in the future
+ // Loki - Run database updates in the background, we should look into fixing this in the future
AsyncTask.execute(() -> {
// Loki - Store message server ID
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
- // Loki - Update mapping of message to original thread id
+ // Loki - Update mapping of message to original thread ID
if (insertResult.isPresent()) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
@@ -1028,10 +1035,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
@NonNull Optional messageServerIDOrNull)
throws StorageFailedException
{
- SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
- String body = message.getBody().isPresent() ? message.getBody().get() : "";
- Recipient originalRecipient = getMessageDestination(content, message);
- Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
+ SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
+ String body = message.getBody().isPresent() ? message.getBody().get() : "";
+ Recipient originalRecipient = getRecipientForMessage(content, message);
+ Recipient masterRecipient = getMasterRecipientForMessage(content, message);
if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent());
@@ -1042,26 +1049,26 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) {
threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second;
} else {
- notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice());
+ notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice());
- Address sender = primaryDeviceRecipient.getAddress();
+ Address sender = masterRecipient.getAddress();
// If message is from group then we need to map it to get the sender of the message
if (message.isGroupMessage()) {
- sender = getPrimaryDeviceRecipient(content.getSender()).getAddress();
+ sender = getMasterRecipient(content.getSender()).getAddress();
}
// Ignore messages from ourselves
if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; }
- IncomingTextMessage _textMessage = new IncomingTextMessage(sender,
- content.getSenderDevice(),
- message.getTimestamp(), body,
- message.getGroupInfo(),
- message.getExpiresInSeconds() * 1000L,
- content.isNeedsReceipt());
+ IncomingTextMessage tm = new IncomingTextMessage(sender,
+ content.getSenderDevice(),
+ message.getTimestamp(), body,
+ message.getGroupInfo(),
+ message.getExpiresInSeconds() * 1000L,
+ content.isNeedsReceipt());
- IncomingEncryptedMessage textMessage = new IncomingEncryptedMessage(_textMessage, body);
+ IncomingEncryptedMessage textMessage = new IncomingEncryptedMessage(tm, body);
// Ignore the message if the body is empty
if (textMessage.getMessageBody().length() == 0) { return; }
@@ -1079,7 +1086,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
MessageNotifier.updateNotification(context, threadId);
}
- // Loki - Run db updates in background, we should look into fixing this in the future
+ // Loki - Run database updates in background, we should look into fixing this in the future
AsyncTask.execute(() -> {
if (insertResult.isPresent()) {
InsertResult result = insertResult.get();
@@ -1090,7 +1097,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
// Loki - Store message server ID
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
- // Loki - Update mapping of message to original thread id
+ // Loki - Update mapping of message to original thread ID
if (result.getMessageId() > -1) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
@@ -1102,95 +1109,86 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
- private boolean isValidPairingMessage(@NonNull PairingAuthorisation authorisation) {
+ private boolean isValidDeviceLinkMessage(@NonNull DeviceLink authorisation) {
boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
- boolean isRequest = (authorisation.getType() == PairingAuthorisation.Type.REQUEST);
+ boolean isRequest = (authorisation.getType() == DeviceLink.Type.REQUEST);
if (authorisation.getRequestSignature() == null) {
Log.d("Loki", "Ignoring pairing request message without a request signature.");
return false;
} else if (isRequest && isSecondaryDevice) {
Log.d("Loki", "Ignoring unexpected pairing request message (the device is already paired as a secondary device).");
return false;
- } else if (isRequest && !authorisation.getPrimaryDevicePublicKey().equals(userHexEncodedPublicKey)) {
+ } else if (isRequest && !authorisation.getMasterHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
Log.d("Loki", "Ignoring pairing request message addressed to another user.");
return false;
- } else if (isRequest && authorisation.getSecondaryDevicePublicKey().equals(userHexEncodedPublicKey)) {
+ } else if (isRequest && authorisation.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
Log.d("Loki", "Ignoring pairing request message from self.");
return false;
}
return authorisation.verify();
}
- private void handleProfileAvatar(SignalServiceContent content, String url) {
- Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
- ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(primaryDevice, url));
- }
-
- private void handlePairingMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceContent content) {
+ private void handleDeviceLinkMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
- if (authorisation.getType() == PairingAuthorisation.Type.REQUEST) {
- handlePairingRequestMessage(authorisation, content);
- } else if (authorisation.getSecondaryDevicePublicKey().equals(userHexEncodedPublicKey)) {
- handlePairingAuthorisationMessage(authorisation, content);
+ if (deviceLink.getType() == DeviceLink.Type.REQUEST) {
+ handleDeviceLinkRequestMessage(deviceLink, content);
+ } else if (deviceLink.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
+ handleDeviceLinkAuthorizedMessage(deviceLink, content);
}
}
- private void handlePairingRequestMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceContent content) {
- boolean isValid = isValidPairingMessage(authorisation);
+ private void handleDeviceLinkRequestMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
+ boolean isValid = isValidDeviceLinkMessage(deviceLink);
DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared();
- if (isValid && linkingSession.isListeningForLinkingRequests()) {
- // Loki - If we successfully received a request then we should store the PreKeyBundle
- storePreKeyBundleIfNeeded(content);
- linkingSession.processLinkingRequest(authorisation);
- }
+ if (!isValid || !linkingSession.isListeningForLinkingRequests()) { return; }
+ storePreKeyBundleIfNeeded(content);
+ linkingSession.processLinkingRequest(deviceLink);
}
- private void handlePairingAuthorisationMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceContent content) {
- // Prepare
- boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
- if (isSecondaryDevice) {
- Log.d("Loki", "Ignoring unexpected pairing authorisation message (the device is already paired as a secondary device).");
+ private void handleDeviceLinkAuthorizedMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
+ // Check preconditions
+ boolean hasExistingDeviceLink = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
+ if (hasExistingDeviceLink) {
+ Log.d("Loki", "Ignoring unexpected device link message (the device is already linked as a slave device).");
return;
}
- boolean isValid = isValidPairingMessage(authorisation);
+ boolean isValid = isValidDeviceLinkMessage(deviceLink);
if (!isValid) {
- Log.d("Loki", "Ignoring invalid pairing authorisation message.");
+ Log.d("Loki", "Ignoring invalid device link message.");
return;
}
if (!DeviceLinkingSession.Companion.getShared().isListeningForLinkingRequests()) {
- Log.d("Loki", "Ignoring pairing authorisation message.");
+ Log.d("Loki", "Ignoring device link message.");
return;
}
- if (authorisation.getType() != PairingAuthorisation.Type.GRANT) { return; }
- Log.d("Loki", "Received pairing authorisation message from: " + authorisation.getPrimaryDevicePublicKey() + ".");
- // Save PreKeyBundle if for whatever reason we got one
+ if (deviceLink.getType() != DeviceLink.Type.AUTHORIZATION) { return; }
+ Log.d("Loki", "Received device link authorized message from: " + deviceLink.getMasterHexEncodedPublicKey() + ".");
+ // Save pre key bundle if we somehow got one
storePreKeyBundleIfNeeded(content);
// Process
- DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(authorisation);
- // Store the primary device's public key
+ DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(deviceLink);
+ // Store the master device's ID
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
- DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(userHexEncodedPublicKey);
- DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(authorisation);
- TextSecurePreferences.setMasterHexEncodedPublicKey(context, authorisation.getPrimaryDevicePublicKey());
+ DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userHexEncodedPublicKey);
+ DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink);
+ TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.getMasterHexEncodedPublicKey());
TextSecurePreferences.setMultiDevice(context, true);
- // Send a background message to the primary device
- MessageSender.sendBackgroundMessage(context, authorisation.getPrimaryDevicePublicKey());
- // Propagate the updates to the file server
- LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared();
- storageAPI.updateUserDeviceMappings();
- // Update display names
+ // Send a background message to the master device
+ MessageSender.sendBackgroundMessage(context, deviceLink.getMasterHexEncodedPublicKey());
+ // Update display name if needed
if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
}
- // Profile avatar updates
+ // Update profile picture if needed
if (content.getDataMessage().isPresent()) {
handleProfileKey(content, content.getDataMessage().get());
}
- // Contact sync
+ // Handle contact sync if needed
if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) {
- handleSynchronizeContactMessage(content.getSyncMessage().get().getContacts().get());
+ handleContactSyncMessage(content.getSyncMessage().get().getContacts().get());
}
+ // The device link is propagated to the file server in LandingActivity.onDeviceLinkAuthorized because we can handle the error there
}
private void setDisplayName(String hexEncodedPublicKey, String profileName) {
@@ -1199,95 +1197,69 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
private void updateGroupChatMessageServerID(Optional messageServerIDOrNull, Optional insertResult) {
- if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) {
- long messageID = insertResult.get().getMessageId();
- long messageServerID = messageServerIDOrNull.get();
- DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID);
- }
+ if (!insertResult.isPresent() || !messageServerIDOrNull.isPresent()) { return; }
+ long messageID = insertResult.get().getMessageId();
+ long messageServerID = messageServerIDOrNull.get();
+ DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID);
}
private void storePreKeyBundleIfNeeded(@NonNull SignalServiceContent content) {
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
- if (!sender.isGroupRecipient() && content.lokiServiceMessage.isPresent()) {
- LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
- if (lokiMessage.getPreKeyBundleMessage() != null) {
- int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
- LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
- ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
- LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
+ if (sender.isGroupRecipient() || !content.lokiServiceMessage.isPresent()) { return; }
+ LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
+ if (lokiMessage.getPreKeyBundleMessage() == null) { return; }
+ int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
+ LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
+ if (registrationID <= 0) { return; }
+ Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
+ PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
+ lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle);
- // Loki - Store the latest pre key bundle
- if (registrationID > 0) {
- Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
- PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
- 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 (content.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());
- }
- }
- }
- }
- }
- }
-
- private void acceptFriendRequestIfNeeded(@NonNull SignalServiceContent content) {
- // If we get anything other than a friend request, we can assume that we have a session with the other user
- if (content.isFriendRequest() || isGroupChatMessage(content)) { return; }
- becomeFriendsWithContact(content.getSender(), true, false);
}
private void handleSessionRequestIfNeeded(@NonNull SignalServiceContent content) {
- if (content.isFriendRequest() && isSessionRequest(content)) {
- // Check if the session request from a member in one of our groups or our friend
- LokiStorageAPI.shared.getPrimaryDevicePublicKey(content.getSender()).success(primaryDevicePublicKey -> {
- String sender = primaryDevicePublicKey != null ? primaryDevicePublicKey : content.getSender();
- long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false));
- LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
- boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
- boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender);
- boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups;
- if (shouldAcceptSessionRequest) {
- // Send a background message to acknowledge session request
- MessageSender.sendBackgroundMessage(context, content.getSender());
- }
- return Unit.INSTANCE;
- });
- }
+ if (!content.isFriendRequest() || !isSessionRequest(content)) { return; }
+ // Check if the session request came from a member in one of our groups or one of our friends
+ LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()).success( masterHexEncodedPublicKey -> {
+ String sender = masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : content.getSender();
+ long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false));
+ LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
+ boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
+ boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender);
+ boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups;
+ if (shouldAcceptSessionRequest) {
+ MessageSender.sendBackgroundMessage(context, content.getSender()); // Send a background message to acknowledge
+ }
+ return Unit.INSTANCE;
+ });
}
- private void becomeFriendsWithContact(String pubKey, boolean syncContact, boolean force) {
+ private void becomeFriendsWithContactIfNeeded(String hexEncodedPublicKey, boolean requiresContactSync, boolean canSkip) {
+ // Ignore friend requests to group recipients
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
- Recipient contactID = Recipient.from(context, Address.fromSerialized(pubKey), false);
+ Recipient contactID = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
if (contactID.isGroupRecipient()) return;
-
+ // Ignore friend requests to recipients we're already friends with
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(contactID);
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) { return; }
-
- // We shouldn't be able to skip from None -> Friends in normal circumstances.
- // Multi-device is the exception to this rule because we want to automatically be friends with a secondary device
- if (!force && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; }
-
- // If the thread's friend request status is not `FRIENDS`, but we're receiving a message,
+ // We shouldn't be able to skip from NONE to FRIENDS under normal circumstances.
+ // Multi-device is the one exception to this rule because we want to automatically become friends with slave devices.
+ if (!canSkip && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; }
+ // If the thread's friend request status is not `FRIENDS` or `NONE`, but we're receiving a message,
// it must be a friend request accepted message. Declining a friend request doesn't send a message.
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
- // Send out a contact sync message
- if (syncContact) {
+ // Send out a contact sync message if needed
+ if (requiresContactSync) {
MessageSender.syncContact(context, contactID.getAddress());
}
- // Allow profile sharing with contact
+ // Enable profile sharing with the recipient
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(contactID, true);
// Update the last message if needed
- LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> {
+ LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey).success( masterHexEncodedPublicKey -> {
Util.runOnMain(() -> {
- long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false));
- FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
+ long masterThreadID = (masterHexEncodedPublicKey == null) ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(masterHexEncodedPublicKey), false));
+ FriendRequestHandler.updateLastFriendRequestMessage(context, masterThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
});
return Unit.INSTANCE;
});
@@ -1295,25 +1267,24 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
if (!content.isFriendRequest() || message.isGroupMessage() || message.isSessionRequest()) { return; }
- // This handles the case where another user sends us a regular message without authorisation
Promise promise = PromiseUtil.timeout(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), 8000);
boolean shouldBecomeFriends = PromiseUtil.get(promise, false);
if (shouldBecomeFriends) {
// Become friends AND update the message they sent
- becomeFriendsWithContact(content.getSender(), true, true);
+ becomeFriendsWithContactIfNeeded(content.getSender(), true, true);
// Send them an accept message back
MessageSender.sendBackgroundMessage(context, content.getSender());
} else {
// Do regular friend request logic checks
- Recipient originalRecipient = getMessageDestination(content, message);
- Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
+ Recipient originalRecipient = getRecipientForMessage(content, message);
+ Recipient masterRecipient = getMasterRecipientForMessage(content, message);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
// Loki - Friend requests only work in direct chats
if (!originalRecipient.getAddress().isPhone()) { return; }
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient);
- long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient);
+ long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(masterRecipient);
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
@@ -1482,7 +1453,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
private void triggerSessionRestorePrompt(@NonNull String sender) {
- Recipient primaryRecipient = getPrimaryDeviceRecipient(sender);
+ Recipient primaryRecipient = getMasterRecipient(sender);
if (!primaryRecipient.isGroupRecipient()) {
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient);
DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, sender);
@@ -1537,12 +1508,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) {
database.setProfileKey(recipient, message.getProfileKey().get());
database.setUnidentifiedAccessMode(recipient, RecipientDatabase.UnidentifiedAccessMode.UNKNOWN);
- String url = content.senderProfileAvatarUrl.or("");
+ String url = content.senderProfilePictureURL.or("");
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(recipient, url));
// Loki - If the recipient is our master device then we need to go and update our avatar mappings on the public chats
if (recipient.isOurMasterDevice()) {
- ApplicationContext.getInstance(context).updatePublicChatProfileAvatarIfNeeded();
+ ApplicationContext.getInstance(context).updatePublicChatProfilePictureIfNeeded();
}
}
}
@@ -1559,11 +1530,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void handleDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message)
{
- // Redirect message to primary device conversation
+ // Redirect message to master device conversation
Address sender = Address.fromSerialized(content.getSender());
if (sender.isPhone()) {
- Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
- sender = primaryDevice.getAddress();
+ Recipient masterDevice = getMasterRecipient(content.getSender());
+ sender = masterDevice.getAddress();
}
for (long timestamp : message.getTimestamps()) {
@@ -1579,11 +1550,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{
if (TextSecurePreferences.isReadReceiptsEnabled(context)) {
- // Redirect message to primary device conversation
+ // Redirect message to master device conversation
Address sender = Address.fromSerialized(content.getSender());
if (sender.isPhone()) {
- Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
- sender = primaryDevice.getAddress();
+ Recipient masterDevice = getMasterRecipient(content.getSender());
+ sender = masterDevice.getAddress();
}
for (long timestamp : message.getTimestamps()) {
@@ -1614,7 +1585,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient);
} else {
// See if we need to redirect the message
- author = getPrimaryDeviceRecipient(content.getSender());
+ author = getMasterRecipient(content.getSender());
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(author);
}
@@ -1748,9 +1719,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
private Optional insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) {
- Recipient primaryDevice = getPrimaryDeviceRecipient(sender);
- SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
- IncomingTextMessage textMessage = new IncomingTextMessage(primaryDevice.getAddress(),
+ Recipient masterDevice = getMasterRecipient(sender);
+ SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
+ IncomingTextMessage textMessage = new IncomingTextMessage(masterDevice.getAddress(),
senderDevice, timestamp, "",
Optional.absent(), 0, false);
@@ -1770,11 +1741,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (message.getMessage().isGroupMessage()) {
return getSyncMessageDestination(message);
} else {
- return getPrimaryDeviceRecipient(message.getDestination().get());
+ return getMasterRecipient(message.getDestination().get());
}
}
- private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) {
+ private Recipient getRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
if (message.isGroupMessage()) {
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())), false);
} else {
@@ -1782,34 +1753,34 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
- private Recipient getMessagePrimaryDestination(SignalServiceContent content, SignalServiceDataMessage message) {
+ private Recipient getMasterRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
if (message.isGroupMessage()) {
- return getMessageDestination(content, message);
+ return getRecipientForMessage(content, message);
} else {
- return getPrimaryDeviceRecipient(content.getSender());
+ return getMasterRecipient(content.getSender());
}
}
/**
- * Get the primary device recipient of the passed in device.
+ * Get the master device recipient of the provided device.
*
- * If the device doesn't have a primary device then it will return the same device.
- * If the device is our primary device then it will return our current device.
- * Otherwise it will return the primary device.
+ * If the device doesn't have a master device this will return the same device.
+ * If the device is our master device then it will return our current device.
+ * Otherwise it will return the master device.
*/
- private Recipient getPrimaryDeviceRecipient(String pubKey) {
+ private Recipient getMasterRecipient(String hexEncodedPublicKey) {
try {
- String primaryDevice = PromiseUtil.timeout(LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey), 5000).get();
- String publicKey = (primaryDevice != null) ? primaryDevice : pubKey;
- // If the public key matches our primary device then we need to forward the message to ourselves (Note to self)
- String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
- if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) {
- publicKey = TextSecurePreferences.getLocalNumber(context);
+ String masterHexEncodedPublicKey = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey), 5000).get();
+ String targetHexEncodedPublicKey = (masterHexEncodedPublicKey != null) ? masterHexEncodedPublicKey : hexEncodedPublicKey;
+ // If the public key matches our master device then we need to forward the message to ourselves (note to self)
+ String ourMasterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
+ if (ourMasterHexEncodedPublicKey != null && ourMasterHexEncodedPublicKey.equals(targetHexEncodedPublicKey)) {
+ targetHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
}
- return Recipient.from(context, Address.fromSerialized(publicKey), false);
+ return Recipient.from(context, Address.fromSerialized(targetHexEncodedPublicKey), false);
} catch (Exception e) {
- Log.d("Loki", "Failed to get primary device public key for " + pubKey + ". " + e.getMessage());
- return Recipient.from(context, Address.fromSerialized(pubKey), false);
+ Log.d("Loki", "Failed to get master device for: " + hexEncodedPublicKey + ". " + e.getMessage());
+ return Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
}
}
@@ -1831,11 +1802,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
- if (content.getPairingAuthorisation().isPresent()) {
+ if (content.getDeviceLink().isPresent()) {
return false;
} else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
- Recipient conversation = getMessageDestination(content, message);
+ Recipient conversation = getRecipientForMessage(content, message);
if (conversation.isGroupRecipient() && conversation.isBlocked()) {
return true;
diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
index 04e672c20a..ef9fa207ac 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
@@ -50,8 +50,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
+import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.IOException;
@@ -368,7 +368,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
if (!member.isPhone() || member.serialize().equalsIgnoreCase(localNumber)) { continue; }
try {
- List secondaryDevices = PromiseUtil.timeout(LokiStorageAPI.shared.getSecondaryDevicePublicKeys(member.serialize()), 5000).get();
+ Set secondaryDevices = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getSlaveHexEncodedPublicKeys(member.serialize()), 5000).get();
memberSet.addAll(Stream.of(secondaryDevices).map(string -> {
// Loki - Calling .map(Address::fromSerialized) is causing errors, thus we use the long method :(
return Address.fromSerialized(string);
diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
index ae133a0394..09bab54caa 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
@@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.logging.Log;
-import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -43,13 +42,12 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
+import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -291,11 +289,11 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
} else {
LokiSyncMessage syncMessage = null;
if (shouldSendSyncMessage) {
- // Set the sync message destination the primary device, this way it will show that we sent a message to the primary device and not a secondary device
- String primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null);
- SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice);
- // We also need to use the original message id and not -1
- syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId);
+ // Set the sync message destination to the master device, this way it will show that we sent a message to the master device and not the slave device
+ String masterDevice = PromiseUtil.get(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(address.getNumber()), null);
+ SignalServiceAddress masterAddress = masterDevice == null ? address : new SignalServiceAddress(masterDevice);
+ // We also need to use the original message ID and not -1
+ syncMessage = new LokiSyncMessage(masterAddress, templateMessageId);
}
return messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified();
}
diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
index a8a084a447..7c0f04834a 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
@@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
-import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
@@ -30,7 +29,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
+import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
@@ -201,7 +200,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
{
try {
// rotateSenderCertificateIfNecessary();
- Recipient recipient = Recipient.from(context, destination, false);
+ Recipient recipient = Recipient.from(context, destination, false);
SignalServiceAddress address = getPushAddress(recipient.getAddress());
Optional profileKey = getProfileKey(recipient);
Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
@@ -236,11 +235,11 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
} else {
LokiSyncMessage syncMessage = null;
if (shouldSendSyncMessage) {
- // Set the sync message destination to the primary device, this way it will show that we sent a message to the primary device and not a secondary device
- String primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null);
- SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice);
- // We also need to use the original message id and not -1
- syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId);
+ // Set the sync message destination to the master device, this way it will show that we sent a message to the master device and not the slave device
+ String masterDevice = PromiseUtil.get(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(address.getNumber()), null);
+ SignalServiceAddress masterAddress = masterDevice == null ? address : new SignalServiceAddress(masterDevice);
+ // We also need to use the original message ID and not -1
+ syncMessage = new LokiSyncMessage(masterAddress, templateMessageId);
}
return messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified();
}
diff --git a/src/org/thoughtcrime/securesms/loki/LokiSessionResetImplementation.kt b/src/org/thoughtcrime/securesms/loki/LokiSessionResetImplementation.kt
new file mode 100644
index 0000000000..f2f92edfd7
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/LokiSessionResetImplementation.kt
@@ -0,0 +1,34 @@
+package org.thoughtcrime.securesms.loki
+
+import android.content.Context
+import org.thoughtcrime.securesms.database.DatabaseFactory
+import org.thoughtcrime.securesms.sms.MessageSender
+import org.whispersystems.libsignal.loki.LokiSessionResetProtocol
+import org.whispersystems.libsignal.loki.LokiSessionResetStatus
+import org.whispersystems.libsignal.protocol.PreKeySignalMessage
+
+class LokiSessionResetImplementation(private val context: Context) : LokiSessionResetProtocol {
+
+ override fun getSessionResetStatus(hexEncodedPublicKey: String): LokiSessionResetStatus {
+ return DatabaseFactory.getLokiThreadDatabase(context).getSessionResetStatus(hexEncodedPublicKey)
+ }
+
+ override fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: LokiSessionResetStatus) {
+ return DatabaseFactory.getLokiThreadDatabase(context).setSessionResetStatus(hexEncodedPublicKey, sessionResetStatus)
+ }
+
+ override fun onNewSessionAdopted(hexEncodedPublicKey: String, oldSessionResetStatus: LokiSessionResetStatus) {
+ if (oldSessionResetStatus == LokiSessionResetStatus.IN_PROGRESS) {
+ // Send a message back to the contact to finalise session reset
+ MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey)
+ }
+
+ // TODO: Show session reset succeed message
+ }
+
+ override fun validatePreKeySignalMessage(sender: String, message: PreKeySignalMessage) {
+ val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getPreKeyRecord(sender)
+ check(preKeyRecord != null) { "Received a background message from a user without an associated pre key record." }
+ check(preKeyRecord.id == (message.preKeyId ?: -1)) { "Received a background message from an unknown source." }
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt b/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt
index b6aefdaa3a..0decbdd0c5 100644
--- a/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt
+++ b/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt
@@ -10,11 +10,11 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.*
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
+import org.whispersystems.libsignal.loki.LokiSessionResetStatus
import org.whispersystems.signalservice.internal.util.JsonUtil
import org.whispersystems.signalservice.loki.api.LokiPublicChat
import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
-import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
@@ -23,11 +23,11 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
companion object {
private val friendRequestTableName = "loki_thread_friend_request_database"
private val sessionResetTableName = "loki_thread_session_reset_database"
- public val publicChatTableName = "loki_public_chat_database"
- public val threadID = "thread_id"
+ val publicChatTableName = "loki_public_chat_database"
+ val threadID = "thread_id"
private val friendRequestStatus = "friend_request_status"
private val sessionResetStatus = "session_reset_status"
- public val publicChat = "public_chat"
+ val publicChat = "public_chat"
@JvmStatic val createFriendRequestTableCommand = "CREATE TABLE $friendRequestTableName ($threadID INTEGER PRIMARY KEY, $friendRequestStatus INTEGER DEFAULT 0);"
@JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTableName ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTableName ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
@@ -79,19 +79,21 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|| friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED
}
- override fun getSessionResetStatus(threadID: Long): LokiThreadSessionResetStatus {
+ fun getSessionResetStatus(hexEncodedPublicKey: String): LokiSessionResetStatus {
+ val threadID = getThreadID(hexEncodedPublicKey)
val database = databaseHelper.readableDatabase
val result = database.get(sessionResetTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
cursor.getInt(sessionResetStatus)
}
return if (result != null) {
- LokiThreadSessionResetStatus.values().first { it.rawValue == result }
+ LokiSessionResetStatus.values().first { it.rawValue == result }
} else {
- LokiThreadSessionResetStatus.NONE
+ LokiSessionResetStatus.NONE
}
}
- override fun setSessionResetStatus(threadID: Long, sessionResetStatus: LokiThreadSessionResetStatus) {
+ fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: LokiSessionResetStatus) {
+ val threadID = getThreadID(hexEncodedPublicKey)
val database = databaseHelper.writableDatabase
val contentValues = ContentValues(2)
contentValues.put(Companion.threadID, threadID)
diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt
index 125fcdd226..a3e08cc8a5 100644
--- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt
+++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.loki
import android.content.Context
import nl.komponents.kovenant.Promise
-import nl.komponents.kovenant.all
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map
import nl.komponents.kovenant.toFailVoid
@@ -16,40 +15,36 @@ import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.recipients.Recipient
-import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress
-import org.whispersystems.signalservice.loki.api.LokiStorageAPI
-import org.whispersystems.signalservice.loki.api.PairingAuthorisation
+import org.whispersystems.signalservice.loki.api.DeviceLink
+import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities
+import org.whispersystems.signalservice.loki.api.LokiFileServerAPI
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.utilities.recover
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
-import java.util.*
-import kotlin.concurrent.schedule
-fun checkForRevocation(context: Context) {
- val primaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return
- val ourDevice = TextSecurePreferences.getLocalNumber(context)
-
- LokiStorageAPI.shared.fetchDeviceMappings(primaryDevice).bind { mappings ->
- val ourMapping = mappings.find { it.secondaryDevicePublicKey == ourDevice }
- if (ourMapping != null) throw Error("Device has not been revoked")
- // remove pairing authorisations for our device
- DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(ourDevice)
- LokiStorageAPI.shared.updateUserDeviceMappings()
+fun checkIsRevokedSlaveDevice(context: Context) {
+ val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return
+ val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
+ LokiFileServerAPI.shared.getDeviceLinks(masterHexEncodedPublicKey, true).bind { deviceLinks ->
+ val deviceLink = deviceLinks.find { it.masterHexEncodedPublicKey == masterHexEncodedPublicKey && it.slaveHexEncodedPublicKey == hexEncodedPublicKey }
+ if (deviceLink != null) throw Error("Device hasn't been revoked.")
+ DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(hexEncodedPublicKey)
+ LokiFileServerAPI.shared.setDeviceLinks(setOf())
}.successUi {
- TextSecurePreferences.setNeedsRevocationCheck(context, false)
+ TextSecurePreferences.setNeedsIsRevokedSlaveDeviceCheck(context, false)
ApplicationContext.getInstance(context).clearData()
}.fail { error ->
- TextSecurePreferences.setNeedsRevocationCheck(context, true)
- Log.d("Loki", "Revocation check failed: ${error.message ?: error}")
+ TextSecurePreferences.setNeedsIsRevokedSlaveDeviceCheck(context, true)
+ Log.d("Loki", "Revocation check failed due to error: ${error.message ?: error}.")
}
}
fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise