Merge branch 'dev'

This commit is contained in:
Niels Andriesse 2020-02-17 16:55:48 +11:00
commit 68785fe44f
60 changed files with 740 additions and 955 deletions

View File

@ -199,8 +199,8 @@ dependencies {
implementation "com.github.ybq:Android-SpinKit:1.4.0" implementation "com.github.ybq:Android-SpinKit:1.4.0"
} }
def canonicalVersionCode = 35 def canonicalVersionCode = 36
def canonicalVersionName = "1.0.1" def canonicalVersionName = "1.0.2"
def postFixSize = 10 def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1, def abiPostFix = ['armeabi-v7a' : 1,

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"> android:shape="oval">
<solid android:color="@color/compose_view_background" /> <solid android:color="@color/compose_view_background" />
</shape> </shape>

View File

@ -30,7 +30,7 @@
android:textAlignment="center" android:textAlignment="center"
android:text="Users can share their Session ID by going into their account settings and tapping &quot;Share Session ID&quot;, or by sharing their QR code." /> android:text="Users can share their Session ID by going into their account settings and tapping &quot;Share Session ID&quot;, or by sharing their QR code." />
<org.thoughtcrime.securesms.loki.redesign.views.SeparatorView <org.thoughtcrime.securesms.loki.redesign.views.LabeledSeparatorView
android:id="@+id/separatorView" android:id="@+id/separatorView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="32dp" android:layout_height="32dp"

View File

@ -1,58 +1,59 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginStart="32dp" android:background="@drawable/default_session_background"
android:layout_marginEnd="32dp"
android:orientation="vertical"> android:orientation="vertical">
<TextView <View
android:id="@+id/titleTextView"
style="@style/Signal.Text.Headline.Registration"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_marginTop="40dp" android:layout_weight="1"/>
android:text="@string/activity_display_name_title"
android:textAlignment="center" />
<TextView <TextView
android:id="@+id/subtitleTextView"
style="@style/Signal.Text.Body"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginLeft="@dimen/very_large_spacing"
android:text="@string/activity_display_name_subtitle" android:layout_marginRight="@dimen/very_large_spacing"
android:textAlignment="center" /> android:textSize="@dimen/large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="Pick your display name" />
<org.thoughtcrime.securesms.components.LabeledEditText <TextView
android:id="@+id/nameEditText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginLeft="@dimen/very_large_spacing"
app:labeledEditText_background="@color/loki_darkest_gray" android:layout_marginTop="6dp"
app:labeledEditText_label="@string/activity_display_name_name_edit_text_label"/> android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/small_font_size"
android:textColor="@color/text"
android:text="This will be your name when you use Session." />
<com.dd.CircularProgressButton <EditText
android:id="@+id/nextButton" style="@style/SmallSessionEditText"
android:id="@+id/displayNameEditText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginBottom="40dp" android:layout_marginTop="12dp"
android:layout_gravity="center_horizontal" android:layout_marginRight="@dimen/very_large_spacing"
android:background="@color/signal_primary" android:hint="Enter a display name" />
android:textColor="@color/white"
app:cpb_colorIndicator="@color/white" <View
app:cpb_colorProgress="@color/textsecure_primary" android:layout_width="match_parent"
app:cpb_cornerRadius="4dp" android:layout_height="0dp"
app:cpb_selectorIdle="@drawable/progress_button_state" android:layout_weight="1"/>
app:cpb_textIdle="@string/activity_display_name_button_title" />
<Button
style="@style/MediumProminentFilledButton"
android:id="@+id/registerButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/small_spacing"
android:text="Continue" />
</LinearLayout> </LinearLayout>
</ScrollView>

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/default_session_background"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
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" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="6dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/small_font_size"
android:textColor="@color/text"
android:text="This will be your name when you use Session." />
<EditText
style="@style/SmallSessionEditText"
android:id="@+id/displayNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="12dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:hint="Enter a display name" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
style="@style/MediumProminentFilledButton"
android:id="@+id/registerButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/small_spacing"
android:text="Continue" />
</LinearLayout>

View File

@ -1,158 +1,77 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".loki.SeedActivity"> android:background="@drawable/default_session_background"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout <org.thoughtcrime.securesms.loki.redesign.views.SeedReminderView
android:id="@+id/seedReminderView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp" <View
android:orientation="vertical" android:layout_width="match_parent"
android:animateLayoutChanges="true"> android:layout_height="0dp"
android:layout_weight="1"/>
<TextView <TextView
android:id="@+id/titleTextView"
style="@style/Signal.Text.Headline.Registration"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="40dp" android:layout_marginLeft="@dimen/very_large_spacing"
android:text="@string/activity_key_pair_title" android:layout_marginRight="@dimen/very_large_spacing"
android:textAlignment="center" /> android:textSize="@dimen/large_font_size"
<TextView
android:id="@+id/seedExplanationTextView1"
style="@style/Signal.Text.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/activity_key_pair_seed_explanation_1"
android:textStyle="bold" android:textStyle="bold"
android:textAlignment="center" /> android:textColor="@color/text"
android:text="Meet your recovery phrase" />
<TextView <TextView
android:id="@+id/mnemonicTextView"
style="@style/Signal.Text.Body"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginLeft="@dimen/very_large_spacing"
android:alpha="0.8" android:layout_marginTop="10dp"
android:textStyle="italic" android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and dont give it to anyone. To restore your Session ID, launch Session and tap Continue your Session." />
<TextView
style="@style/SessionIDTextView"
android:id="@+id/seedTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:gravity="center"
android:textSize="@dimen/medium_font_size"
android:textAlignment="center" android:textAlignment="center"
tools:text="quick brown fox jump lazy dog" /> android:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" />
<TextView
android:id="@+id/revealButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textAlignment="center"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:alpha="0.6"
android:text="Hold to reveal" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button <Button
style="@style/MediumProminentOutlineButton"
android:id="@+id/copyButton" android:id="@+id/copyButton"
android:layout_width="match_parent" android:layout_width="196dp"
android:layout_height="50dp" android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="20dp" android:layout_marginBottom="@dimen/medium_spacing"
android:background="@color/transparent" android:text="Copy" />
android:textColor="@color/signal_primary"
android:text="@string/activity_key_pair_copy_button_title"
android:elevation="0dp"
android:stateListAnimator="@null" />
<TextView
android:id="@+id/seedExplanationTextView2"
style="@style/Signal.Text.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:visibility="gone"
android:text="@string/activity_key_pair_seed_explanation_2"
android:textAlignment="center" />
<org.thoughtcrime.securesms.components.LabeledEditText
android:id="@+id/mnemonicEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="20dp"
android:visibility="gone"
app:labeledEditText_background="@color/loki_darkest_gray"
app:labeledEditText_label="@string/activity_key_pair_mnemonic_edit_text_label"/>
<TextView
android:id="@+id/linkExplanationTextView"
style="@style/Signal.Text.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:visibility="gone"
android:text="@string/activity_key_pair_seed_explanation_3"
android:textAlignment="center" />
<org.thoughtcrime.securesms.components.LabeledEditText
android:id="@+id/publicKeyEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="20dp"
android:visibility="gone"
app:labeledEditText_background="@color/loki_darkest_gray"
app:labeledEditText_label="@string/activity_key_pair_public_key_edit_text_label"/>
<Button
android:id="@+id/scanQRButton"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/transparent"
android:elevation="0dp"
android:stateListAnimator="@null"
android:text="@string/fragment_scan_qr_code_title"
android:textColor="@color/signal_primary"
android:visibility="gone" />
<Button
android:id="@+id/toggleRestoreModeButton"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/transparent"
android:textColor="@color/signal_primary"
android:text="@string/activity_key_pair_toggle_mode_button_title_1"
android:elevation="0dp"
android:stateListAnimator="@null" />
<Button
android:id="@+id/toggleRegisterModeButton"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/transparent"
android:textColor="@color/signal_primary"
android:text="@string/activity_key_pair_toggle_mode_button_title_2"
android:visibility="gone"
android:elevation="0dp"
android:stateListAnimator="@null" />
<Button
android:id="@+id/toggleLinkModeButton"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/transparent"
android:textColor="@color/signal_primary"
android:text="@string/activity_key_pair_toggle_mode_button_title_3"
android:elevation="0dp"
android:stateListAnimator="@null" />
<com.dd.CircularProgressButton
android:id="@+id/mainButton"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="40dp"
android:layout_gravity="center_horizontal"
android:background="@color/signal_primary"
android:textColor="@color/white"
app:cpb_colorIndicator="@color/white"
app:cpb_colorProgress="@color/textsecure_primary"
app:cpb_cornerRadius="4dp"
app:cpb_selectorIdle="@drawable/progress_button_state"
app:cpb_textIdle="@string/activity_key_pair_main_button_title_1" />
</LinearLayout> </LinearLayout>
</ScrollView>

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/default_session_background"
android:gravity="center_horizontal"
android:orientation="vertical">
<org.thoughtcrime.securesms.loki.redesign.views.SeedReminderView
android:id="@+id/seedReminderView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
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" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="10dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and dont give it to anyone. To restore your Session ID, launch Session and tap Continue your Session." />
<TextView
style="@style/SessionIDTextView"
android:id="@+id/seedTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:gravity="center"
android:textSize="@dimen/medium_font_size"
android:textAlignment="center"
android:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" />
<TextView
android:id="@+id/revealButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textAlignment="center"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:alpha="0.6"
android:text="Hold to reveal" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
style="@style/MediumProminentOutlineButton"
android:id="@+id/copyButton"
android:layout_width="196dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginBottom="@dimen/medium_spacing"
android:text="Copy" />
</LinearLayout>

View File

@ -107,7 +107,7 @@
</RelativeLayout> </RelativeLayout>
<org.thoughtcrime.securesms.loki.redesign.views.SeparatorView <org.thoughtcrime.securesms.loki.redesign.views.LabeledSeparatorView
android:id="@+id/separatorView" android:id="@+id/separatorView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="32dp" android:layout_height="32dp"

View File

@ -38,7 +38,7 @@
android:id="@+id/moderator_icon_image_view" android:id="@+id/moderator_icon_image_view"
android:layout_width="20dp" android:layout_width="20dp"
android:layout_height="20dp" android:layout_height="20dp"
android:src="@drawable/icon_crown" android:src="@drawable/ic_crown"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />

View File

@ -30,7 +30,7 @@
android:textAlignment="center" android:textAlignment="center"
android:text="Users can share their Session ID by going into their account settings and tapping &quot;Share Session ID&quot;, or by sharing their QR code." /> android:text="Users can share their Session ID by going into their account settings and tapping &quot;Share Session ID&quot;, or by sharing their QR code." />
<org.thoughtcrime.securesms.loki.redesign.views.SeparatorView <org.thoughtcrime.securesms.loki.redesign.views.LabeledSeparatorView
android:id="@+id/separatorView" android:id="@+id/separatorView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="32dp" android:layout_height="32dp"

View File

@ -23,7 +23,7 @@
android:id="@+id/moderatorIconImageView" android:id="@+id/moderatorIconImageView"
android:layout_width="16dp" android:layout_width="16dp"
android:layout_height="16dp" android:layout_height="16dp"
android:src="@drawable/icon_crown" android:src="@drawable/ic_crown"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />

View File

@ -1618,13 +1618,13 @@
<!-- Friend request view --> <!-- Friend request view -->
<string name="view_friend_request_accept_button_title">Accept</string> <string name="view_friend_request_accept_button_title">Accept</string>
<string name="view_friend_request_reject_button_title">Decline</string> <string name="view_friend_request_reject_button_title">Decline</string>
<string name="view_friend_request_incoming_pending_message">%1$s sent you a message request</string> <string name="view_friend_request_incoming_pending_message">%1$s sent you a session request</string>
<string name="view_friend_request_incoming_accepted_message">You\'ve accepted %1$s\'s message request</string> <string name="view_friend_request_incoming_accepted_message">You\'ve accepted %1$s\'s session request</string>
<string name="view_friend_request_incoming_declined_message">You\'ve declined %1$s\'s message request</string> <string name="view_friend_request_incoming_declined_message">You\'ve declined %1$s\'s session request</string>
<string name="view_friend_request_incoming_expired_message">%1$s\'s message request has expired</string> <string name="view_friend_request_incoming_expired_message">%1$s\'s session request has expired</string>
<string name="view_friend_request_outgoing_pending_message">You\'ve sent %1$s a message request</string> <string name="view_friend_request_outgoing_pending_message">You\'ve sent %1$s a session request</string>
<string name="view_friend_request_outgoing_accepted_message">%1$s accepted your message request</string> <string name="view_friend_request_outgoing_accepted_message">%1$s accepted your session request</string>
<string name="view_friend_request_outgoing_expired_message">Your message request to %1$s has expired</string> <string name="view_friend_request_outgoing_expired_message">Your session request to %1$s has expired</string>
<!-- Conversation activity --> <!-- Conversation activity -->
<string name="activity_conversation_pending_friend_request_hint">Pending Friend Request…</string> <string name="activity_conversation_pending_friend_request_hint">Pending Friend Request…</string>
<string name="activity_conversation_default_hint">New Message</string> <string name="activity_conversation_default_hint">New Message</string>

View File

@ -6,6 +6,7 @@
<domain includeSubdomains="true">storage.seed1.loki.network</domain> <domain includeSubdomains="true">storage.seed1.loki.network</domain>
<domain includeSubdomains="true">storage.seed2.loki.network</domain> <domain includeSubdomains="true">storage.seed2.loki.network</domain>
<domain includeSubdomains="true">public.loki.foundation:22023</domain> <domain includeSubdomains="true">public.loki.foundation:22023</domain>
<domain includeSubdomains="true">file-dev.lokinet.org</domain>
<domain includeSubdomains="true">127.0.0.1</domain> <domain includeSubdomains="true">127.0.0.1</domain>
</domain-config> </domain-config>
</network-security-config> </network-security-config>

View File

@ -96,14 +96,13 @@ import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol; 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.LokiLongPoller;
import org.whispersystems.signalservice.loki.api.LokiP2PAPI; import org.whispersystems.signalservice.loki.api.LokiP2PAPI;
import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate; import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.LokiPublicChat;
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI; import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
import org.whispersystems.signalservice.loki.api.LokiRSSFeed; import org.whispersystems.signalservice.loki.api.LokiRSSFeed;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import java.security.Security; import java.security.Security;
import java.util.ArrayList; import java.util.ArrayList;
@ -115,7 +114,6 @@ import java.util.concurrent.TimeUnit;
import dagger.ObjectGraph; import dagger.ObjectGraph;
import kotlin.Unit; import kotlin.Unit;
import network.loki.messenger.BuildConfig; import network.loki.messenger.BuildConfig;
import okhttp3.Cache;
import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant; import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant;
import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant; import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
@ -184,18 +182,19 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
ProcessLifecycleOwner.get().getLifecycle().addObserver(this); ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
// Loki - Set up P2P API if needed // Loki - Set up P2P API if needed
setUpP2PAPI(); setUpP2PAPI();
// Loki - Set the cache
LokiDotNetAPI.setCache(new Cache(this.getCacheDir(), OK_HTTP_CACHE_SIZE));
// Loki - Update device mappings // Loki - Update device mappings
if (setUpStorageAPIIfNeeded()) { if (setUpStorageAPIIfNeeded()) {
LokiStorageAPI.Companion.getShared().updateUserDeviceMappings(); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
if (TextSecurePreferences.needsRevocationCheck(this)) { if (userHexEncodedPublicKey != null) {
checkNeedsRevocation(); LokiFileServerAPI.Companion.getShared().getDeviceLinks(userHexEncodedPublicKey, true);
if (TextSecurePreferences.getNeedsIsRevokedSlaveDeviceCheck(this)) {
MultiDeviceUtilities.checkIsRevokedSlaveDevice(this);
}
} }
} }
// Loki - Set up public chat manager // Loki - Set up public chat manager
lokiPublicChatManager = new LokiPublicChatManager(this); lokiPublicChatManager = new LokiPublicChatManager(this);
updatePublicChatProfileAvatarIfNeeded(); updatePublicChatProfilePictureIfNeeded();
} }
@Override @Override
@ -206,6 +205,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
KeyCachingService.onAppForegrounded(this); KeyCachingService.onAppForegrounded(this);
// Loki - Start long polling if needed // Loki - Start long polling if needed
startLongPollingIfNeeded(); startLongPollingIfNeeded();
// Loki - Start open group polling if needed
lokiPublicChatManager.startPollersIfNeeded(); lokiPublicChatManager.startPollersIfNeeded();
} }
@ -466,7 +466,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
boolean isDebugMode = BuildConfig.DEBUG; boolean isDebugMode = BuildConfig.DEBUG;
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabaseProtocol database = DatabaseFactory.getLokiAPIDatabase(this); LokiAPIDatabaseProtocol database = DatabaseFactory.getLokiAPIDatabase(this);
LokiStorageAPI.Companion.configure(isDebugMode, userHexEncodedPublicKey, userPrivateKey, database); LokiFileServerAPI.Companion.configure(isDebugMode, userHexEncodedPublicKey, userPrivateKey, database);
return true; return true;
} }
return false; return false;
@ -534,7 +534,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
public void createRSSFeedsIfNeeded() { public void createRSSFeedsIfNeeded() {
ArrayList<LokiRSSFeed> feeds = new ArrayList<>(); ArrayList<LokiRSSFeed> feeds = new ArrayList<>();
feeds.add(lokiNewsFeed()); // feeds.add(lokiNewsFeed());
feeds.add(lokiMessengerUpdatesFeed()); feeds.add(lokiMessengerUpdatesFeed());
for (LokiRSSFeed feed : feeds) { for (LokiRSSFeed feed : feeds) {
boolean isFeedSetUp = TextSecurePreferences.isChatSetUp(this, feed.getId()); boolean isFeedSetUp = TextSecurePreferences.isChatSetUp(this, feed.getId());
@ -590,7 +590,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
if (lokiMessengerUpdatesFeedPoller != null) lokiMessengerUpdatesFeedPoller.startIfNeeded(); if (lokiMessengerUpdatesFeedPoller != null) lokiMessengerUpdatesFeedPoller.startIfNeeded();
} }
public void updatePublicChatProfileAvatarIfNeeded() { public void updatePublicChatProfilePictureIfNeeded() {
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
LokiPublicChatAPI publicChatAPI = null; LokiPublicChatAPI publicChatAPI = null;
try { try {
@ -616,11 +616,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
} }
}); });
} }
// endregion
public void checkNeedsRevocation() {
MultiDeviceUtilities.checkForRevocation(this);
}
public void checkNeedsDatabaseReset() { public void checkNeedsDatabaseReset() {
if (TextSecurePreferences.resetDatabase(this)) { if (TextSecurePreferences.resetDatabase(this)) {
@ -646,4 +641,5 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
this.startActivity(mainIntent); this.startActivity(mainIntent);
Runtime.getRuntime().exit(0); Runtime.getRuntime().exit(0);
} }
// endregion
} }

View File

@ -59,7 +59,7 @@ import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.util.StreamDetails; import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI; import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI; 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.ByteArrayInputStream;
import java.io.File; import java.io.File;
@ -407,7 +407,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
//Loki - Upload the profile photo here //Loki - Upload the profile photo here
if (avatar != null) { if (avatar != null) {
Log.d("Loki", "Start uploading profile photo"); Log.d("Loki", "Start uploading profile photo");
LokiStorageAPI storageAPI = LokiStorageAPI.shared; LokiFileServerAPI storageAPI = LokiFileServerAPI.shared;
LokiDotNetAPI.UploadResult result = storageAPI.uploadProfilePicture(storageAPI.getServer(), profileKey, avatar); LokiDotNetAPI.UploadResult result = storageAPI.uploadProfilePicture(storageAPI.getServer(), profileKey, avatar);
Log.d("Loki", "Profile photo uploaded, the url is " + result.getUrl()); Log.d("Loki", "Profile photo uploaded, the url is " + result.getUrl());
TextSecurePreferences.setProfileAvatarUrl(context, result.getUrl()); TextSecurePreferences.setProfileAvatarUrl(context, result.getUrl());
@ -422,7 +422,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
ProfileKeyUtil.setEncodedProfileKey(context, newProfileKey); ProfileKeyUtil.setEncodedProfileKey(context, newProfileKey);
// Update profile key on the public chat server // Update profile key on the public chat server
ApplicationContext.getInstance(context).updatePublicChatProfileAvatarIfNeeded(); ApplicationContext.getInstance(context).updatePublicChatProfilePictureIfNeeded();
} catch (Exception e) { } catch (Exception e) {
Log.d("Loki", "Failed to upload profile photo: " + e); Log.d("Loki", "Failed to upload profile photo: " + e);
return false; return false;

View File

@ -190,7 +190,7 @@ public class DeviceListFragment extends ListFragment
private void updateAddDeviceButtonVisibility() { private void updateAddDeviceButtonVisibility() {
if (addDeviceButton != null) { if (addDeviceButton != null) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext()); 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); addDeviceButton.setVisibility(isDeviceLinkingEnabled ? View.VISIBLE : View.INVISIBLE);
} }
} }

View File

@ -9,14 +9,13 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobs.TypingSendJob; import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Util; 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.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import kotlin.Unit; import kotlin.Unit;
@ -83,7 +82,7 @@ public class TypingStatusSender {
} }
private void sendTyping(long threadId, boolean typingStarted) { private void sendTyping(long threadId, boolean typingStarted) {
LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); LokiFileServerAPI storageAPI = LokiFileServerAPI.Companion.getShared();
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Recipient recipient = threadDatabase.getRecipientForThreadId(threadId); Recipient recipient = threadDatabase.getRecipientForThreadId(threadId);
@ -91,7 +90,7 @@ public class TypingStatusSender {
ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted)); ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted));
return; return;
} }
LokiStorageAPI.shared.getAllDevicePublicKeys(recipient.getAddress().serialize()).success(devices -> { LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(recipient.getAddress().serialize()).success(devices -> {
for (String device : devices) { for (String device : devices) {
Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false); Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false);
long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient); long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient);

View File

@ -232,10 +232,10 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.thoughtcrime.securesms.util.views.Stub; import org.thoughtcrime.securesms.util.views.Stub;
import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional; 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.LokiAPI;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; 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.LokiMessageFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.Mention; import org.whispersystems.signalservice.loki.messaging.Mention;
@ -331,7 +331,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private MenuItem searchViewItem; private MenuItem searchViewItem;
private ProgressBar messageStatusProgressBar; private ProgressBar messageStatusProgressBar;
private ImageView muteIndicatorImageView; private ImageView muteIndicatorImageView;
private TextView actionBarSubtitleTextView; private TextView subtitleTextView;
private AttachmentTypeSelector attachmentTypeSelector; private AttachmentTypeSelector attachmentTypeSelector;
private AttachmentManager attachmentManager; private AttachmentManager attachmentManager;
@ -362,6 +362,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private final DynamicNoActionBarTheme dynamicTheme = new DynamicNoActionBarTheme(); private final DynamicNoActionBarTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
// Message Status Bar
private ArrayList<BroadcastReceiver> broadcastReceivers = new ArrayList<>(); private ArrayList<BroadcastReceiver> broadcastReceivers = new ArrayList<>();
private String messageStatus = null; private String messageStatus = null;
@ -404,6 +405,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
registerMessageStatusObserver("sendingMessage"); registerMessageStatusObserver("sendingMessage");
registerMessageStatusObserver("messageSent"); registerMessageStatusObserver("messageSent");
registerMessageStatusObserver("messageFailed"); 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(); initializeReceivers();
initializeActionBar(); initializeActionBar();
@ -543,7 +553,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
initializeIdentityRecords(); initializeIdentityRecords();
composeText.setTransport(sendButton.getSelectedTransport()); composeText.setTransport(sendButton.getSelectedTransport());
updateTitleTextView(glideRequests, recipient); updateTitleTextView(recipient);
updateSubtitleTextView(); updateSubtitleTextView();
setActionBarColor(recipient.getColor()); setActionBarColor(recipient.getColor());
setBlockedUserState(recipient, isSecureText, isDefaultSms); setBlockedUserState(recipient, isSecureText, isDefaultSms);
@ -636,7 +646,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
case GROUP_EDIT: case GROUP_EDIT:
recipient = Recipient.from(this, data.getParcelableExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA), true); recipient = Recipient.from(this, data.getParcelableExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA), true);
recipient.addListener(this); recipient.addListener(this);
updateTitleTextView(glideRequests, recipient); updateTitleTextView(recipient);
updateSubtitleTextView(); updateSubtitleTextView();
NotificationChannels.updateContactChannelName(this, recipient); NotificationChannels.updateContactChannelName(this, recipient);
setBlockedUserState(recipient, isSecureText, isDefaultSms); setBlockedUserState(recipient, isSecureText, isDefaultSms);
@ -740,9 +750,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
MenuInflater inflater = this.getMenuInflater(); MenuInflater inflater = this.getMenuInflater();
menu.clear(); 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) { if (recipient.getExpireMessages() > 0) {
inflater.inflate(R.menu.conversation_expiring_on, menu); 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); if (isSecureText) inflater.inflate(R.menu.conversation_callable_secure, menu);
else inflater.inflate(R.menu.conversation_callable_insecure, 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); inflater.inflate(R.menu.conversation_group_options, menu);
if (!isPushGroupConversation()) { if (!isPushGroupConversation()) {
@ -1679,7 +1689,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sessionRestoreBannerView = ViewUtil.findById(this, R.id.sessionRestoreBannerView); sessionRestoreBannerView = ViewUtil.findById(this, R.id.sessionRestoreBannerView);
messageStatusProgressBar = ViewUtil.findById(this, R.id.messageStatusProgressBar); messageStatusProgressBar = ViewUtil.findById(this, R.id.messageStatusProgressBar);
muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView); muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView);
actionBarSubtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView); subtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView);
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle); ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button); ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
@ -1870,7 +1880,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Log.i(TAG, "onModified(" + recipient.getAddress().serialize() + ")"); Log.i(TAG, "onModified(" + recipient.getAddress().serialize() + ")");
Util.runOnMain(() -> { Util.runOnMain(() -> {
Log.i(TAG, "onModifiedRun(): " + recipient.getRegistered()); Log.i(TAG, "onModifiedRun(): " + recipient.getRegistered());
updateTitleTextView(glideRequests, recipient); updateTitleTextView(recipient);
updateSubtitleTextView(); updateSubtitleTextView();
// titleView.setVerified(identityRecords.isVerified()); // titleView.setVerified(identityRecords.isVerified());
setBlockedUserState(recipient, isSecureText, isDefaultSms); setBlockedUserState(recipient, isSecureText, isDefaultSms);
@ -2290,7 +2300,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (threadID != this.threadId) { if (threadID != this.threadId) {
Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID); Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID);
if (threadRecipient != null && !threadRecipient.isGroupRecipient()) { 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 // We should update our input if this thread is a part of the other threads device
if (devices.contains(recipient.getAddress().serialize())) { if (devices.contains(recipient.getAddress().serialize())) {
this.updateInputPanel(); this.updateInputPanel();
@ -2313,33 +2323,29 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void updateInputPanel() { 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 fixes the case where the input panel disables and enables rapidly, which can occur when we are
- 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. 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) { if (recipient.isGroupRecipient() || isNoteToSelf() || isFriendsWithAnyDevice) { setInputPanelEnabled(true); return; }
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); LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId);
boolean isPending = friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED; boolean isPending = friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED;
setInputPanelEnabled(!isPending); setInputPanelEnabled(!isPending);
// We should always have the input panel enabled if we are friends with the current user // Always enable the input panel if we are friends with the current user
isFriendsWithAnyDevice = friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS; isFriendsWithAnyDevice = (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS);
// Multi-device input logic
if (!isFriendsWithAnyDevice) { if (!isFriendsWithAnyDevice) {
// We should enable the input if we don't have any pending friend requests OR we are friends with a linked device // 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 -> { MultiDeviceUtilities.hasPendingFriendRequestWithAnyLinkedDevice(this, recipient).success( hasPendingRequests -> {
if (!hasPendingRequests) { if (!hasPendingRequests) {
setInputPanelEnabled(true); setInputPanelEnabled(true);
} else { } else {
MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient).success( isFriends -> { 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 // Enable the input panel if we're friends with any of the user's devices
isFriendsWithAnyDevice = isFriends; isFriendsWithAnyDevice = isFriends;
setInputPanelEnabled(isFriends); setInputPanelEnabled(isFriends);
return Unit.INSTANCE; return Unit.INSTANCE;
@ -2353,7 +2359,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void setInputPanelEnabled(boolean enabled) { private void setInputPanelEnabled(boolean enabled) {
Util.runOnMain(() -> { Util.runOnMain(() -> {
updateToggleButtonState(); updateToggleButtonState();
String hint = enabled ? "Message" : "Pending message request"; String hint = enabled ? "Message" : "Pending session request";
inputPanel.setHint(hint); inputPanel.setHint(hint);
inputPanel.setEnabled(enabled); inputPanel.setEnabled(enabled);
if (enabled) { if (enabled) {
@ -2407,13 +2413,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendTextMessage(forceSms, expiresIn, subscriptionId, initiating); sendTextMessage(forceSms, expiresIn, subscriptionId, initiating);
} }
} catch (RecipientFormattingException ex) { } 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); Log.w(TAG, ex);
} catch (InvalidMessageException ex) { } catch (InvalidMessageException ex) {
// Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_message_is_empty_exclamation,
// Toast.LENGTH_SHORT).show();
Log.w(TAG, ex); Log.w(TAG, ex);
} }
} }
@ -2565,7 +2566,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private void updateToggleButtonState() { 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) { if (!isNoteToSelf() && !recipient.isGroupRecipient() && !isFriendsWithAnyDevice) {
buttonToggle.display(sendButton); buttonToggle.display(sendButton);
quickAttachmentToggle.hide(); quickAttachmentToggle.hide();
@ -3160,13 +3161,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
// region Loki // region Loki
private void updateTitleTextView(GlideRequests glide, Recipient recipient) { private void updateTitleTextView(Recipient recipient) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
List<PairingAuthorisation> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getPairingAuthorisations(userHexEncodedPublicKey); Set<DeviceLink> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userHexEncodedPublicKey);
HashSet<String> userLinkedDeviceHexEncodedPublicKeys = new HashSet<>(); HashSet<String> userLinkedDeviceHexEncodedPublicKeys = new HashSet<>();
for (PairingAuthorisation deviceLink : deviceLinks) { for (DeviceLink deviceLink : deviceLinks) {
userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getPrimaryDevicePublicKey().toLowerCase()); userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getMasterHexEncodedPublicKey().toLowerCase());
userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getSecondaryDevicePublicKey().toLowerCase()); userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getSlaveHexEncodedPublicKey().toLowerCase());
} }
userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey.toLowerCase()); userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey.toLowerCase());
if (recipient == null) { if (recipient == null) {
@ -3174,45 +3175,46 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} else if (userLinkedDeviceHexEncodedPublicKeys.contains(recipient.getAddress().toString().toLowerCase())) { } else if (userLinkedDeviceHexEncodedPublicKeys.contains(recipient.getAddress().toString().toLowerCase())) {
titleTextView.setText("Note to Self"); titleTextView.setText("Note to Self");
} else { } 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() { private void updateSubtitleTextView() {
muteIndicatorImageView.setVisibility(View.GONE); muteIndicatorImageView.setVisibility(View.GONE);
actionBarSubtitleTextView.setVisibility(View.VISIBLE); subtitleTextView.setVisibility(View.VISIBLE);
if (messageStatus != null) { if (messageStatus != null) {
switch (messageStatus) { switch (messageStatus) {
case "calculatingPoW": actionBarSubtitleTextView.setText("Encrypting message"); break; case "calculatingPoW": subtitleTextView.setText("Encrypting message"); break;
case "contactingNetwork": actionBarSubtitleTextView.setText("Tracing a path"); break; case "contactingNetwork": subtitleTextView.setText("Tracing a path"); break;
case "sendingMessage": actionBarSubtitleTextView.setText("Sending message"); break; case "sendingMessage": subtitleTextView.setText("Sending message"); break;
case "messageSent": actionBarSubtitleTextView.setText("Message sent securely"); break; case "messageSent": subtitleTextView.setText("Message sent securely"); break;
case "messageFailed": actionBarSubtitleTextView.setText("Message failed to send"); break; case "messageFailed": subtitleTextView.setText("Message failed to send"); break;
} }
} else if (recipient.isMuted()) { } else if (recipient.isMuted()) {
muteIndicatorImageView.setVisibility(View.VISIBLE); 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")) { } else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) {
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null) { if (publicChat != null) {
Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer()); Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer());
if (userCount == null) { userCount = 0; } if (userCount == null) { userCount = 0; }
if (userCount >= 200) { if (userCount >= 200) {
actionBarSubtitleTextView.setText("200+ members"); subtitleTextView.setText("200+ members");
} else { } else {
actionBarSubtitleTextView.setText(userCount + " members"); subtitleTextView.setText(userCount + " members");
} }
} else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) { } else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) {
actionBarSubtitleTextView.setText(recipient.getAddress().toString()); subtitleTextView.setText(recipient.getAddress().toString());
} else { } else {
actionBarSubtitleTextView.setVisibility(View.GONE); subtitleTextView.setVisibility(View.GONE);
} }
} else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) { } else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) {
actionBarSubtitleTextView.setText(recipient.getAddress().toString()); subtitleTextView.setText(recipient.getAddress().toString());
} else { } 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) { private void setMessageStatusProgressAnimatedIfPossible(int progress) {
@ -3284,18 +3286,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void acceptFriendRequest(@NotNull MessageRecord friendRequest) { 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); LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(this);
long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id); long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id);
long threadId = originalThreadID < 0 ? this.threadId : originalThreadID; long threadID = originalThreadID < 0 ? this.threadId : originalThreadID;
Recipient contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID);
Recipient contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId);
Address address = contact.getAddress(); Address address = contact.getAddress();
String contactPubKey = address.serialize(); String contactHexEncodedPublicKey = address.serialize();
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS); DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
DatabaseFactory.getRecipientDatabase(this).setProfileSharing(contact, true); DatabaseFactory.getRecipientDatabase(this).setProfileSharing(contact, true);
MessageSender.sendBackgroundMessageToAllDevices(this, contactPubKey); MessageSender.sendBackgroundMessageToAllDevices(this, contactHexEncodedPublicKey);
MessageSender.syncContact(this, address); MessageSender.syncContact(this, address);
updateInputPanel(); updateInputPanel();
} }
@ -3304,10 +3305,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void rejectFriendRequest(@NotNull MessageRecord friendRequest) { public void rejectFriendRequest(@NotNull MessageRecord friendRequest) {
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(this); LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(this);
long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id); long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id);
long threadId = originalThreadID < 0 ? this.threadId : originalThreadID; long threadID = originalThreadID < 0 ? this.threadId : originalThreadID;
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.NONE);
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.NONE); String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID).getAddress().toString();
String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString();
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(contactID); DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(contactID);
updateInputPanel(); updateInputPanel();
} }
@ -3315,20 +3315,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public boolean isNoteToSelf() { public boolean isNoteToSelf() {
return TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize()); return TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize());
} }
// endregion
public void restoreSession() { public void restoreSession() {
// Loki - User clicked restore session
if (recipient.isGroupRecipient()) { return; } if (recipient.isGroupRecipient()) { return; }
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(this); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(this);
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(this); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(this);
Set<String> devices = lokiThreadDatabase.getSessionRestoreDevices(threadId); Set<String> devices = lokiThreadDatabase.getSessionRestoreDevices(threadId);
for (String device : devices) { MessageSender.sendRestoreSessionMessage(this, device); } for (String device : devices) { MessageSender.sendRestoreSessionMessage(this, device); }
long messageId = smsDatabase.insertMessageOutbox(threadId, new OutgoingTextMessage(recipient,"", 0, 0), false, System.currentTimeMillis(), null); long messageID = smsDatabase.insertMessageOutbox(threadId, new OutgoingTextMessage(recipient,"", 0, 0), false, System.currentTimeMillis(), null);
if (messageId > -1) { if (messageID > -1) {
smsDatabase.markAsLokiSessionRestoreSent(messageId); smsDatabase.markAsLokiSessionRestoreSent(messageID);
} }
lokiThreadDatabase.removeAllSessionRestoreDevices(threadId); lokiThreadDatabase.removeAllSessionRestoreDevices(threadId);
updateSessionRestoreBanner(); updateSessionRestoreBanner();
} }
// endregion
} }

View File

@ -10,11 +10,11 @@ import org.thoughtcrime.securesms.logging.Log;
import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.protocol.CiphertextMessage; import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.signalservice.loki.messaging.LokiSessionDatabaseProtocol; import org.whispersystems.libsignal.state.SessionStore;
import java.util.List; import java.util.List;
public class TextSecureSessionStore implements LokiSessionDatabaseProtocol { public class TextSecureSessionStore implements SessionStore {
private static final String TAG = TextSecureSessionStore.class.getSimpleName(); private static final String TAG = TextSecureSessionStore.class.getSimpleName();

View File

@ -135,7 +135,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.getCreateGroupChatAuthTokenTableCommand()); db.execSQL(LokiAPIDatabase.getCreateGroupChatAuthTokenTableCommand());
db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand()); db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand());
db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand()); db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand());
db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand()); db.execSQL(LokiAPIDatabase.getCreateDeviceLinkTableCommand());
db.execSQL(LokiAPIDatabase.getCreateUserCountTableCommand()); db.execSQL(LokiAPIDatabase.getCreateUserCountTableCommand());
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
@ -518,7 +518,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
} }
if (oldVersion < lokiV3) { if (oldVersion < lokiV3) {
db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand()); db.execSQL(LokiAPIDatabase.getCreateDeviceLinkTableCommand());
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand()); db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
db.execSQL("ALTER TABLE groups ADD COLUMN avatar_url TEXT"); db.execSQL("ALTER TABLE groups ADD COLUMN avatar_url TEXT");

View File

@ -11,13 +11,14 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities; import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities;
import org.thoughtcrime.securesms.util.AsyncLoader; import org.thoughtcrime.securesms.util.AsyncLoader;
import org.thoughtcrime.securesms.util.TextSecurePreferences; 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 org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
import java.io.File; import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set;
public class DeviceListLoader extends AsyncLoader<List<Device>> { public class DeviceListLoader extends AsyncLoader<List<Device>> {
@ -33,7 +34,7 @@ public class DeviceListLoader extends AsyncLoader<List<Device>> {
public List<Device> loadInBackground() { public List<Device> loadInBackground() {
try { try {
String ourPublicKey = TextSecurePreferences.getLocalNumber(getContext()); String ourPublicKey = TextSecurePreferences.getLocalNumber(getContext());
List<String> secondaryDevicePublicKeys = LokiStorageAPI.shared.getSecondaryDevicePublicKeys(ourPublicKey).get(); Set<String> secondaryDevicePublicKeys = LokiDeviceLinkUtilities.INSTANCE.getSlaveHexEncodedPublicKeys(ourPublicKey).get();
List<Device> devices = Stream.of(secondaryDevicePublicKeys).map(this::mapToDevice).toList(); List<Device> devices = Stream.of(secondaryDevicePublicKeys).map(this::mapToDevice).toList();
Collections.sort(devices, new DeviceComparator()); Collections.sort(devices, new DeviceComparator());
return devices; return devices;

View File

@ -7,7 +7,6 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.CreateProfileActivity; import org.thoughtcrime.securesms.CreateProfileActivity;
import org.thoughtcrime.securesms.DeviceListFragment; import org.thoughtcrime.securesms.DeviceListFragment;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent; import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.gcm.FcmService; 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.jobs.TypingSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation;
import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.MessageSenderEventListener; import org.thoughtcrime.securesms.push.MessageSenderEventListener;
@ -160,7 +160,7 @@ public class SignalCommunicationModule {
DatabaseFactory.getLokiThreadDatabase(context), DatabaseFactory.getLokiThreadDatabase(context),
DatabaseFactory.getLokiMessageDatabase(context), DatabaseFactory.getLokiMessageDatabase(context),
DatabaseFactory.getLokiPreKeyBundleDatabase(context), DatabaseFactory.getLokiPreKeyBundleDatabase(context),
new TextSecureSessionStore(context), new LokiSessionResetImplementation(context),
DatabaseFactory.getLokiUserDatabase(context), DatabaseFactory.getLokiUserDatabase(context),
((ApplicationContext)context.getApplicationContext()).broadcaster); ((ApplicationContext)context.getApplicationContext()).broadcaster);
} else { } else {

View File

@ -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;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type; import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; 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 org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.util.Collections; import java.util.Collections;
@ -318,7 +318,7 @@ public class GroupMessageProcessor {
try { try {
String masterHexEncodedPublicKey = hexEncodedPublicKey.equalsIgnoreCase(ourPublicKey) String masterHexEncodedPublicKey = hexEncodedPublicKey.equalsIgnoreCase(ourPublicKey)
? TextSecurePreferences.getMasterHexEncodedPublicKey(context) ? TextSecurePreferences.getMasterHexEncodedPublicKey(context)
: PromiseUtil.timeout(LokiStorageAPI.shared.getPrimaryDevicePublicKey(hexEncodedPublicKey), 5000).get(); : PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey), 5000).get();
return masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : hexEncodedPublicKey; return masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : hexEncodedPublicKey;
} catch (Exception e) { } catch (Exception e) {
return hexEncodedPublicKey; return hexEncodedPublicKey;
@ -329,7 +329,7 @@ public class GroupMessageProcessor {
String ourNumber = TextSecurePreferences.getLocalNumber(context); String ourNumber = TextSecurePreferences.getLocalNumber(context);
for (String member : members) { for (String member : members) {
// Make sure we have session with all of the members secondary devices // 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; } if (devices.contains(ourNumber)) { return Unit.INSTANCE; }
for (String device : devices) { for (String device : devices) {
SignalProtocolAddress protocolAddress = new SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID); SignalProtocolAddress protocolAddress = new SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID);

View File

@ -27,7 +27,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; 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.IOException;
import java.io.InputStream; import java.io.InputStream;

View File

@ -68,13 +68,13 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.FriendRequestHandler; 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.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyBundleDatabase; import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation;
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyRecordDatabase;
import org.thoughtcrime.securesms.loki.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity; 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.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
@ -101,6 +101,8 @@ import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; 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.PreKeyBundle;
import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.util.guava.Optional; 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.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; 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.DeviceLinkingSession;
import org.whispersystems.signalservice.loki.api.LokiAPI; import org.whispersystems.signalservice.loki.api.LokiAPI;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher; import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage; import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.InputStream; import java.io.InputStream;
@ -270,9 +271,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context); SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
LokiPreKeyRecordDatabase lokiPreKeyRecordDatabase = DatabaseFactory.getLokiPreKeyRecordDatabase(context); LokiSessionResetProtocol lokiSessionResetProtocol = new LokiSessionResetImplementation(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(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); SignalServiceContent content = cipher.decrypt(envelope);
@ -282,21 +283,21 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return; return;
} }
if (shouldIgnore(content)) { if (shouldIgnore(content)) {
Log.i(TAG, "Ignoring message."); Log.i(TAG, "Ignoring message.");
return; return;
} }
// Loki - Handle friend request acceptance if needed // 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); handleSessionRequestIfNeeded(content);
// Loki - Store pre key bundle // Loki - Store pre key bundle if needed
// We shouldn't store it if it's a pairing message if (!content.getDeviceLink().isPresent()) {
if (!content.getPairingAuthorisation().isPresent()) {
storePreKeyBundleIfNeeded(content); storePreKeyBundleIfNeeded(content);
} }
@ -310,35 +311,35 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
// Loki - Store the sender display name if needed // Loki - Store the sender display name if needed
Optional<String> rawSenderDisplayName = content.senderDisplayName; Optional<String> rawSenderDisplayName = content.senderDisplayName;
if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) { 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 // If we got a name from our master device then set our display name to match
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourPrimaryDevice != null && content.getSender().equals(ourPrimaryDevice)) { if (ourMasterDevice != null && content.getSender().equals(ourMasterDevice)) {
TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get()); 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) // 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 -> { MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success( isOneOfOurDevices -> {
if (!isOneOfOurDevice) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); } if (!isOneOfOurDevices) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); }
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
} }
if (content.getPairingAuthorisation().isPresent()) { if (content.getDeviceLink().isPresent()) {
handlePairingMessage(content.getPairingAuthorisation().get(), content); handleDeviceLinkMessage(content.getDeviceLink().get(), content);
} else if (content.getDataMessage().isPresent()) { } else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get(); SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
if (!content.isFriendRequest() && message.isUnpairingRequest()) { if (!content.isFriendRequest() && message.isUnlinkingRequest()) {
// Make sure we got the request from our primary device // Make sure we got the request from our master device
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) { if (ourMasterDevice != null && ourMasterDevice.equals(content.getSender())) {
TextSecurePreferences.setDatabaseResetFromUnpair(context, true); TextSecurePreferences.setDatabaseResetFromUnpair(context, true);
MultiDeviceUtilities.checkForRevocation(context); MultiDeviceUtilities.checkIsRevokedSlaveDevice(context);
} }
} else { } else {
// Loki - Don't process session restore message any further // 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); if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId); else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
@ -362,6 +363,18 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
handleNeedsDeliveryReceipt(content, message); 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 // Loki - Handle friend request logic if needed
updateFriendRequestStatusIfNeeded(content, message); 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.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp());
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().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 Log.w(TAG, "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) { } else if (content.getCallMessage().isPresent()) {
Log.i(TAG, "Got call message..."); Log.i(TAG, "Got call message...");
@ -402,11 +415,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (envelope.isPreKeySignalMessage()) { if (envelope.isPreKeySignalMessage()) {
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob()); ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob());
} }
// Loki - Handle session reset logic
if (!content.isFriendRequest()) {
cipher.handleSessionResetRequestIfNeeded(content, cipher.getSessionStatus(content));
}
} catch (ProtocolInvalidVersionException e) { } catch (ProtocolInvalidVersionException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
@ -539,19 +547,19 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
if (threadId != null) { if (threadId != null) {
resetSession(content.getSender(), threadId); resetSession(content.getSender());
MessageNotifier.updateNotification(context, threadId); MessageNotifier.updateNotification(context, threadId);
} }
} }
private void resetSession(String hexEncodedPublicKey, long threadId) { private void resetSession(String hexEncodedPublicKey) {
TextSecureSessionStore sessionStore = new TextSecureSessionStore(context); TextSecureSessionStore sessionStore = new TextSecureSessionStore(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
Log.d("Loki", "Received a session reset request from: " + hexEncodedPublicKey + "; archiving the session."); Log.d("Loki", "Received a session reset request from: " + hexEncodedPublicKey + "; archiving the session.");
sessionStore.archiveAllSessions(hexEncodedPublicKey); sessionStore.archiveAllSessions(hexEncodedPublicKey);
lokiThreadDatabase.setSessionResetStatus(threadId, LokiThreadSessionResetStatus.REQUEST_RECEIVED); lokiThreadDatabase.setSessionResetStatus(hexEncodedPublicKey, LokiSessionResetStatus.REQUEST_RECEIVED);
Log.d("Loki", "Sending a ping back to " + hexEncodedPublicKey + "."); Log.d("Loki", "Sending a ping back to " + hexEncodedPublicKey + ".");
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey); MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
@ -593,7 +601,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{ {
GroupMessageProcessor.process(context, content, message, false); 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()); handleExpirationUpdate(content, message, Optional.absent());
} }
@ -619,7 +627,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{ {
try { try {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getMessageDestination(content, message); Recipient recipient = getRecipientForMessage(content, message);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromSerialized(content.getSender()), IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromSerialized(content.getSender()),
message.getTimestamp(), -1, message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, true, message.getExpiresInSeconds() * 1000L, true,
@ -670,35 +678,35 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
private void handleSynchronizeContactMessage(@NonNull ContactsMessage contactsMessage) { private void handleContactSyncMessage(@NonNull ContactsMessage contactsMessage) {
if (contactsMessage.getContactsStream().isStream()) { if (!contactsMessage.getContactsStream().isStream()) { return; }
Log.d("Loki", "Received contact sync message"); Log.d("Loki", "Received contact sync message.");
try { try {
InputStream in = contactsMessage.getContactsStream().asStream().getInputStream(); InputStream in = contactsMessage.getContactsStream().asStream().getInputStream();
DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in); DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in);
List<DeviceContact> devices = contactsInputStream.readAll(); List<DeviceContact> deviceContacts = contactsInputStream.readAll();
for (DeviceContact deviceContact : devices) { for (DeviceContact deviceContact : deviceContacts) {
// Check if we have the contact as a friend and that we're not trying to sync our own device // Check if we have the contact as a friend and that we're not trying to sync our own device
String pubKey = deviceContact.getNumber(); String hexEncodedPublicKey = deviceContact.getNumber();
Address address = Address.fromSerialized(pubKey); Address address = Address.fromSerialized(hexEncodedPublicKey);
if (!address.isPhone() || address.toPhoneString().equals(TextSecurePreferences.getLocalNumber(context))) { continue; } 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 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 Otherwise, if we have received a friend request from them, automatically accept the friend request.
*/ */
Recipient recipient = Recipient.from(context, address, false); Recipient recipient = Recipient.from(context, address, false);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) { if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
MessageSender.sendBackgroundFriendRequest(context, pubKey, "Please accept to enable messages to be synced across devices"); MessageSender.sendBackgroundFriendRequest(context, hexEncodedPublicKey, "Please accept to enable messages to be synced across devices");
Log.d("Loki", "Sent friend request to " + pubKey); Log.d("Loki", "Sent friend request to " + hexEncodedPublicKey);
} else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { } else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
// Accept the incoming friend request // Accept the incoming friend request
becomeFriendsWithContact(pubKey, false, false); becomeFriendsWithContactIfNeeded(hexEncodedPublicKey, false, false);
// Send them an accept message back // Send them an accept message back
MessageSender.sendBackgroundMessage(context, pubKey); MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
Log.d("Loki", "Became friends with " + deviceContact.getNumber()); Log.d("Loki", "Became friends with " + deviceContact.getNumber());
} }
@ -707,8 +715,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
// TODO: Handle avatar - Download and set avatar? // TODO: Handle avatar - Download and set avatar?
} }
} catch (Exception e) { } catch (Exception e) {
Log.d("Loki", "Failed to sync contact: " + 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); 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) { if (isSenderMasterDevice) {
handleProfileKey(content, message.getMessage()); handleProfileKey(content, message.getMessage());
} }
@ -841,21 +848,21 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
@NonNull Optional<Long> messageServerIDOrNull) @NonNull Optional<Long> messageServerIDOrNull)
throws StorageFailedException throws StorageFailedException
{ {
Recipient originalRecipient = getMessageDestination(content, message); Recipient originalRecipient = getRecipientForMessage(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); Recipient masterRecipient = getMasterRecipientForMessage(content, message);
notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice()); notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice());
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote()); Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts()); Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts());
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or("")); Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
Optional<Attachment> sticker = getStickerAttachment(message.getSticker()); Optional<Attachment> 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 is from group then we need to map it to get the sender of the message
if (message.isGroupMessage()) { if (message.isGroupMessage()) {
sender = getPrimaryDeviceRecipient(content.getSender()).getAddress(); sender = getMasterRecipient(content.getSender()).getAddress();
} }
// Ignore messages from ourselves // Ignore messages from ourselves
@ -905,12 +912,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); 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(() -> { AsyncTask.execute(() -> {
// Loki - Store message server ID // Loki - Store message server ID
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult); updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
// Loki - Update mapping of message to original thread id // Loki - Update mapping of message to original thread ID
if (insertResult.isPresent()) { if (insertResult.isPresent()) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
@ -1030,8 +1037,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{ {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context); SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
String body = message.getBody().isPresent() ? message.getBody().get() : ""; String body = message.getBody().isPresent() ? message.getBody().get() : "";
Recipient originalRecipient = getMessageDestination(content, message); Recipient originalRecipient = getRecipientForMessage(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); Recipient masterRecipient = getMasterRecipientForMessage(content, message);
if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) { if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent()); handleExpirationUpdate(content, message, Optional.absent());
@ -1042,26 +1049,26 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) { if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) {
threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second; threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second;
} else { } 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 is from group then we need to map it to get the sender of the message
if (message.isGroupMessage()) { if (message.isGroupMessage()) {
sender = getPrimaryDeviceRecipient(content.getSender()).getAddress(); sender = getMasterRecipient(content.getSender()).getAddress();
} }
// Ignore messages from ourselves // Ignore messages from ourselves
if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; } if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; }
IncomingTextMessage _textMessage = new IncomingTextMessage(sender, IncomingTextMessage tm = new IncomingTextMessage(sender,
content.getSenderDevice(), content.getSenderDevice(),
message.getTimestamp(), body, message.getTimestamp(), body,
message.getGroupInfo(), message.getGroupInfo(),
message.getExpiresInSeconds() * 1000L, message.getExpiresInSeconds() * 1000L,
content.isNeedsReceipt()); content.isNeedsReceipt());
IncomingEncryptedMessage textMessage = new IncomingEncryptedMessage(_textMessage, body); IncomingEncryptedMessage textMessage = new IncomingEncryptedMessage(tm, body);
// Ignore the message if the body is empty // Ignore the message if the body is empty
if (textMessage.getMessageBody().length() == 0) { return; } if (textMessage.getMessageBody().length() == 0) { return; }
@ -1079,7 +1086,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
MessageNotifier.updateNotification(context, threadId); 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(() -> { AsyncTask.execute(() -> {
if (insertResult.isPresent()) { if (insertResult.isPresent()) {
InsertResult result = insertResult.get(); InsertResult result = insertResult.get();
@ -1090,7 +1097,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
// Loki - Store message server ID // Loki - Store message server ID
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult); 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) { if (result.getMessageId() > -1) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(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; boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
boolean isRequest = (authorisation.getType() == PairingAuthorisation.Type.REQUEST); boolean isRequest = (authorisation.getType() == DeviceLink.Type.REQUEST);
if (authorisation.getRequestSignature() == null) { if (authorisation.getRequestSignature() == null) {
Log.d("Loki", "Ignoring pairing request message without a request signature."); Log.d("Loki", "Ignoring pairing request message without a request signature.");
return false; return false;
} else if (isRequest && isSecondaryDevice) { } else if (isRequest && isSecondaryDevice) {
Log.d("Loki", "Ignoring unexpected pairing request message (the device is already paired as a secondary device)."); Log.d("Loki", "Ignoring unexpected pairing request message (the device is already paired as a secondary device).");
return false; 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."); Log.d("Loki", "Ignoring pairing request message addressed to another user.");
return false; 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."); Log.d("Loki", "Ignoring pairing request message from self.");
return false; return false;
} }
return authorisation.verify(); return authorisation.verify();
} }
private void handleProfileAvatar(SignalServiceContent content, String url) { private void handleDeviceLinkMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(primaryDevice, url));
}
private void handlePairingMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceContent content) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
if (authorisation.getType() == PairingAuthorisation.Type.REQUEST) { if (deviceLink.getType() == DeviceLink.Type.REQUEST) {
handlePairingRequestMessage(authorisation, content); handleDeviceLinkRequestMessage(deviceLink, content);
} else if (authorisation.getSecondaryDevicePublicKey().equals(userHexEncodedPublicKey)) { } else if (deviceLink.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
handlePairingAuthorisationMessage(authorisation, content); handleDeviceLinkAuthorizedMessage(deviceLink, content);
} }
} }
private void handlePairingRequestMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceContent content) { private void handleDeviceLinkRequestMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
boolean isValid = isValidPairingMessage(authorisation); boolean isValid = isValidDeviceLinkMessage(deviceLink);
DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared(); DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared();
if (isValid && linkingSession.isListeningForLinkingRequests()) { if (!isValid || !linkingSession.isListeningForLinkingRequests()) { return; }
// Loki - If we successfully received a request then we should store the PreKeyBundle
storePreKeyBundleIfNeeded(content); storePreKeyBundleIfNeeded(content);
linkingSession.processLinkingRequest(authorisation); linkingSession.processLinkingRequest(deviceLink);
}
} }
private void handlePairingAuthorisationMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceContent content) { private void handleDeviceLinkAuthorizedMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
// Prepare // Check preconditions
boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null; boolean hasExistingDeviceLink = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
if (isSecondaryDevice) { if (hasExistingDeviceLink) {
Log.d("Loki", "Ignoring unexpected pairing authorisation message (the device is already paired as a secondary device)."); Log.d("Loki", "Ignoring unexpected device link message (the device is already linked as a slave device).");
return; return;
} }
boolean isValid = isValidPairingMessage(authorisation); boolean isValid = isValidDeviceLinkMessage(deviceLink);
if (!isValid) { if (!isValid) {
Log.d("Loki", "Ignoring invalid pairing authorisation message."); Log.d("Loki", "Ignoring invalid device link message.");
return; return;
} }
if (!DeviceLinkingSession.Companion.getShared().isListeningForLinkingRequests()) { if (!DeviceLinkingSession.Companion.getShared().isListeningForLinkingRequests()) {
Log.d("Loki", "Ignoring pairing authorisation message."); Log.d("Loki", "Ignoring device link message.");
return; return;
} }
if (authorisation.getType() != PairingAuthorisation.Type.GRANT) { return; } if (deviceLink.getType() != DeviceLink.Type.AUTHORIZATION) { return; }
Log.d("Loki", "Received pairing authorisation message from: " + authorisation.getPrimaryDevicePublicKey() + "."); Log.d("Loki", "Received device link authorized message from: " + deviceLink.getMasterHexEncodedPublicKey() + ".");
// Save PreKeyBundle if for whatever reason we got one // Save pre key bundle if we somehow got one
storePreKeyBundleIfNeeded(content); storePreKeyBundleIfNeeded(content);
// Process // Process
DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(authorisation); DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(deviceLink);
// Store the primary device's public key // Store the master device's ID
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(userHexEncodedPublicKey); DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userHexEncodedPublicKey);
DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(authorisation); DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink);
TextSecurePreferences.setMasterHexEncodedPublicKey(context, authorisation.getPrimaryDevicePublicKey()); TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.getMasterHexEncodedPublicKey());
TextSecurePreferences.setMultiDevice(context, true); TextSecurePreferences.setMultiDevice(context, true);
// Send a background message to the primary device // Send a background message to the master device
MessageSender.sendBackgroundMessage(context, authorisation.getPrimaryDevicePublicKey()); MessageSender.sendBackgroundMessage(context, deviceLink.getMasterHexEncodedPublicKey());
// Propagate the updates to the file server // Update display name if needed
LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared();
storageAPI.updateUserDeviceMappings();
// Update display names
if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) { if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get()); TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
} }
// Profile avatar updates // Update profile picture if needed
if (content.getDataMessage().isPresent()) { if (content.getDataMessage().isPresent()) {
handleProfileKey(content, content.getDataMessage().get()); handleProfileKey(content, content.getDataMessage().get());
} }
// Contact sync // Handle contact sync if needed
if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) { 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) { private void setDisplayName(String hexEncodedPublicKey, String profileName) {
@ -1199,95 +1197,69 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
private void updateGroupChatMessageServerID(Optional<Long> messageServerIDOrNull, Optional<InsertResult> insertResult) { private void updateGroupChatMessageServerID(Optional<Long> messageServerIDOrNull, Optional<InsertResult> insertResult) {
if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) { if (!insertResult.isPresent() || !messageServerIDOrNull.isPresent()) { return; }
long messageID = insertResult.get().getMessageId(); long messageID = insertResult.get().getMessageId();
long messageServerID = messageServerIDOrNull.get(); long messageServerID = messageServerIDOrNull.get();
DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID); DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID);
} }
}
private void storePreKeyBundleIfNeeded(@NonNull SignalServiceContent content) { private void storePreKeyBundleIfNeeded(@NonNull SignalServiceContent content) {
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false); Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
if (!sender.isGroupRecipient() && content.lokiServiceMessage.isPresent()) { if (sender.isGroupRecipient() || !content.lokiServiceMessage.isPresent()) { return; }
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get(); LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
if (lokiMessage.getPreKeyBundleMessage() != null) { if (lokiMessage.getPreKeyBundleMessage() == null) { return; }
int registrationID = TextSecurePreferences.getLocalRegistrationId(context); int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context); LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); if (registrationID <= 0) { return; }
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
// Loki - Store the latest pre key bundle
if (registrationID > 0) {
Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + "."); Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID); PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle); lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle);
// Loki - If we received a friend request, but we were already friends with this user, then reset the session
if (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) { private void handleSessionRequestIfNeeded(@NonNull SignalServiceContent content) {
if (content.isFriendRequest() && isSessionRequest(content)) { if (!content.isFriendRequest() || !isSessionRequest(content)) { return; }
// Check if the session request from a member in one of our groups or our friend // Check if the session request came from a member in one of our groups or one of our friends
LokiStorageAPI.shared.getPrimaryDevicePublicKey(content.getSender()).success(primaryDevicePublicKey -> { LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()).success( masterHexEncodedPublicKey -> {
String sender = primaryDevicePublicKey != null ? primaryDevicePublicKey : content.getSender(); String sender = masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : content.getSender();
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false)); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false));
LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS; boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender); boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender);
boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups; boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups;
if (shouldAcceptSessionRequest) { if (shouldAcceptSessionRequest) {
// Send a background message to acknowledge session request MessageSender.sendBackgroundMessage(context, content.getSender()); // Send a background message to acknowledge
MessageSender.sendBackgroundMessage(context, content.getSender());
} }
return Unit.INSTANCE; 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); 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; if (contactID.isGroupRecipient()) return;
// Ignore friend requests to recipients we're already friends with
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(contactID); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(contactID);
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID); LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) { return; } if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) { return; }
// We shouldn't be able to skip from NONE to FRIENDS under normal circumstances.
// We shouldn't be able to skip from None -> Friends in normal circumstances. // Multi-device is the one exception to this rule because we want to automatically become friends with slave devices.
// Multi-device is the exception to this rule because we want to automatically be friends with a secondary device if (!canSkip && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; }
if (!force && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; } // If the thread's friend request status is not `FRIENDS` or `NONE`, but we're receiving a message,
// If the thread's friend request status is not `FRIENDS`, but we're receiving a message,
// it must be a friend request accepted message. Declining a friend request doesn't send a message. // it must be a friend request accepted message. Declining a friend request doesn't send a message.
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
// Send out a contact sync message // Send out a contact sync message if needed
if (syncContact) { if (requiresContactSync) {
MessageSender.syncContact(context, contactID.getAddress()); MessageSender.syncContact(context, contactID.getAddress());
} }
// Allow profile sharing with contact // Enable profile sharing with the recipient
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(contactID, true); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(contactID, true);
// Update the last message if needed // Update the last message if needed
LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> { LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey).success( masterHexEncodedPublicKey -> {
Util.runOnMain(() -> { Util.runOnMain(() -> {
long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false)); long masterThreadID = (masterHexEncodedPublicKey == null) ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(masterHexEncodedPublicKey), false));
FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); FriendRequestHandler.updateLastFriendRequestMessage(context, masterThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
}); });
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
@ -1295,25 +1267,24 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
if (!content.isFriendRequest() || message.isGroupMessage() || message.isSessionRequest()) { return; } if (!content.isFriendRequest() || message.isGroupMessage() || message.isSessionRequest()) { return; }
// This handles the case where another user sends us a regular message without authorisation
Promise<Boolean, Exception> promise = PromiseUtil.timeout(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), 8000); Promise<Boolean, Exception> promise = PromiseUtil.timeout(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), 8000);
boolean shouldBecomeFriends = PromiseUtil.get(promise, false); boolean shouldBecomeFriends = PromiseUtil.get(promise, false);
if (shouldBecomeFriends) { if (shouldBecomeFriends) {
// Become friends AND update the message they sent // Become friends AND update the message they sent
becomeFriendsWithContact(content.getSender(), true, true); becomeFriendsWithContactIfNeeded(content.getSender(), true, true);
// Send them an accept message back // Send them an accept message back
MessageSender.sendBackgroundMessage(context, content.getSender()); MessageSender.sendBackgroundMessage(context, content.getSender());
} else { } else {
// Do regular friend request logic checks // Do regular friend request logic checks
Recipient originalRecipient = getMessageDestination(content, message); Recipient originalRecipient = getRecipientForMessage(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); Recipient masterRecipient = getMasterRecipientForMessage(content, message);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
// Loki - Friend requests only work in direct chats // Loki - Friend requests only work in direct chats
if (!originalRecipient.getAddress().isPhone()) { return; } if (!originalRecipient.getAddress().isPhone()) { return; }
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient); 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); LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) { if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
@ -1482,7 +1453,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
private void triggerSessionRestorePrompt(@NonNull String sender) { private void triggerSessionRestorePrompt(@NonNull String sender) {
Recipient primaryRecipient = getPrimaryDeviceRecipient(sender); Recipient primaryRecipient = getMasterRecipient(sender);
if (!primaryRecipient.isGroupRecipient()) { if (!primaryRecipient.isGroupRecipient()) {
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient);
DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, sender); 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())) { if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) {
database.setProfileKey(recipient, message.getProfileKey().get()); database.setProfileKey(recipient, message.getProfileKey().get());
database.setUnidentifiedAccessMode(recipient, RecipientDatabase.UnidentifiedAccessMode.UNKNOWN); 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)); 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 // 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()) { 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, private void handleDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message) @NonNull SignalServiceReceiptMessage message)
{ {
// Redirect message to primary device conversation // Redirect message to master device conversation
Address sender = Address.fromSerialized(content.getSender()); Address sender = Address.fromSerialized(content.getSender());
if (sender.isPhone()) { if (sender.isPhone()) {
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender()); Recipient masterDevice = getMasterRecipient(content.getSender());
sender = primaryDevice.getAddress(); sender = masterDevice.getAddress();
} }
for (long timestamp : message.getTimestamps()) { for (long timestamp : message.getTimestamps()) {
@ -1579,11 +1550,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{ {
if (TextSecurePreferences.isReadReceiptsEnabled(context)) { if (TextSecurePreferences.isReadReceiptsEnabled(context)) {
// Redirect message to primary device conversation // Redirect message to master device conversation
Address sender = Address.fromSerialized(content.getSender()); Address sender = Address.fromSerialized(content.getSender());
if (sender.isPhone()) { if (sender.isPhone()) {
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender()); Recipient masterDevice = getMasterRecipient(content.getSender());
sender = primaryDevice.getAddress(); sender = masterDevice.getAddress();
} }
for (long timestamp : message.getTimestamps()) { for (long timestamp : message.getTimestamps()) {
@ -1614,7 +1585,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient); threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient);
} else { } else {
// See if we need to redirect the message // See if we need to redirect the message
author = getPrimaryDeviceRecipient(content.getSender()); author = getMasterRecipient(content.getSender());
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(author); threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(author);
} }
@ -1748,9 +1719,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) { private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) {
Recipient primaryDevice = getPrimaryDeviceRecipient(sender); Recipient masterDevice = getMasterRecipient(sender);
SmsDatabase database = DatabaseFactory.getSmsDatabase(context); SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
IncomingTextMessage textMessage = new IncomingTextMessage(primaryDevice.getAddress(), IncomingTextMessage textMessage = new IncomingTextMessage(masterDevice.getAddress(),
senderDevice, timestamp, "", senderDevice, timestamp, "",
Optional.absent(), 0, false); Optional.absent(), 0, false);
@ -1770,11 +1741,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (message.getMessage().isGroupMessage()) { if (message.getMessage().isGroupMessage()) {
return getSyncMessageDestination(message); return getSyncMessageDestination(message);
} else { } 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()) { if (message.isGroupMessage()) {
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())), false); return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())), false);
} else { } 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()) { if (message.isGroupMessage()) {
return getMessageDestination(content, message); return getRecipientForMessage(content, message);
} else { } 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 doesn't have a master device this will return the same device.
* If the device is our primary device then it will return our current device. * If the device is our master device then it will return our current device.
* Otherwise it will return the primary device. * Otherwise it will return the master device.
*/ */
private Recipient getPrimaryDeviceRecipient(String pubKey) { private Recipient getMasterRecipient(String hexEncodedPublicKey) {
try { try {
String primaryDevice = PromiseUtil.timeout(LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey), 5000).get(); String masterHexEncodedPublicKey = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey), 5000).get();
String publicKey = (primaryDevice != null) ? primaryDevice : pubKey; String targetHexEncodedPublicKey = (masterHexEncodedPublicKey != null) ? masterHexEncodedPublicKey : hexEncodedPublicKey;
// If the public key matches our primary device then we need to forward the message to ourselves (Note to self) // If the public key matches our master device then we need to forward the message to ourselves (note to self)
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); String ourMasterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) { if (ourMasterHexEncodedPublicKey != null && ourMasterHexEncodedPublicKey.equals(targetHexEncodedPublicKey)) {
publicKey = TextSecurePreferences.getLocalNumber(context); targetHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
} }
return Recipient.from(context, Address.fromSerialized(publicKey), false); return Recipient.from(context, Address.fromSerialized(targetHexEncodedPublicKey), false);
} catch (Exception e) { } catch (Exception e) {
Log.d("Loki", "Failed to get primary device public key for " + pubKey + ". " + e.getMessage()); Log.d("Loki", "Failed to get master device for: " + hexEncodedPublicKey + ". " + e.getMessage());
return Recipient.from(context, Address.fromSerialized(pubKey), false); 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); Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
if (content.getPairingAuthorisation().isPresent()) { if (content.getDeviceLink().isPresent()) {
return false; return false;
} else if (content.getDataMessage().isPresent()) { } else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get(); SignalServiceDataMessage message = content.getDataMessage().get();
Recipient conversation = getMessageDestination(content, message); Recipient conversation = getRecipientForMessage(content, message);
if (conversation.isGroupRecipient() && conversation.isBlocked()) { if (conversation.isGroupRecipient() && conversation.isBlocked()) {
return true; return true;

View File

@ -50,8 +50,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; 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.LokiPublicChat;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.IOException; import java.io.IOException;
@ -368,7 +368,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
if (!member.isPhone() || member.serialize().equalsIgnoreCase(localNumber)) { continue; } if (!member.isPhone() || member.serialize().equalsIgnoreCase(localNumber)) { continue; }
try { try {
List<String> secondaryDevices = PromiseUtil.timeout(LokiStorageAPI.shared.getSecondaryDevicePublicKeys(member.serialize()), 5000).get(); Set<String> secondaryDevices = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getSlaveHexEncodedPublicKeys(member.serialize()), 5000).get();
memberSet.addAll(Stream.of(secondaryDevices).map(string -> { memberSet.addAll(Stream.of(secondaryDevices).map(string -> {
// Loki - Calling .map(Address::fromSerialized) is causing errors, thus we use the long method :( // Loki - Calling .map(Address::fromSerialized) is causing errors, thus we use the long method :(
return Address.fromSerialized(string); return Address.fromSerialized(string);

View File

@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.recipients.Recipient; 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.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; 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.messaging.LokiSyncMessage;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -291,11 +289,11 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
} else { } else {
LokiSyncMessage syncMessage = null; LokiSyncMessage syncMessage = null;
if (shouldSendSyncMessage) { 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 // 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 primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null); String masterDevice = PromiseUtil.get(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(address.getNumber()), null);
SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); SignalServiceAddress masterAddress = masterDevice == null ? address : new SignalServiceAddress(masterDevice);
// We also need to use the original message id and not -1 // We also need to use the original message ID and not -1
syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); syncMessage = new LokiSyncMessage(masterAddress, templateMessageId);
} }
return messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified(); return messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified();
} }

View File

@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; 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.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; 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.messaging.LokiSyncMessage;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
@ -236,11 +235,11 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
} else { } else {
LokiSyncMessage syncMessage = null; LokiSyncMessage syncMessage = null;
if (shouldSendSyncMessage) { 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 // 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 primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null); String masterDevice = PromiseUtil.get(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(address.getNumber()), null);
SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); SignalServiceAddress masterAddress = masterDevice == null ? address : new SignalServiceAddress(masterDevice);
// We also need to use the original message id and not -1 // We also need to use the original message ID and not -1
syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); syncMessage = new LokiSyncMessage(masterAddress, templateMessageId);
} }
return messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified(); return messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified();
} }

View File

@ -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." }
}
}

View File

@ -10,11 +10,11 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.* import org.thoughtcrime.securesms.loki.redesign.utilities.*
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
import org.whispersystems.signalservice.internal.util.JsonUtil import org.whispersystems.signalservice.internal.util.JsonUtil
import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.LokiPublicChat
import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol { class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
@ -23,11 +23,11 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
companion object { companion object {
private val friendRequestTableName = "loki_thread_friend_request_database" private val friendRequestTableName = "loki_thread_friend_request_database"
private val sessionResetTableName = "loki_thread_session_reset_database" private val sessionResetTableName = "loki_thread_session_reset_database"
public val publicChatTableName = "loki_public_chat_database" val publicChatTableName = "loki_public_chat_database"
public val threadID = "thread_id" val threadID = "thread_id"
private val friendRequestStatus = "friend_request_status" private val friendRequestStatus = "friend_request_status"
private val sessionResetStatus = "session_reset_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 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 createSessionResetTableCommand = "CREATE TABLE $sessionResetTableName ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTableName ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);" @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 || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED
} }
override fun getSessionResetStatus(threadID: Long): LokiThreadSessionResetStatus { fun getSessionResetStatus(hexEncodedPublicKey: String): LokiSessionResetStatus {
val threadID = getThreadID(hexEncodedPublicKey)
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
val result = database.get(sessionResetTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor -> val result = database.get(sessionResetTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
cursor.getInt(sessionResetStatus) cursor.getInt(sessionResetStatus)
} }
return if (result != null) { return if (result != null) {
LokiThreadSessionResetStatus.values().first { it.rawValue == result } LokiSessionResetStatus.values().first { it.rawValue == result }
} else { } 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 database = databaseHelper.writableDatabase
val contentValues = ContentValues(2) val contentValues = ContentValues(2)
contentValues.put(Companion.threadID, threadID) contentValues.put(Companion.threadID, threadID)

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.loki
import android.content.Context import android.content.Context
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.all
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
import nl.komponents.kovenant.toFailVoid 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.database.DatabaseFactory
import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.DeviceLink
import org.whispersystems.signalservice.loki.api.PairingAuthorisation 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.messaging.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.utilities.recover import org.whispersystems.signalservice.loki.utilities.recover
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
import java.util.*
import kotlin.concurrent.schedule
fun checkForRevocation(context: Context) { fun checkIsRevokedSlaveDevice(context: Context) {
val primaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return
val ourDevice = TextSecurePreferences.getLocalNumber(context) val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
LokiFileServerAPI.shared.getDeviceLinks(masterHexEncodedPublicKey, true).bind { deviceLinks ->
LokiStorageAPI.shared.fetchDeviceMappings(primaryDevice).bind { mappings -> val deviceLink = deviceLinks.find { it.masterHexEncodedPublicKey == masterHexEncodedPublicKey && it.slaveHexEncodedPublicKey == hexEncodedPublicKey }
val ourMapping = mappings.find { it.secondaryDevicePublicKey == ourDevice } if (deviceLink != null) throw Error("Device hasn't been revoked.")
if (ourMapping != null) throw Error("Device has not been revoked") DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(hexEncodedPublicKey)
// remove pairing authorisations for our device LokiFileServerAPI.shared.setDeviceLinks(setOf())
DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(ourDevice)
LokiStorageAPI.shared.updateUserDeviceMappings()
}.successUi { }.successUi {
TextSecurePreferences.setNeedsRevocationCheck(context, false) TextSecurePreferences.setNeedsIsRevokedSlaveDeviceCheck(context, false)
ApplicationContext.getInstance(context).clearData() ApplicationContext.getInstance(context).clearData()
}.fail { error -> }.fail { error ->
TextSecurePreferences.setNeedsRevocationCheck(context, true) TextSecurePreferences.setNeedsIsRevokedSlaveDeviceCheck(context, true)
Log.d("Loki", "Revocation check failed: ${error.message ?: error}") Log.d("Loki", "Revocation check failed due to error: ${error.message ?: error}.")
} }
} }
fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise<Map<String, LokiThreadFriendRequestStatus>, Exception> { fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise<Map<String, LokiThreadFriendRequestStatus>, Exception> {
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> return LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(hexEncodedPublicKey).map { keys ->
val map = mutableMapOf<String, LokiThreadFriendRequestStatus>() val map = mutableMapOf<String, LokiThreadFriendRequestStatus>()
for (devicePublicKey in keys) { for (devicePublicKey in keys) {
val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false) val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false)
@ -63,7 +58,7 @@ fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: Str
fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise<Map<String, Boolean>, Unit> { fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise<Map<String, Boolean>, Unit> {
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> return LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(hexEncodedPublicKey).map { keys ->
val devices = keys.toMutableSet() val devices = keys.toMutableSet()
if (hexEncodedPublicKey != userHexEncodedPublicKey) { if (hexEncodedPublicKey != userHexEncodedPublicKey) {
devices.remove(userHexEncodedPublicKey) devices.remove(userHexEncodedPublicKey)
@ -92,7 +87,7 @@ fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Conte
return Promise.of(true) return Promise.of(true)
} }
return LokiStorageAPI.shared.getPrimaryDevicePublicKey(publicKey).bind { primaryDevicePublicKey -> return LokiDeviceLinkUtilities.getMasterHexEncodedPublicKey(publicKey).bind { primaryDevicePublicKey ->
// If the public key doesn't have any other devices then go through regular friend request logic // If the public key doesn't have any other devices then go through regular friend request logic
if (primaryDevicePublicKey == null) { if (primaryDevicePublicKey == null) {
return@bind Promise.of(false) return@bind Promise.of(false)
@ -108,66 +103,44 @@ fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Conte
} }
} }
fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> { fun sendDeviceLinkMessage(context: Context, hexEncodedPublicKey: String, deviceLink: DeviceLink): Promise<Unit, Exception> {
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(contactHexEncodedPublicKey) val address = SignalServiceAddress(hexEncodedPublicKey)
val message = SignalServiceDataMessage.newBuilder().withPairingAuthorisation(authorisation) val message = SignalServiceDataMessage.newBuilder().withDeviceLink(deviceLink)
// A REQUEST should always act as a friend request. A GRANT should always be replying back as a normal message. // A REQUEST should always act as a friend request. An AUTHORIZATION should always be a normal message.
if (authorisation.type == PairingAuthorisation.Type.REQUEST) { if (deviceLink.type == DeviceLink.Type.REQUEST) {
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number) val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
message.asFriendRequest(true).withPreKeyBundle(preKeyBundle) message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
} else { } else {
// Send over our profile key so that our linked device can get our profile picture // Send over our profile key so that our linked device can get our profile picture
message.withProfileKey(ProfileKeyUtil.getProfileKey(context)) message.withProfileKey(ProfileKeyUtil.getProfileKey(context))
} }
return try { return try {
Log.d("Loki", "Sending authorisation message to: $contactHexEncodedPublicKey.") Log.d("Loki", "Sending device link message to: $hexEncodedPublicKey.")
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(contactHexEncodedPublicKey), false)) val udAccess = UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false))
val result = messageSender.sendMessage(0, address, udAccess, message.build()) val result = messageSender.sendMessage(0, address, udAccess, message.build())
if (result.success == null) { if (result.success == null) {
val exception = when { val exception = when {
result.isNetworkFailure -> "Failed to send authorisation message due to a network error." result.isNetworkFailure -> "Failed to send device link message due to a network error."
else -> "Failed to send authorisation message." else -> "Failed to send device link message."
} }
throw Exception(exception) throw Exception(exception)
} }
Promise.ofSuccess(Unit) Promise.ofSuccess(Unit)
} catch (e: Exception) { } catch (e: Exception) {
Log.d("Loki", "Failed to send authorisation message to: $contactHexEncodedPublicKey.") Log.d("Loki", "Failed to send device link message to: $hexEncodedPublicKey.")
Promise.ofFail(e) Promise.ofFail(e)
} }
} }
fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisation: PairingAuthorisation) { fun signAndSendDeviceLinkMessage(context: Context, deviceLink: DeviceLink): Promise<Unit, Exception> {
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
val signedPairingAuthorisation = pairingAuthorisation.sign(PairingAuthorisation.Type.GRANT, userPrivateKey) val signedDeviceLink = deviceLink.sign(DeviceLink.Type.AUTHORIZATION, userPrivateKey)
if (signedPairingAuthorisation == null || signedPairingAuthorisation.type != PairingAuthorisation.Type.GRANT) { if (signedDeviceLink == null || signedDeviceLink.type != DeviceLink.Type.AUTHORIZATION) {
Log.d("Loki", "Failed to sign pairing authorization.") return Promise.ofFail(Exception("Failed to sign device link."))
return
}
DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation)
TextSecurePreferences.setMultiDevice(context, true)
val address = Address.fromSerialized(pairingAuthorisation.secondaryDevicePublicKey);
val sendPromise = retryIfNeeded(8) {
sendPairingAuthorisationMessage(context, address.serialize(), signedPairingAuthorisation)
}.fail {
Log.d("Loki", "Failed to send pairing authorization message to ${address.serialize()}.")
}
val updatePromise = LokiStorageAPI.shared.updateUserDeviceMappings().fail {
Log.d("Loki", "Failed to update device mapping")
}
// If both promises complete successfully then we should sync our contacts
all(listOf(sendPromise, updatePromise), cancelOthersOnError = false).success {
Log.d("Loki", "Successfully pairing with a secondary device! Syncing contacts.")
// Send out sync contact after a delay
Timer().schedule(3000) {
MessageSender.syncAllContacts(context, address)
} }
return retryIfNeeded(8) {
sendDeviceLinkMessage(context, deviceLink.slaveHexEncodedPublicKey, signedDeviceLink)
} }
} }
@ -177,7 +150,7 @@ fun isOneOfOurDevices(context: Context, address: Address): Promise<Boolean, Exce
} }
val ourPublicKey = TextSecurePreferences.getLocalNumber(context) val ourPublicKey = TextSecurePreferences.getLocalNumber(context)
return LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).map { devices -> return LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(ourPublicKey).map { devices ->
devices.contains(address.serialize()) devices.contains(address.serialize())
} }
} }

View File

@ -10,8 +10,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.jobs.BaseJob import org.thoughtcrime.securesms.jobs.BaseJob
import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.util.JsonUtil import org.whispersystems.signalservice.internal.util.JsonUtil
@ -99,7 +97,7 @@ class PushBackgroundMessageSendJob private constructor(
} }
if (message.get("sessionRestore", false)) { if (message.get("sessionRestore", false)) {
dataMessage.asSessionRestore(true) dataMessage.asSessionRestorationRequest(true)
} }
if (message.get("sessionRequest", false)) { if (message.get("sessionRequest", false)) {

View File

@ -104,9 +104,9 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
val recipients = selectedMembers.map { val recipients = selectedMembers.map {
Recipient.from(this, Address.fromSerialized(it), false) Recipient.from(this, Address.fromSerialized(it), false)
}.toSet() }.toSet()
val ourNumber = TextSecurePreferences.getMasterHexEncodedPublicKey(this) ?: TextSecurePreferences.getLocalNumber(this) val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this) ?: TextSecurePreferences.getLocalNumber(this)
val local = Recipient.from(this, Address.fromSerialized(ourNumber), false) val admin = Recipient.from(this, Address.fromSerialized(masterHexEncodedPublicKey), false)
CreateClosedGroupTask(WeakReference(this), null, name.toString(), recipients, setOf(local)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) CreateClosedGroupTask(WeakReference(this), null, name.toString(), recipients, setOf( admin )).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
} }
private fun handleOpenConversation(threadId: Long, recipient: Recipient) { private fun handleOpenConversation(threadId: Long, recipient: Recipient) {
@ -122,7 +122,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
// region Tasks // region Tasks
internal class CreateClosedGroupTask( internal class CreateClosedGroupTask(
private val activity: WeakReference<CreateClosedGroupActivity>, private val activity: WeakReference<CreateClosedGroupActivity>,
private val avatar: Bitmap?, private val profilePicture: Bitmap?,
private val name: String?, private val name: String?,
private val members: Set<Recipient>, private val members: Set<Recipient>,
private val admins: Set<Recipient> private val admins: Set<Recipient>
@ -130,24 +130,18 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
override fun doInBackground(vararg params: Void?): Optional<GroupManager.GroupActionResult> { override fun doInBackground(vararg params: Void?): Optional<GroupManager.GroupActionResult> {
val activity = activity.get() ?: return Optional.absent() val activity = activity.get() ?: return Optional.absent()
return Optional.of(GroupManager.createGroup(activity, members, avatar, name, false, admins)) return Optional.of(GroupManager.createGroup(activity, members, profilePicture, name, false, admins))
} }
override fun onPostExecute(result: Optional<GroupManager.GroupActionResult>) { override fun onPostExecute(result: Optional<GroupManager.GroupActionResult>) {
val activity = activity.get() val activity = activity.get() ?: return super.onPostExecute(result)
if (activity == null) {
super.onPostExecute(result)
return
}
if (result.isPresent && result.get().threadId > -1) { if (result.isPresent && result.get().threadId > -1) {
if (!activity.isFinishing) { if (!activity.isFinishing) {
activity.handleOpenConversation(result.get().threadId, result.get().groupRecipient) activity.handleOpenConversation(result.get().threadId, result.get().groupRecipient)
} }
} else { } else {
super.onPostExecute(result) super.onPostExecute(result)
Toast.makeText(activity.applicationContext, Toast.makeText(activity.applicationContext, "One of the members of your group has an invalid Session ID.", Toast.LENGTH_LONG).show()
R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show()
} }
} }
} }

View File

@ -12,9 +12,9 @@ class CreateClosedGroupLoader(context: Context) : AsyncLoader<List<String>>(cont
val threadDatabase = DatabaseFactory.getThreadDatabase(context) val threadDatabase = DatabaseFactory.getThreadDatabase(context)
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val deviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getPairingAuthorisations(userHexEncodedPublicKey) val deviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getDeviceLinks(userHexEncodedPublicKey)
val userLinkedDeviceHexEncodedPublicKeys = deviceLinks.flatMap { val userLinkedDeviceHexEncodedPublicKeys = deviceLinks.flatMap {
listOf( it.primaryDevicePublicKey.toLowerCase(), it.secondaryDevicePublicKey.toLowerCase() ) listOf( it.masterHexEncodedPublicKey.toLowerCase(), it.slaveHexEncodedPublicKey.toLowerCase() )
}.toMutableSet() }.toMutableSet()
userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey.toLowerCase()) userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey.toLowerCase())
val cursor = threadDatabase.conversationList val cursor = threadDatabase.conversationList

View File

@ -4,7 +4,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import kotlinx.android.synthetic.main.activity_display_name_v2.* import kotlinx.android.synthetic.main.activity_display_name.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
@ -19,7 +19,7 @@ class DisplayNameActivity : BaseActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
setContentView(R.layout.activity_display_name_v2) setContentView(R.layout.activity_display_name)
displayNameEditText.imeOptions = displayNameEditText.imeOptions or 16777216 // Always use incognito keyboard displayNameEditText.imeOptions = displayNameEditText.imeOptions or 16777216 // Always use incognito keyboard
registerButton.setOnClickListener { register() } registerButton.setOnClickListener { register() }
} }

View File

@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragmentDelegate import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragmentDelegate
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
@ -72,6 +73,8 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
val displayName = TextSecurePreferences.getProfileName(this) val displayName = TextSecurePreferences.getProfileName(this)
val lokiPublicChatAPI = application.lokiPublicChatAPI!! val lokiPublicChatAPI = application.lokiPublicChatAPI!!
application.lokiPublicChatManager.addChat(url, channel).successUi { application.lokiPublicChatManager.addChat(url, channel).successUi {
DatabaseFactory.getLokiAPIDatabase(this).removeLastMessageServerID(channel, url)
DatabaseFactory.getLokiAPIDatabase(this).removeLastDeletionServerID(channel, url)
lokiPublicChatAPI.getMessages(channel, url) lokiPublicChatAPI.getMessages(channel, url)
lokiPublicChatAPI.setDisplayName(displayName, url) lokiPublicChatAPI.setDisplayName(displayName, url)
lokiPublicChatAPI.join(channel, url) lokiPublicChatAPI.join(channel, url)
@ -133,7 +136,10 @@ class EnterChatURLFragment : Fragment() {
private fun joinPublicChatIfPossible() { private fun joinPublicChatIfPossible() {
val inputMethodManager = context!!.getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager val inputMethodManager = context!!.getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(chatURLEditText.windowToken, 0) inputMethodManager.hideSoftInputFromWindow(chatURLEditText.windowToken, 0)
val chatURL = chatURLEditText.text.trim().toString().toLowerCase().replace("http://", "https://") var chatURL = chatURLEditText.text.trim().toString().toLowerCase().replace("http://", "https://")
if (!chatURL.toLowerCase().startsWith("https")) {
chatURL = "https://$chatURL"
}
(activity!! as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL) (activity!! as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
} }
} }

View File

@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.loki.redesign.dialogs.LinkDeviceSlaveModeDialo
import org.thoughtcrime.securesms.loki.redesign.utilities.push import org.thoughtcrime.securesms.loki.redesign.utilities.push
import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.redesign.utilities.show import org.thoughtcrime.securesms.loki.redesign.utilities.show
import org.thoughtcrime.securesms.loki.sendPairingAuthorisationMessage import org.thoughtcrime.securesms.loki.sendDeviceLinkMessage
import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.Hex import org.thoughtcrime.securesms.util.Hex
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
@ -26,7 +26,8 @@ import org.whispersystems.curve25519.Curve25519
import org.whispersystems.libsignal.ecc.Curve import org.whispersystems.libsignal.ecc.Curve
import org.whispersystems.libsignal.ecc.ECKeyPair import org.whispersystems.libsignal.ecc.ECKeyPair
import org.whispersystems.libsignal.util.KeyHelper import org.whispersystems.libsignal.util.KeyHelper
import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.api.DeviceLink
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI
import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
@ -92,11 +93,11 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey) TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
TextSecurePreferences.setHasSeenWelcomeScreen(this, true) TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setPromptedPushRegistration(this, true) TextSecurePreferences.setPromptedPushRegistration(this, true)
val authorisation = PairingAuthorisation(hexEncodedPublicKey, userHexEncodedPublicKey).sign(PairingAuthorisation.Type.REQUEST, keyPair!!.privateKey.serialize()) val deviceLink = DeviceLink(hexEncodedPublicKey, userHexEncodedPublicKey).sign(DeviceLink.Type.REQUEST, keyPair!!.privateKey.serialize())
if (authorisation == null) { if (deviceLink == null) {
Log.d("Loki", "Failed to sign device link request.") Log.d("Loki", "Failed to sign device link request.")
reset() reset()
return Toast.makeText(application, "Couldn't link device.", Toast.LENGTH_SHORT).show() return Toast.makeText(application, "Couldn't link device.", Toast.LENGTH_LONG).show()
} }
val application = ApplicationContext.getInstance(this) val application = ApplicationContext.getInstance(this)
application.startLongPollingIfNeeded() application.startLongPollingIfNeeded()
@ -107,13 +108,14 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
linkDeviceDialog.show(supportFragmentManager, "Link Device Dialog") linkDeviceDialog.show(supportFragmentManager, "Link Device Dialog")
AsyncTask.execute { AsyncTask.execute {
retryIfNeeded(8) { retryIfNeeded(8) {
sendPairingAuthorisationMessage(this@LandingActivity, authorisation.primaryDevicePublicKey, authorisation) sendDeviceLinkMessage(this@LandingActivity, deviceLink.masterHexEncodedPublicKey, deviceLink)
} }
} }
} }
override fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation) { override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
TextSecurePreferences.setMasterHexEncodedPublicKey(this, authorization.primaryDevicePublicKey) LokiFileServerAPI.shared.addDeviceLink(deviceLink)
TextSecurePreferences.setMasterHexEncodedPublicKey(this, deviceLink.masterHexEncodedPublicKey)
val intent = Intent(this, HomeActivity::class.java) val intent = Intent(this, HomeActivity::class.java)
show(intent) show(intent)
finish() finish()

View File

@ -12,16 +12,21 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import kotlinx.android.synthetic.main.activity_linked_devices.* import kotlinx.android.synthetic.main.activity_linked_devices.*
import network.loki.messenger.R import network.loki.messenger.R
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.devicelist.Device import org.thoughtcrime.securesms.devicelist.Device
import org.thoughtcrime.securesms.loki.redesign.dialogs.* import org.thoughtcrime.securesms.loki.redesign.dialogs.*
import org.thoughtcrime.securesms.loki.signAndSendPairingAuthorisationMessage import org.thoughtcrime.securesms.loki.signAndSendDeviceLinkMessage
import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.DeviceLink
import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.api.LokiFileServerAPI
import java.util.*
import kotlin.concurrent.schedule
class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager.LoaderCallbacks<List<Device>>, DeviceClickListener, EditDeviceNameDialogDelegate, LinkDeviceMasterModeDialogDelegate { class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager.LoaderCallbacks<List<Device>>, DeviceClickListener, EditDeviceNameDialogDelegate, LinkDeviceMasterModeDialogDelegate {
private var devices = listOf<Device>() private var devices = listOf<Device>()
@ -118,20 +123,46 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager
private fun unlinkDevice(slaveDeviceHexEncodedPublicKey: String) { private fun unlinkDevice(slaveDeviceHexEncodedPublicKey: String) {
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
val database = DatabaseFactory.getLokiAPIDatabase(this) val database = DatabaseFactory.getLokiAPIDatabase(this)
database.removePairingAuthorisation(userHexEncodedPublicKey, slaveDeviceHexEncodedPublicKey) val deviceLinks = database.getDeviceLinks(userHexEncodedPublicKey)
LokiStorageAPI.shared.updateUserDeviceMappings().success { val deviceLink = deviceLinks.find { it.masterHexEncodedPublicKey == userHexEncodedPublicKey && it.slaveHexEncodedPublicKey == slaveDeviceHexEncodedPublicKey }
if (deviceLink == null) {
return Toast.makeText(this, "Couldn't unlink device.", Toast.LENGTH_LONG).show()
}
LokiFileServerAPI.shared.setDeviceLinks(setOf()).successUi {
AsyncTask.execute {
DatabaseFactory.getLokiAPIDatabase(this).clearDeviceLinks(userHexEncodedPublicKey)
deviceLinks.forEach { deviceLink ->
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)
}
MessageSender.sendUnpairRequest(this, slaveDeviceHexEncodedPublicKey) MessageSender.sendUnpairRequest(this, slaveDeviceHexEncodedPublicKey)
} }
LoaderManager.getInstance(this).restartLoader(0, null, this) LoaderManager.getInstance(this).restartLoader(0, null, this)
Toast.makeText(this, "Your device was unlinked successfully", Toast.LENGTH_LONG).show() Toast.makeText(this, "Your device was unlinked successfully", Toast.LENGTH_LONG).show()
}.fail {
Toast.makeText(this, "Couldn't unlink device.", Toast.LENGTH_LONG).show()
}
} }
override fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation) { override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
AsyncTask.execute { LokiFileServerAPI.shared.addDeviceLink(deviceLink).success {
signAndSendPairingAuthorisationMessage(this, authorization) signAndSendDeviceLinkMessage(this, deviceLink).success {
TextSecurePreferences.setMultiDevice(this, true)
Util.runOnMain { Util.runOnMain {
LoaderManager.getInstance(this).restartLoader(0, null, this) LoaderManager.getInstance(this).restartLoader(0, null, this)
} }
Timer().schedule(4000) {
MessageSender.syncAllContacts(this@LinkedDevicesActivity, Address.fromSerialized(deviceLink.slaveHexEncodedPublicKey))
}
}.fail {
LokiFileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)
Util.runOnMain {
Toast.makeText(this, "Couldn't link device", Toast.LENGTH_LONG).show()
}
}
}.failUi {
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)
Toast.makeText(this, "Couldn't link device", Toast.LENGTH_LONG).show()
} }
} }

View File

@ -6,7 +6,7 @@ import org.thoughtcrime.securesms.devicelist.Device
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.AsyncLoader import org.thoughtcrime.securesms.util.AsyncLoader
import org.thoughtcrime.securesms.util.TextSecurePreferences 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 org.whispersystems.signalservice.loki.crypto.MnemonicCodec
import java.io.File import java.io.File
@ -20,7 +20,7 @@ class LinkedDevicesLoader(context: Context) : AsyncLoader<List<Device>>(context)
override fun loadInBackground(): List<Device>? { override fun loadInBackground(): List<Device>? {
try { try {
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val slaveDeviceHexEncodedPublicKeys = LokiStorageAPI.shared.getSecondaryDevicePublicKeys(userHexEncodedPublicKey).get() val slaveDeviceHexEncodedPublicKeys = LokiDeviceLinkUtilities.getSlaveHexEncodedPublicKeys(userHexEncodedPublicKey).get()
return slaveDeviceHexEncodedPublicKeys.map { hexEncodedPublicKey -> return slaveDeviceHexEncodedPublicKeys.map { hexEncodedPublicKey ->
val shortID = MnemonicUtilities.getFirst3Words(mnemonicCodec, hexEncodedPublicKey) val shortID = MnemonicUtilities.getFirst3Words(mnemonicCodec, hexEncodedPublicKey)
val name = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey) val name = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)

View File

@ -9,7 +9,7 @@ import android.text.SpannableString
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import kotlinx.android.synthetic.main.activity_seed_v2.* import kotlinx.android.synthetic.main.activity_seed.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
@ -33,7 +33,7 @@ class SeedActivity : BaseActionBarActivity() {
// region Lifecycle // region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_seed_v2) setContentView(R.layout.activity_seed)
supportActionBar!!.title = "Your Recovery Phrase" supportActionBar!!.title = "Your Recovery Phrase"
val seedReminderViewTitle = SpannableString("You're almost finished! 90%") val seedReminderViewTitle = SpannableString("You're almost finished! 90%")
seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

View File

@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.crypto.ProfileCipher import org.whispersystems.signalservice.api.crypto.ProfileCipher
import org.whispersystems.signalservice.api.util.StreamDetails import org.whispersystems.signalservice.api.util.StreamDetails
import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.LokiFileServerAPI
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.security.SecureRandom import java.security.SecureRandom
@ -159,7 +159,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this) val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey) val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey)
if (isUpdatingProfilePicture && profilePicture != null) { if (isUpdatingProfilePicture && profilePicture != null) {
val storageAPI = LokiStorageAPI.shared val storageAPI = LokiFileServerAPI.shared
val deferred = deferred<Unit, Exception>() val deferred = deferred<Unit, Exception>()
AsyncTask.execute { AsyncTask.execute {
val stream = StreamDetails(ByteArrayInputStream(profilePicture), "image/jpeg", profilePicture.size.toLong()) val stream = StreamDetails(ByteArrayInputStream(profilePicture), "image/jpeg", profilePicture.size.toLong())
@ -178,7 +178,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), profilePicture) AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), profilePicture)
TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt()) TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt())
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey) ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
ApplicationContext.getInstance(this).updatePublicChatProfileAvatarIfNeeded() ApplicationContext.getInstance(this).updatePublicChatProfilePictureIfNeeded()
profilePictureView.update() profilePictureView.update()
} }
profilePictureToBeUploaded = null profilePictureToBeUploaded = null

View File

@ -17,15 +17,15 @@ import org.thoughtcrime.securesms.loki.redesign.utilities.QRCodeUtilities
import org.thoughtcrime.securesms.loki.toPx import org.thoughtcrime.securesms.loki.toPx
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.DeviceLink
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession import org.whispersystems.signalservice.loki.api.DeviceLinkingSession
import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListener { class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListener {
private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) } private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) }
private lateinit var contentView: View private lateinit var contentView: View
private var authorization: PairingAuthorisation? = null private var deviceLink: DeviceLink? = null
var delegate: LinkDeviceMasterModeDialogDelegate? = null var delegate: LinkDeviceMasterModeDialogDelegate? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@ -45,10 +45,10 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
return result return result
} }
override fun requestUserAuthorization(authorization: PairingAuthorisation) { override fun requestUserAuthorization(deviceLink: DeviceLink) {
if (authorization.type != PairingAuthorisation.Type.REQUEST || authorization.primaryDevicePublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.authorization != null) { return } if (deviceLink.type != DeviceLink.Type.REQUEST || deviceLink.masterHexEncodedPublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
Util.runOnMain { Util.runOnMain {
this.authorization = authorization this.deviceLink = deviceLink
contentView.qrCodeImageView.visibility = View.GONE contentView.qrCodeImageView.visibility = View.GONE
val titleTextViewLayoutParams = contentView.titleTextView.layoutParams as LinearLayout.LayoutParams val titleTextViewLayoutParams = contentView.titleTextView.layoutParams as LinearLayout.LayoutParams
titleTextViewLayoutParams.topMargin = toPx(8, resources) titleTextViewLayoutParams.topMargin = toPx(8, resources)
@ -56,13 +56,13 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
contentView.titleTextView.text = "Linking Request Received" contentView.titleTextView.text = "Linking Request Received"
contentView.explanationTextView.text = "Please check that the words below match those shown on your other device" contentView.explanationTextView.text = "Please check that the words below match those shown on your other device"
contentView.mnemonicTextView.visibility = View.VISIBLE contentView.mnemonicTextView.visibility = View.VISIBLE
contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), authorization.secondaryDevicePublicKey) contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), deviceLink.slaveHexEncodedPublicKey)
contentView.authorizeButton.visibility = View.VISIBLE contentView.authorizeButton.visibility = View.VISIBLE
} }
} }
private fun authorizeDeviceLink() { private fun authorizeDeviceLink() {
val authorization = this.authorization ?: return val authorization = this.deviceLink ?: return
delegate?.onDeviceLinkRequestAuthorized(authorization) delegate?.onDeviceLinkRequestAuthorized(authorization)
DeviceLinkingSession.shared.stopListeningForLinkingRequests() DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this) DeviceLinkingSession.shared.removeListener(this)
@ -72,8 +72,8 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
private fun onDeviceLinkCanceled() { private fun onDeviceLinkCanceled() {
DeviceLinkingSession.shared.stopListeningForLinkingRequests() DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this) DeviceLinkingSession.shared.removeListener(this)
if (authorization != null) { if (deviceLink != null) {
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorization!!.secondaryDevicePublicKey) DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(deviceLink!!.slaveHexEncodedPublicKey)
} }
dismiss() dismiss()
delegate?.onDeviceLinkCanceled() delegate?.onDeviceLinkCanceled()
@ -82,6 +82,6 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
interface LinkDeviceMasterModeDialogDelegate { interface LinkDeviceMasterModeDialogDelegate {
fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation) fun onDeviceLinkRequestAuthorized(authorization: DeviceLink)
fun onDeviceLinkCanceled() fun onDeviceLinkCanceled()
} }

View File

@ -15,15 +15,15 @@ import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.DeviceLink
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession import org.whispersystems.signalservice.loki.api.DeviceLinkingSession
import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener { class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener {
private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) } private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) }
private lateinit var contentView: View private lateinit var contentView: View
private var authorization: PairingAuthorisation? = null private var deviceLink: DeviceLink? = null
var delegate: LinkDeviceSlaveModeDialogDelegate? = null var delegate: LinkDeviceSlaveModeDialogDelegate? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@ -40,10 +40,10 @@ class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener
return result return result
} }
override fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation) { override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
if (authorization.type != PairingAuthorisation.Type.GRANT || authorization.secondaryDevicePublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.authorization != null) { return } if (deviceLink.type != DeviceLink.Type.AUTHORIZATION || deviceLink.slaveHexEncodedPublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
Util.runOnMain { Util.runOnMain {
this.authorization = authorization this.deviceLink = deviceLink
DeviceLinkingSession.shared.stopListeningForLinkingRequests() DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this) DeviceLinkingSession.shared.removeListener(this)
contentView.spinner.visibility = View.GONE contentView.spinner.visibility = View.GONE
@ -56,7 +56,7 @@ class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener
contentView.cancelButton.visibility = View.GONE contentView.cancelButton.visibility = View.GONE
Handler().postDelayed({ Handler().postDelayed({
dismiss() dismiss()
delegate?.onDeviceLinkRequestAuthorized(authorization) delegate?.onDeviceLinkRequestAuthorized(deviceLink)
}, 4000) }, 4000)
} }
} }
@ -71,6 +71,6 @@ class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener
interface LinkDeviceSlaveModeDialogDelegate { interface LinkDeviceSlaveModeDialogDelegate {
fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation) fun onDeviceLinkRequestAuthorized(authorization: DeviceLink)
fun onDeviceLinkCanceled() fun onDeviceLinkCanceled()
} }

View File

@ -15,7 +15,7 @@ import java.util.concurrent.TimeUnit
class BackgroundPollWorker : PersistentAlarmManagerListener() { class BackgroundPollWorker : PersistentAlarmManagerListener() {
companion object { companion object {
private val pollInterval = TimeUnit.MINUTES.toMillis(2) private val pollInterval = TimeUnit.MINUTES.toMillis(1)
@JvmStatic @JvmStatic
fun schedule(context: Context) { fun schedule(context: Context) {

View File

@ -10,7 +10,7 @@ import java.util.concurrent.TimeUnit
class BackgroundPublicChatPollWorker : PersistentAlarmManagerListener() { class BackgroundPublicChatPollWorker : PersistentAlarmManagerListener() {
companion object { companion object {
private val pollInterval = TimeUnit.MINUTES.toMillis(4) private val pollInterval = TimeUnit.MINUTES.toMillis(2)
@JvmStatic @JvmStatic
fun schedule(context: Context) { fun schedule(context: Context) {

View File

@ -7,9 +7,9 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.* import org.thoughtcrime.securesms.loki.redesign.utilities.*
import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.DeviceLink
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol
import org.whispersystems.signalservice.loki.api.LokiAPITarget import org.whispersystems.signalservice.loki.api.LokiAPITarget
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
// TODO: Clean this up a bit // TODO: Clean this up a bit
@ -48,14 +48,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
private val lastDeletionServerIDCacheIndex = "loki_api_last_deletion_server_id_cache_index" private val lastDeletionServerIDCacheIndex = "loki_api_last_deletion_server_id_cache_index"
private val lastDeletionServerID = "last_deletion_server_id" private val lastDeletionServerID = "last_deletion_server_id"
@JvmStatic val createLastDeletionServerIDTableCommand = "CREATE TABLE $lastDeletionServerIDCache ($lastDeletionServerIDCacheIndex STRING PRIMARY KEY, $lastDeletionServerID INTEGER DEFAULT 0);" @JvmStatic val createLastDeletionServerIDTableCommand = "CREATE TABLE $lastDeletionServerIDCache ($lastDeletionServerIDCacheIndex STRING PRIMARY KEY, $lastDeletionServerID INTEGER DEFAULT 0);"
// Pairing authorisation cache // Device link cache
private val pairingAuthorisationCache = "loki_pairing_authorisation_cache" private val deviceLinkCache = "loki_pairing_authorisation_cache"
private val primaryDevicePublicKey = "primary_device" private val masterHexEncodedPublicKey = "primary_device"
private val secondaryDevicePublicKey = "secondary_device" private val slaveHexEncodedPublicKey = "secondary_device"
private val requestSignature = "request_signature" private val requestSignature = "request_signature"
private val grantSignature = "grant_signature" private val authorizationSignature = "grant_signature"
@JvmStatic val createPairingAuthorisationTableCommand = "CREATE TABLE $pairingAuthorisationCache ($primaryDevicePublicKey TEXT, $secondaryDevicePublicKey TEXT, " + @JvmStatic val createDeviceLinkTableCommand = "CREATE TABLE $deviceLinkCache ($masterHexEncodedPublicKey TEXT, $slaveHexEncodedPublicKey TEXT, " +
"$requestSignature TEXT NULLABLE DEFAULT NULL, $grantSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($primaryDevicePublicKey, $secondaryDevicePublicKey));" "$requestSignature TEXT NULLABLE DEFAULT NULL, $authorizationSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($masterHexEncodedPublicKey, $slaveHexEncodedPublicKey));"
// User count cache // User count cache
private val userCountCache = "loki_user_count_cache" private val userCountCache = "loki_user_count_cache"
private val publicChatID = "public_chat_id" private val publicChatID = "public_chat_id"
@ -179,35 +179,35 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
database.delete(lastDeletionServerIDCache,"$lastDeletionServerIDCacheIndex = ?", wrap(index)) database.delete(lastDeletionServerIDCache,"$lastDeletionServerIDCacheIndex = ?", wrap(index))
} }
override fun getPairingAuthorisations(hexEncodedPublicKey: String): List<PairingAuthorisation> { override fun getDeviceLinks(hexEncodedPublicKey: String): Set<DeviceLink> {
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
return database.getAll(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) { cursor -> return database.getAll(deviceLinkCache, "$masterHexEncodedPublicKey = ? OR $slaveHexEncodedPublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) { cursor ->
val primaryDevicePubKey = cursor.getString(primaryDevicePublicKey) val masterHexEncodedPublicKey = cursor.getString(masterHexEncodedPublicKey)
val secondaryDevicePubKey = cursor.getString(secondaryDevicePublicKey) val slaveHexEncodedPublicKey = cursor.getString(slaveHexEncodedPublicKey)
val requestSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(requestSignature))) null else cursor.getBase64EncodedData(requestSignature) val requestSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(requestSignature))) null else cursor.getBase64EncodedData(requestSignature)
val grantSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(grantSignature))) null else cursor.getBase64EncodedData(grantSignature) val authorizationSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(authorizationSignature))) null else cursor.getBase64EncodedData(authorizationSignature)
PairingAuthorisation(primaryDevicePubKey, secondaryDevicePubKey, requestSignature, grantSignature) DeviceLink(masterHexEncodedPublicKey, slaveHexEncodedPublicKey, requestSignature, authorizationSignature)
} }.toSet()
} }
override fun insertOrUpdatePairingAuthorisation(authorisation: PairingAuthorisation) { override fun clearDeviceLinks(hexEncodedPublicKey: String) {
val database = databaseHelper.writableDatabase
database.delete(deviceLinkCache, "$masterHexEncodedPublicKey = ? OR $slaveHexEncodedPublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey ))
}
override fun addDeviceLink(deviceLink: DeviceLink) {
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
val values = ContentValues() val values = ContentValues()
values.put(primaryDevicePublicKey, authorisation.primaryDevicePublicKey) values.put(masterHexEncodedPublicKey, deviceLink.masterHexEncodedPublicKey)
values.put(secondaryDevicePublicKey, authorisation.secondaryDevicePublicKey) values.put(slaveHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey)
if (authorisation.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(authorisation.requestSignature)) } if (deviceLink.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(deviceLink.requestSignature)) }
if (authorisation.grantSignature != null) { values.put(grantSignature, Base64.encodeBytes(authorisation.grantSignature)) } if (deviceLink.authorizationSignature != null) { values.put(authorizationSignature, Base64.encodeBytes(deviceLink.authorizationSignature)) }
database.insertOrUpdate(pairingAuthorisationCache, values, "$primaryDevicePublicKey = ? AND $secondaryDevicePublicKey = ?", arrayOf( authorisation.primaryDevicePublicKey, authorisation.secondaryDevicePublicKey )) database.insertOrUpdate(deviceLinkCache, values, "$masterHexEncodedPublicKey = ? AND $slaveHexEncodedPublicKey = ?", arrayOf( deviceLink.masterHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey ))
} }
override fun removePairingAuthorisations(hexEncodedPublicKey: String) { override fun removeDeviceLink(deviceLink: DeviceLink) {
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
database.delete(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) database.delete(deviceLinkCache, "$masterHexEncodedPublicKey = ? OR $slaveHexEncodedPublicKey = ?", arrayOf( deviceLink.masterHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey ))
}
fun removePairingAuthorisation(primaryDevicePublicKey: String, secondaryDevicePublicKey: String) {
val database = databaseHelper.writableDatabase
database.delete(pairingAuthorisationCache, "${Companion.primaryDevicePublicKey} = ? OR ${Companion.secondaryDevicePublicKey} = ?", arrayOf( primaryDevicePublicKey, secondaryDevicePublicKey ))
} }
fun getUserCount(group: Long, server: String): Int? { fun getUserCount(group: Long, server: String): Int? {

View File

@ -21,10 +21,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.messages.SignalServiceGroup import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.*
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI
import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.utilities.successBackground import org.whispersystems.signalservice.loki.utilities.successBackground
import java.security.MessageDigest import java.security.MessageDigest
@ -155,36 +152,36 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
fun pollForNewMessages() { fun pollForNewMessages() {
fun processIncomingMessage(message: LokiPublicChatMessage) { fun processIncomingMessage(message: LokiPublicChatMessage) {
// If the sender of the current message is not a secondary device, we need to set the display name in the database // If the sender of the current message is not a slave device, set the display name in the database
val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(message.hexEncodedPublicKey).get() val masterHexEncodedPublicKey = LokiDeviceLinkUtilities.getMasterHexEncodedPublicKey(message.hexEncodedPublicKey).get()
if (primaryDevice == null) { if (masterHexEncodedPublicKey == null) {
val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})" val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName) DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)
} }
val senderPublicKey = primaryDevice ?: message.hexEncodedPublicKey val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.hexEncodedPublicKey
val serviceDataMessage = getDataMessage(message) val serviceDataMessage = getDataMessage(message)
val serviceContent = SignalServiceContent(serviceDataMessage, senderPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false) val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false)
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
} else { } else {
PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
} }
// Update profile avatar if needed // Update profile picture if needed
val senderRecipient = Recipient.from(context, Address.fromSerialized(senderPublicKey), false) val senderAsRecipient = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false)
if (message.avatar != null && message.avatar!!.url.isNotEmpty()) { if (message.profilePicture != null && message.profilePicture!!.url.isNotEmpty()) {
val profileKey = message.avatar!!.profileKey val profileKey = message.profilePicture!!.profileKey
val url = message.avatar!!.url val url = message.profilePicture!!.url
if (senderRecipient.profileKey == null || !MessageDigest.isEqual(senderRecipient.profileKey, profileKey)) { if (senderAsRecipient.profileKey == null || !MessageDigest.isEqual(senderAsRecipient.profileKey, profileKey)) {
val database = DatabaseFactory.getRecipientDatabase(context) val database = DatabaseFactory.getRecipientDatabase(context)
database.setProfileKey(senderRecipient, profileKey) database.setProfileKey(senderAsRecipient, profileKey)
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderRecipient, url)) ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url))
} }
} else if (senderRecipient.profileAvatar.orEmpty().isNotEmpty()) { } else if (senderAsRecipient.profileAvatar.orEmpty().isNotEmpty()) {
// Unset the avatar if we had an avatar before and we're not friends with the person // Clear the profile picture if we had a profile picture before and we're not friends with the person
val threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(senderRecipient) val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(senderAsRecipient)
val friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId) val friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
if (friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) { if (friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) {
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderRecipient, "")) ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, ""))
} }
} }
} }
@ -193,25 +190,25 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
val isDuplicate = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID) != null val isDuplicate = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID) != null
if (isDuplicate) { return } if (isDuplicate) { return }
if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { return } if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { return }
val localNumber = TextSecurePreferences.getLocalNumber(context) val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val dataMessage = getDataMessage(message) val dataMessage = getDataMessage(message)
val transcript = SentTranscriptMessage(localNumber, dataMessage.timestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(localNumber, false)) val transcript = SentTranscriptMessage(userHexEncodedPublicKey, dataMessage.timestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(userHexEncodedPublicKey, false))
transcript.messageServerID = messageServerID transcript.messageServerID = messageServerID
if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) { if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) {
PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript) PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript)
} else { } else {
PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript)
} }
// If we got a message from our master device then make sure our mappings stay in sync // If we got a message from our master device then make sure our mapping stays in sync
val recipient = Recipient.from(context, Address.fromSerialized(message.hexEncodedPublicKey), false) val recipient = Recipient.from(context, Address.fromSerialized(message.hexEncodedPublicKey), false)
if (recipient.isOurMasterDevice && message.avatar != null) { if (recipient.isOurMasterDevice && message.profilePicture != null) {
val profileKey = message.avatar!!.profileKey val profileKey = message.profilePicture!!.profileKey
val url = message.avatar!!.url val url = message.profilePicture!!.url
if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, profileKey)) { if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, profileKey)) {
val database = DatabaseFactory.getRecipientDatabase(context) val database = DatabaseFactory.getRecipientDatabase(context)
database.setProfileKey(recipient, profileKey) database.setProfileKey(recipient, profileKey)
database.setProfileAvatar(recipient, url) database.setProfileAvatar(recipient, url)
ApplicationContext.getInstance(context).updatePublicChatProfileAvatarIfNeeded() ApplicationContext.getInstance(context).updatePublicChatProfilePictureIfNeeded()
} }
} }
} }
@ -219,29 +216,26 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
var uniqueDevices = setOf<String>() var uniqueDevices = setOf<String>()
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
val database = DatabaseFactory.getLokiAPIDatabase(context) val database = DatabaseFactory.getLokiAPIDatabase(context)
LokiStorageAPI.configure(false, userHexEncodedPublicKey, userPrivateKey, database) LokiFileServerAPI.configure(false, userHexEncodedPublicKey, userPrivateKey, database)
LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).bind { devices -> LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(userHexEncodedPublicKey).bind { devices ->
userDevices = devices userDevices = devices
api.getMessages(group.channel, group.server) api.getMessages(group.channel, group.server)
}.bind { messages -> }.bind { messages ->
if (messages.isNotEmpty()) { if (messages.isNotEmpty()) {
// We need to fetch device mappings for all the devices we don't have // We need to fetch the device mapping for any devices we don't have
uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet() uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet()
val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && LokiFileServerAPI.shared.hasDeviceLinkCacheExpired(hexEncodedPublicKey = it) }
if (devicesToUpdate.isNotEmpty()) { if (devicesToUpdate.isNotEmpty()) {
return@bind LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).then { messages } return@bind LokiFileServerAPI.shared.getDeviceLinks(devicesToUpdate.toSet()).then { messages }
} }
} }
Promise.of(messages) Promise.of(messages)
}.successBackground { }.successBackground {
// Get the set of primary device pubKeys FROM the secondary devices in uniqueDevices
val newDisplayNameUpdatees = uniqueDevices.mapNotNull { val newDisplayNameUpdatees = uniqueDevices.mapNotNull {
// This will return null if current device is primary // This will return null if the current device is a master device
// So if it's non-null then we know the device is a secondary device LokiDeviceLinkUtilities.getMasterHexEncodedPublicKey(it).get()
val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get()
primaryDevice
}.toSet() }.toSet()
// Fetch the display names of the primary devices // Fetch the display names of the master devices
displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees) displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees)
}.successBackground { messages -> }.successBackground { messages ->
// Process messages in the background // Process messages in the background

View File

@ -14,7 +14,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.messages.SignalServiceGroup import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.loki.api.LokiRSSFeed import org.whispersystems.signalservice.loki.api.LokiRSSFeed
import org.whispersystems.signalservice.loki.api.LokiRSSProxy import org.whispersystems.signalservice.loki.api.LokiRSSFeedProxy
import org.whispersystems.signalservice.loki.utilities.successBackground import org.whispersystems.signalservice.loki.utilities.successBackground
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.regex.Pattern import java.util.regex.Pattern
@ -48,7 +48,7 @@ class LokiRSSFeedPoller(private val context: Context, private val feed: LokiRSSF
} }
private fun poll() { private fun poll() {
LokiRSSProxy.fetch(feed.url).successBackground { xml -> LokiRSSFeedProxy.fetch(feed.url).successBackground { xml ->
val items = XMLParser(xml).call() val items = XMLParser(xml).call()
items.reversed().forEach { item -> items.reversed().forEach { item ->
val title = item.title ?: return@forEach val title = item.title ?: return@forEach

View File

@ -6,6 +6,11 @@ import android.support.v4.content.LocalBroadcastManager
class Broadcaster(private val context: Context) : org.whispersystems.signalservice.loki.utilities.Broadcaster { class Broadcaster(private val context: Context) : org.whispersystems.signalservice.loki.utilities.Broadcaster {
override fun broadcast(event: String) {
val intent = Intent(event)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
}
override fun broadcast(event: String, long: Long) { override fun broadcast(event: String, long: Long) {
val intent = Intent(event) val intent = Intent(event)
intent.putExtra("long", long) intent.putExtra("long", long)

View File

@ -53,7 +53,7 @@ object MentionUtilities {
} }
} }
val result = SpannableString(text) val result = SpannableString(text)
val userLinkedDeviceHexEncodedPublicKeys = DatabaseFactory.getLokiAPIDatabase(context).getPairingAuthorisations(userHexEncodedPublicKey).flatMap { listOf( it.primaryDevicePublicKey, it.secondaryDevicePublicKey ) }.toMutableSet() val userLinkedDeviceHexEncodedPublicKeys = DatabaseFactory.getLokiAPIDatabase(context).getDeviceLinks(userHexEncodedPublicKey).flatMap { listOf( it.masterHexEncodedPublicKey, it.slaveHexEncodedPublicKey ) }.toMutableSet()
userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey) userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey)
for (mention in mentions) { for (mention in mentions) {
if (!userLinkedDeviceHexEncodedPublicKeys.contains(mention.second)) { continue } if (!userLinkedDeviceHexEncodedPublicKeys.contains(mention.second)) { continue }

View File

@ -12,7 +12,7 @@ import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.getColorWithID import org.thoughtcrime.securesms.loki.getColorWithID
import org.thoughtcrime.securesms.loki.toPx import org.thoughtcrime.securesms.loki.toPx
class SeparatorView : RelativeLayout { class LabeledSeparatorView : RelativeLayout {
private val path = Path() private val path = Path()

View File

@ -3,13 +3,12 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context; import android.content.Context;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
public class PushMediaConstraints extends MediaConstraints { public class PushMediaConstraints extends MediaConstraints {
private static final int MAX_IMAGE_DIMEN_LOWMEM = 768; private static final int MAX_IMAGE_DIMEN_LOWMEM = 768;
private static final int MAX_IMAGE_DIMEN = 4096; private static final int MAX_IMAGE_DIMEN = 4096;
private static final int KB = 1024;
private static final int MB = 1024 * KB;
@Override @Override
public int getImageMaxWidth(Context context) { public int getImageMaxWidth(Context context) {
@ -23,26 +22,26 @@ public class PushMediaConstraints extends MediaConstraints {
@Override @Override
public int getImageMaxSize(Context context) { public int getImageMaxSize(Context context) {
return 6 * MB; return LokiFileServerAPI.Companion.getMaxFileSize();
} }
@Override @Override
public int getGifMaxSize(Context context) { public int getGifMaxSize(Context context) {
return 25 * MB; return LokiFileServerAPI.Companion.getMaxFileSize();
} }
@Override @Override
public int getVideoMaxSize(Context context) { public int getVideoMaxSize(Context context) {
return 100 * MB; return LokiFileServerAPI.Companion.getMaxFileSize();
} }
@Override @Override
public int getAudioMaxSize(Context context) { public int getAudioMaxSize(Context context) {
return 100 * MB; return LokiFileServerAPI.Companion.getMaxFileSize();
} }
@Override @Override
public int getDocumentMaxSize(Context context) { public int getDocumentMaxSize(Context context) {
return 100 * MB; return LokiFileServerAPI.Companion.getMaxFileSize();
} }
} }

View File

@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;

View File

@ -9,14 +9,10 @@ fun getOpenGroupDisplayName(recipient: Recipient, threadRecipient: Recipient, co
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient) val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient)
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
val hexEncodedPublicKey = recipient.address.toString() val hexEncodedPublicKey = recipient.address.toString()
val displayName: String? val displayName = if (publicChat != null) {
displayName = if (publicChat != null) {
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey) DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
} else { } else {
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey) DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
} }
if (displayName == null) { return displayName ?: hexEncodedPublicKey
return hexEncodedPublicKey
}
return displayName
} }

View File

@ -38,7 +38,7 @@ public class MessageSenderEventListener implements SignalServiceMessageSender.Ev
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sent, messageID, threadID); FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sent, messageID, threadID);
} }
@Override public void onFriendRequestSendingFail(long messageID, long threadID) { @Override public void onFriendRequestSendingFailed(long messageID, long threadID) {
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Failed, messageID, threadID); FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Failed, messageID, threadID);
} }
} }

View File

@ -60,7 +60,7 @@ import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.ContactTokenDetails;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
@ -99,7 +99,7 @@ public class MessageSender {
sendBackgroundMessage(context, contactHexEncodedPublicKey); sendBackgroundMessage(context, contactHexEncodedPublicKey);
// Go through the other devices and only send background messages if we're friends or we have received friend request // Go through the other devices and only send background messages if we're friends or we have received friend request
LokiStorageAPI.shared.getAllDevicePublicKeys(contactHexEncodedPublicKey).success(devices -> { LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(contactHexEncodedPublicKey).success(devices -> {
Util.runOnMain(() -> { Util.runOnMain(() -> {
for (String device : devices) { for (String device : devices) {
// Don't send message to the device we already have sent to // Don't send message to the device we already have sent to
@ -234,7 +234,7 @@ public class MessageSender {
final int ttl) { final int ttl) {
String ourPublicKey = TextSecurePreferences.getLocalNumber(context); String ourPublicKey = TextSecurePreferences.getLocalNumber(context);
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).success(devices -> { LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(ourPublicKey).success(devices -> {
Util.runOnMain(() -> { Util.runOnMain(() -> {
for (String device : devices) { for (String device : devices) {
// Don't send to ourselves // Don't send to ourselves
@ -306,8 +306,8 @@ public class MessageSender {
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
// Just send the message normally if it's a group message or we're sending to one of our devices // Just send the message normally if it's a group message or we're sending to one of our devices
String recipientPublicKey = recipient.getAddress().serialize(); String recipientHexEncodedPublicKey = recipient.getAddress().serialize();
if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey) || PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false)) { if (GeneralUtilitiesKt.isPublicChat(context, recipientHexEncodedPublicKey) || PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false)) {
if (type == MessageType.MEDIA) { if (type == MessageType.MEDIA) {
PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false); PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false);
} else { } else {
@ -318,16 +318,16 @@ public class MessageSender {
// If we get here then we are sending a message to a device that is not ours // If we get here then we are sending a message to a device that is not ours
boolean[] hasSentSyncMessage = { false }; boolean[] hasSentSyncMessage = { false };
MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey).success(devices -> { MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientHexEncodedPublicKey).success(devices -> {
int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet()); int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet());
Util.runOnMain(() -> { Util.runOnMain(() -> {
ArrayList<Job> jobs = new ArrayList<>(); ArrayList<Job> jobs = new ArrayList<>();
for (Map.Entry<String, Boolean> entry : devices.entrySet()) { for (Map.Entry<String, Boolean> entry : devices.entrySet()) {
String devicePublicKey = entry.getKey(); String deviceHexEncodedPublicKey = entry.getKey();
boolean isFriend = entry.getValue(); boolean isFriend = entry.getValue();
Address address = Address.fromSerialized(devicePublicKey); Address address = Address.fromSerialized(deviceHexEncodedPublicKey);
long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L; long messageIDToUse = recipientHexEncodedPublicKey.equals(deviceHexEncodedPublicKey) ? messageId : -1L;
if (isFriend) { if (isFriend) {
// Send a normal message if the user is friends with the recipient // Send a normal message if the user is friends with the recipient
@ -340,7 +340,7 @@ public class MessageSender {
} }
if (shouldSendSyncMessage) { hasSentSyncMessage[0] = true; } if (shouldSendSyncMessage) { hasSentSyncMessage[0] = true; }
} else { } else {
// Send friend requests to non friends. If the user is friends with any // Send friend requests to non-friends. If the user is friends with any
// of the devices then send out a default friend request message. // of the devices then send out a default friend request message.
boolean isFriendsWithAny = (friendCount > 0); boolean isFriendsWithAny = (friendCount > 0);
String defaultFriendRequestMessage = isFriendsWithAny ? "Please accept to enable messages to be synced across devices" : null; String defaultFriendRequestMessage = isFriendsWithAny ? "Please accept to enable messages to be synced across devices" : null;

View File

@ -1235,11 +1235,11 @@ public class TextSecurePreferences {
return getBooleanPreference(context, "database_reset_unpair", false); return getBooleanPreference(context, "database_reset_unpair", false);
} }
public static void setNeedsRevocationCheck(Context context, boolean needsCheck) { public static void setNeedsIsRevokedSlaveDeviceCheck(Context context, boolean value) {
setBooleanPreference(context, "needs_revocation", needsCheck); setBooleanPreference(context, "needs_revocation", value);
} }
public static boolean needsRevocationCheck(Context context) { public static boolean getNeedsIsRevokedSlaveDeviceCheck(Context context) {
return getBooleanPreference(context, "needs_revocation", false); return getBooleanPreference(context, "needs_revocation", false);
} }