mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 03:55:16 +00:00
Merge branch 'dev'
This commit is contained in:
commit
68785fe44f
@ -199,8 +199,8 @@ dependencies {
|
||||
implementation "com.github.ybq:Android-SpinKit:1.4.0"
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 35
|
||||
def canonicalVersionName = "1.0.1"
|
||||
def canonicalVersionCode = 36
|
||||
def canonicalVersionName = "1.0.2"
|
||||
|
||||
def postFixSize = 10
|
||||
def abiPostFix = ['armeabi-v7a' : 1,
|
||||
|
@ -1,5 +1,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">
|
||||
|
||||
<solid android:color="@color/compose_view_background" />
|
||||
|
||||
</shape>
|
@ -30,7 +30,7 @@
|
||||
android:textAlignment="center"
|
||||
android:text="Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code." />
|
||||
|
||||
<org.thoughtcrime.securesms.loki.redesign.views.SeparatorView
|
||||
<org.thoughtcrime.securesms.loki.redesign.views.LabeledSeparatorView
|
||||
android:id="@+id/separatorView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
|
@ -1,58 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
<LinearLayout
|
||||
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">
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/default_session_background"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<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_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:orientation="vertical">
|
||||
android:layout_marginLeft="@dimen/very_large_spacing"
|
||||
android:layout_marginRight="@dimen/very_large_spacing"
|
||||
android:textSize="@dimen/large_font_size"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text"
|
||||
android:text="Pick your display name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/titleTextView"
|
||||
style="@style/Signal.Text.Headline.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
android:text="@string/activity_display_name_title"
|
||||
android:textAlignment="center" />
|
||||
<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." />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitleTextView"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/activity_display_name_subtitle"
|
||||
android:textAlignment="center" />
|
||||
<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" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.LabeledEditText
|
||||
android:id="@+id/nameEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
app:labeledEditText_background="@color/loki_darkest_gray"
|
||||
app:labeledEditText_label="@string/activity_display_name_name_edit_text_label"/>
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/nextButton"
|
||||
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_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>
|
||||
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
@ -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>
|
@ -1,158 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
<LinearLayout
|
||||
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_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_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_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:orientation="vertical"
|
||||
android:animateLayoutChanges="true">
|
||||
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:id="@+id/titleTextView"
|
||||
style="@style/Signal.Text.Headline.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
android:text="@string/activity_key_pair_title"
|
||||
android:textAlignment="center" />
|
||||
<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 don’t give it to anyone. To restore your Session ID, launch Session and tap Continue your Session." />
|
||||
|
||||
<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:textAlignment="center" />
|
||||
<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/mnemonicTextView"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:alpha="0.8"
|
||||
android:textStyle="italic"
|
||||
android:textAlignment="center"
|
||||
tools:text="quick brown fox jump lazy dog" />
|
||||
<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" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/copyButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@color/transparent"
|
||||
android:textColor="@color/signal_primary"
|
||||
android:text="@string/activity_key_pair_copy_button_title"
|
||||
android:elevation="0dp"
|
||||
android:stateListAnimator="@null" />
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
<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>
|
||||
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
@ -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 don’t 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>
|
@ -107,7 +107,7 @@
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.loki.redesign.views.SeparatorView
|
||||
<org.thoughtcrime.securesms.loki.redesign.views.LabeledSeparatorView
|
||||
android:id="@+id/separatorView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
|
@ -38,7 +38,7 @@
|
||||
android:id="@+id/moderator_icon_image_view"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/icon_crown"
|
||||
android:src="@drawable/ic_crown"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
android:textAlignment="center"
|
||||
android:text="Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code." />
|
||||
|
||||
<org.thoughtcrime.securesms.loki.redesign.views.SeparatorView
|
||||
<org.thoughtcrime.securesms.loki.redesign.views.LabeledSeparatorView
|
||||
android:id="@+id/separatorView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
|
@ -23,7 +23,7 @@
|
||||
android:id="@+id/moderatorIconImageView"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@drawable/icon_crown"
|
||||
android:src="@drawable/ic_crown"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
|
@ -1618,13 +1618,13 @@
|
||||
<!-- Friend request view -->
|
||||
<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_incoming_pending_message">%1$s sent you a message 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_declined_message">You\'ve declined %1$s\'s message request</string>
|
||||
<string name="view_friend_request_incoming_expired_message">%1$s\'s message 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_accepted_message">%1$s accepted your message request</string>
|
||||
<string name="view_friend_request_outgoing_expired_message">Your message request to %1$s has expired</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 session 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 session request has expired</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 session request</string>
|
||||
<string name="view_friend_request_outgoing_expired_message">Your session request to %1$s has expired</string>
|
||||
<!-- Conversation activity -->
|
||||
<string name="activity_conversation_pending_friend_request_hint">Pending Friend Request…</string>
|
||||
<string name="activity_conversation_default_hint">New Message</string>
|
||||
|
@ -6,6 +6,7 @@
|
||||
<domain includeSubdomains="true">storage.seed1.loki.network</domain>
|
||||
<domain includeSubdomains="true">storage.seed2.loki.network</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-config>
|
||||
</network-security-config>
|
@ -96,14 +96,13 @@ import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiLongPoller;
|
||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiRSSFeed;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
@ -115,7 +114,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import dagger.ObjectGraph;
|
||||
import kotlin.Unit;
|
||||
import network.loki.messenger.BuildConfig;
|
||||
import okhttp3.Cache;
|
||||
|
||||
import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant;
|
||||
import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
|
||||
@ -184,18 +182,19 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||
// Loki - Set up P2P API if needed
|
||||
setUpP2PAPI();
|
||||
// Loki - Set the cache
|
||||
LokiDotNetAPI.setCache(new Cache(this.getCacheDir(), OK_HTTP_CACHE_SIZE));
|
||||
// Loki - Update device mappings
|
||||
if (setUpStorageAPIIfNeeded()) {
|
||||
LokiStorageAPI.Companion.getShared().updateUserDeviceMappings();
|
||||
if (TextSecurePreferences.needsRevocationCheck(this)) {
|
||||
checkNeedsRevocation();
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||
if (userHexEncodedPublicKey != null) {
|
||||
LokiFileServerAPI.Companion.getShared().getDeviceLinks(userHexEncodedPublicKey, true);
|
||||
if (TextSecurePreferences.getNeedsIsRevokedSlaveDeviceCheck(this)) {
|
||||
MultiDeviceUtilities.checkIsRevokedSlaveDevice(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Loki - Set up public chat manager
|
||||
lokiPublicChatManager = new LokiPublicChatManager(this);
|
||||
updatePublicChatProfileAvatarIfNeeded();
|
||||
updatePublicChatProfilePictureIfNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -206,6 +205,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
KeyCachingService.onAppForegrounded(this);
|
||||
// Loki - Start long polling if needed
|
||||
startLongPollingIfNeeded();
|
||||
// Loki - Start open group polling if needed
|
||||
lokiPublicChatManager.startPollersIfNeeded();
|
||||
}
|
||||
|
||||
@ -466,7 +466,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
boolean isDebugMode = BuildConfig.DEBUG;
|
||||
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
|
||||
LokiAPIDatabaseProtocol database = DatabaseFactory.getLokiAPIDatabase(this);
|
||||
LokiStorageAPI.Companion.configure(isDebugMode, userHexEncodedPublicKey, userPrivateKey, database);
|
||||
LokiFileServerAPI.Companion.configure(isDebugMode, userHexEncodedPublicKey, userPrivateKey, database);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -534,7 +534,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
|
||||
public void createRSSFeedsIfNeeded() {
|
||||
ArrayList<LokiRSSFeed> feeds = new ArrayList<>();
|
||||
feeds.add(lokiNewsFeed());
|
||||
// feeds.add(lokiNewsFeed());
|
||||
feeds.add(lokiMessengerUpdatesFeed());
|
||||
for (LokiRSSFeed feed : feeds) {
|
||||
boolean isFeedSetUp = TextSecurePreferences.isChatSetUp(this, feed.getId());
|
||||
@ -590,7 +590,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
if (lokiMessengerUpdatesFeedPoller != null) lokiMessengerUpdatesFeedPoller.startIfNeeded();
|
||||
}
|
||||
|
||||
public void updatePublicChatProfileAvatarIfNeeded() {
|
||||
public void updatePublicChatProfilePictureIfNeeded() {
|
||||
AsyncTask.execute(() -> {
|
||||
LokiPublicChatAPI publicChatAPI = null;
|
||||
try {
|
||||
@ -616,11 +616,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
}
|
||||
});
|
||||
}
|
||||
// endregion
|
||||
|
||||
public void checkNeedsRevocation() {
|
||||
MultiDeviceUtilities.checkForRevocation(this);
|
||||
}
|
||||
|
||||
public void checkNeedsDatabaseReset() {
|
||||
if (TextSecurePreferences.resetDatabase(this)) {
|
||||
@ -646,4 +641,5 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
this.startActivity(mainIntent);
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
@ -407,7 +407,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
||||
//Loki - Upload the profile photo here
|
||||
if (avatar != null) {
|
||||
Log.d("Loki", "Start uploading profile photo");
|
||||
LokiStorageAPI storageAPI = LokiStorageAPI.shared;
|
||||
LokiFileServerAPI storageAPI = LokiFileServerAPI.shared;
|
||||
LokiDotNetAPI.UploadResult result = storageAPI.uploadProfilePicture(storageAPI.getServer(), profileKey, avatar);
|
||||
Log.d("Loki", "Profile photo uploaded, the url is " + result.getUrl());
|
||||
TextSecurePreferences.setProfileAvatarUrl(context, result.getUrl());
|
||||
@ -422,7 +422,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
||||
ProfileKeyUtil.setEncodedProfileKey(context, newProfileKey);
|
||||
|
||||
// Update profile key on the public chat server
|
||||
ApplicationContext.getInstance(context).updatePublicChatProfileAvatarIfNeeded();
|
||||
ApplicationContext.getInstance(context).updatePublicChatProfilePictureIfNeeded();
|
||||
} catch (Exception e) {
|
||||
Log.d("Loki", "Failed to upload profile photo: " + e);
|
||||
return false;
|
||||
|
@ -190,7 +190,7 @@ public class DeviceListFragment extends ListFragment
|
||||
private void updateAddDeviceButtonVisibility() {
|
||||
if (addDeviceButton != null) {
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
||||
boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(getContext()).getPairingAuthorisations(userHexEncodedPublicKey).isEmpty();
|
||||
boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(getContext()).getDeviceLinks(userHexEncodedPublicKey).isEmpty();
|
||||
addDeviceButton.setVisibility(isDeviceLinkingEnabled ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
@ -9,14 +9,13 @@ import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
|
||||
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kotlin.Unit;
|
||||
@ -83,7 +82,7 @@ public class TypingStatusSender {
|
||||
}
|
||||
|
||||
private void sendTyping(long threadId, boolean typingStarted) {
|
||||
LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared();
|
||||
LokiFileServerAPI storageAPI = LokiFileServerAPI.Companion.getShared();
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
Recipient recipient = threadDatabase.getRecipientForThreadId(threadId);
|
||||
|
||||
@ -91,7 +90,7 @@ public class TypingStatusSender {
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted));
|
||||
return;
|
||||
}
|
||||
LokiStorageAPI.shared.getAllDevicePublicKeys(recipient.getAddress().serialize()).success(devices -> {
|
||||
LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(recipient.getAddress().serialize()).success(devices -> {
|
||||
for (String device : devices) {
|
||||
Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false);
|
||||
long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient);
|
||||
|
@ -232,10 +232,10 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.loki.api.DeviceLink;
|
||||
import org.whispersystems.signalservice.loki.api.LokiAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
|
||||
import org.whispersystems.signalservice.loki.messaging.Mention;
|
||||
@ -331,7 +331,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private MenuItem searchViewItem;
|
||||
private ProgressBar messageStatusProgressBar;
|
||||
private ImageView muteIndicatorImageView;
|
||||
private TextView actionBarSubtitleTextView;
|
||||
private TextView subtitleTextView;
|
||||
|
||||
private AttachmentTypeSelector attachmentTypeSelector;
|
||||
private AttachmentManager attachmentManager;
|
||||
@ -362,6 +362,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private final DynamicNoActionBarTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
// Message Status Bar
|
||||
private ArrayList<BroadcastReceiver> broadcastReceivers = new ArrayList<>();
|
||||
private String messageStatus = null;
|
||||
|
||||
@ -404,6 +405,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
registerMessageStatusObserver("sendingMessage");
|
||||
registerMessageStatusObserver("messageSent");
|
||||
registerMessageStatusObserver("messageFailed");
|
||||
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Toast.makeText(ConversationActivity.this, "Your clock is out of sync with the service node network.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
};
|
||||
broadcastReceivers.add(broadcastReceiver);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter("clockOutOfSync"));
|
||||
|
||||
initializeReceivers();
|
||||
initializeActionBar();
|
||||
@ -543,7 +553,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
initializeIdentityRecords();
|
||||
composeText.setTransport(sendButton.getSelectedTransport());
|
||||
|
||||
updateTitleTextView(glideRequests, recipient);
|
||||
updateTitleTextView(recipient);
|
||||
updateSubtitleTextView();
|
||||
setActionBarColor(recipient.getColor());
|
||||
setBlockedUserState(recipient, isSecureText, isDefaultSms);
|
||||
@ -636,7 +646,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
case GROUP_EDIT:
|
||||
recipient = Recipient.from(this, data.getParcelableExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA), true);
|
||||
recipient.addListener(this);
|
||||
updateTitleTextView(glideRequests, recipient);
|
||||
updateTitleTextView(recipient);
|
||||
updateSubtitleTextView();
|
||||
NotificationChannels.updateContactChannelName(this, recipient);
|
||||
setBlockedUserState(recipient, isSecureText, isDefaultSms);
|
||||
@ -740,9 +750,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
MenuInflater inflater = this.getMenuInflater();
|
||||
menu.clear();
|
||||
|
||||
boolean isLokiGroupChat = recipient.getAddress().isPublicChat() || recipient.getAddress().isRSSFeed();
|
||||
boolean isOpenGroupOrRSSFeed = recipient.getAddress().isPublicChat() || recipient.getAddress().isRSSFeed();
|
||||
|
||||
if (isSecureText && !isLokiGroupChat) {
|
||||
if (isSecureText && !isOpenGroupOrRSSFeed) {
|
||||
if (recipient.getExpireMessages() > 0) {
|
||||
inflater.inflate(R.menu.conversation_expiring_on, menu);
|
||||
|
||||
@ -762,7 +772,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (isSecureText) inflater.inflate(R.menu.conversation_callable_secure, menu);
|
||||
else inflater.inflate(R.menu.conversation_callable_insecure, menu);
|
||||
*/
|
||||
} else if (isGroupConversation() && !isLokiGroupChat) {
|
||||
} else if (isGroupConversation() && !isOpenGroupOrRSSFeed) {
|
||||
inflater.inflate(R.menu.conversation_group_options, menu);
|
||||
|
||||
if (!isPushGroupConversation()) {
|
||||
@ -1679,7 +1689,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
sessionRestoreBannerView = ViewUtil.findById(this, R.id.sessionRestoreBannerView);
|
||||
messageStatusProgressBar = ViewUtil.findById(this, R.id.messageStatusProgressBar);
|
||||
muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView);
|
||||
actionBarSubtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView);
|
||||
subtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView);
|
||||
|
||||
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
|
||||
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
|
||||
@ -1870,7 +1880,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
Log.i(TAG, "onModified(" + recipient.getAddress().serialize() + ")");
|
||||
Util.runOnMain(() -> {
|
||||
Log.i(TAG, "onModifiedRun(): " + recipient.getRegistered());
|
||||
updateTitleTextView(glideRequests, recipient);
|
||||
updateTitleTextView(recipient);
|
||||
updateSubtitleTextView();
|
||||
// titleView.setVerified(identityRecords.isVerified());
|
||||
setBlockedUserState(recipient, isSecureText, isDefaultSms);
|
||||
@ -2290,7 +2300,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (threadID != this.threadId) {
|
||||
Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID);
|
||||
if (threadRecipient != null && !threadRecipient.isGroupRecipient()) {
|
||||
LokiStorageAPI.shared.getAllDevicePublicKeys(threadRecipient.getAddress().serialize()).success(devices -> {
|
||||
LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(threadRecipient.getAddress().serialize()).success(devices -> {
|
||||
// We should update our input if this thread is a part of the other threads device
|
||||
if (devices.contains(recipient.getAddress().serialize())) {
|
||||
this.updateInputPanel();
|
||||
@ -2313,33 +2323,29 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private void updateInputPanel() {
|
||||
/*
|
||||
isFriendsWithAnyDevice caches whether we are friends with any of the other users device.
|
||||
isFriendsWithAnyDevice reflects whether we are friends with any of the other user's devices.
|
||||
|
||||
This stops the case where the input panel disables and enables rapidly.
|
||||
- This can occur when we are not friends with the current thread BUT multi-device tells us that we are friends with another one of their devices.
|
||||
This fixes the case where the input panel disables and enables rapidly, which can occur when we are
|
||||
not friends with the current thread BUT multi device tells us that we are friends with another one of their devices.
|
||||
*/
|
||||
if (recipient.isGroupRecipient() || isNoteToSelf() || isFriendsWithAnyDevice) {
|
||||
setInputPanelEnabled(true);
|
||||
return;
|
||||
}
|
||||
if (recipient.isGroupRecipient() || isNoteToSelf() || isFriendsWithAnyDevice) { setInputPanelEnabled(true); return; }
|
||||
|
||||
// It could take a while before our promise resolves, so we assume the best case
|
||||
// Disable the input panel if a friend request is pending
|
||||
LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId);
|
||||
boolean isPending = friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED;
|
||||
setInputPanelEnabled(!isPending);
|
||||
|
||||
// We should always have the input panel enabled if we are friends with the current user
|
||||
isFriendsWithAnyDevice = friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
|
||||
// Always enable the input panel if we are friends with the current user
|
||||
isFriendsWithAnyDevice = (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS);
|
||||
|
||||
// Multi-device input logic
|
||||
if (!isFriendsWithAnyDevice) {
|
||||
// We should enable the input if we don't have any pending friend requests OR we are friends with a linked device
|
||||
MultiDeviceUtilities.hasPendingFriendRequestWithAnyLinkedDevice(this, recipient).success(hasPendingRequests -> {
|
||||
// Enable the input panel if we don't have any pending friend requests OR we are friends with one of the user's linked devices
|
||||
MultiDeviceUtilities.hasPendingFriendRequestWithAnyLinkedDevice(this, recipient).success( hasPendingRequests -> {
|
||||
if (!hasPendingRequests) {
|
||||
setInputPanelEnabled(true);
|
||||
} else {
|
||||
MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient).success(isFriends -> {
|
||||
// If we are friend with any of the other devices then we want to make sure the input panel is always enabled for the duration of this conversation
|
||||
MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient).success( isFriends -> {
|
||||
// Enable the input panel if we're friends with any of the user's devices
|
||||
isFriendsWithAnyDevice = isFriends;
|
||||
setInputPanelEnabled(isFriends);
|
||||
return Unit.INSTANCE;
|
||||
@ -2353,7 +2359,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private void setInputPanelEnabled(boolean enabled) {
|
||||
Util.runOnMain(() -> {
|
||||
updateToggleButtonState();
|
||||
String hint = enabled ? "Message" : "Pending message request";
|
||||
String hint = enabled ? "Message" : "Pending session request";
|
||||
inputPanel.setHint(hint);
|
||||
inputPanel.setEnabled(enabled);
|
||||
if (enabled) {
|
||||
@ -2407,13 +2413,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
sendTextMessage(forceSms, expiresIn, subscriptionId, initiating);
|
||||
}
|
||||
} catch (RecipientFormattingException ex) {
|
||||
Toast.makeText(ConversationActivity.this,
|
||||
R.string.ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation,
|
||||
Toast.LENGTH_LONG).show();
|
||||
Log.w(TAG, ex);
|
||||
} catch (InvalidMessageException ex) {
|
||||
// Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_message_is_empty_exclamation,
|
||||
// Toast.LENGTH_SHORT).show();
|
||||
Log.w(TAG, ex);
|
||||
}
|
||||
}
|
||||
@ -2565,7 +2566,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void updateToggleButtonState() {
|
||||
// Don't allow attachments if we're not friends with any device
|
||||
// Don't allow attachments if we're not friends with any of the user's devices
|
||||
if (!isNoteToSelf() && !recipient.isGroupRecipient() && !isFriendsWithAnyDevice) {
|
||||
buttonToggle.display(sendButton);
|
||||
quickAttachmentToggle.hide();
|
||||
@ -3160,13 +3161,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
// region Loki
|
||||
private void updateTitleTextView(GlideRequests glide, Recipient recipient) {
|
||||
private void updateTitleTextView(Recipient recipient) {
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||
List<PairingAuthorisation> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getPairingAuthorisations(userHexEncodedPublicKey);
|
||||
Set<DeviceLink> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userHexEncodedPublicKey);
|
||||
HashSet<String> userLinkedDeviceHexEncodedPublicKeys = new HashSet<>();
|
||||
for (PairingAuthorisation deviceLink : deviceLinks) {
|
||||
userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getPrimaryDevicePublicKey().toLowerCase());
|
||||
userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getSecondaryDevicePublicKey().toLowerCase());
|
||||
for (DeviceLink deviceLink : deviceLinks) {
|
||||
userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getMasterHexEncodedPublicKey().toLowerCase());
|
||||
userLinkedDeviceHexEncodedPublicKeys.add(deviceLink.getSlaveHexEncodedPublicKey().toLowerCase());
|
||||
}
|
||||
userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey.toLowerCase());
|
||||
if (recipient == null) {
|
||||
@ -3174,45 +3175,46 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
} else if (userLinkedDeviceHexEncodedPublicKeys.contains(recipient.getAddress().toString().toLowerCase())) {
|
||||
titleTextView.setText("Note to Self");
|
||||
} else {
|
||||
titleTextView.setText((recipient.getName() == null || recipient.getName().isEmpty()) ? recipient.getAddress().toString() : recipient.getName());
|
||||
boolean hasName = (recipient.getName() != null && !recipient.getName().isEmpty());
|
||||
titleTextView.setText(hasName ? recipient.getName() : recipient.getAddress().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSubtitleTextView() {
|
||||
muteIndicatorImageView.setVisibility(View.GONE);
|
||||
actionBarSubtitleTextView.setVisibility(View.VISIBLE);
|
||||
subtitleTextView.setVisibility(View.VISIBLE);
|
||||
if (messageStatus != null) {
|
||||
switch (messageStatus) {
|
||||
case "calculatingPoW": actionBarSubtitleTextView.setText("Encrypting message"); break;
|
||||
case "contactingNetwork": actionBarSubtitleTextView.setText("Tracing a path"); break;
|
||||
case "sendingMessage": actionBarSubtitleTextView.setText("Sending message"); break;
|
||||
case "messageSent": actionBarSubtitleTextView.setText("Message sent securely"); break;
|
||||
case "messageFailed": actionBarSubtitleTextView.setText("Message failed to send"); break;
|
||||
case "calculatingPoW": subtitleTextView.setText("Encrypting message"); break;
|
||||
case "contactingNetwork": subtitleTextView.setText("Tracing a path"); break;
|
||||
case "sendingMessage": subtitleTextView.setText("Sending message"); break;
|
||||
case "messageSent": subtitleTextView.setText("Message sent securely"); break;
|
||||
case "messageFailed": subtitleTextView.setText("Message failed to send"); break;
|
||||
}
|
||||
} else if (recipient.isMuted()) {
|
||||
muteIndicatorImageView.setVisibility(View.VISIBLE);
|
||||
actionBarSubtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()));
|
||||
subtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()));
|
||||
} else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) {
|
||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
||||
if (publicChat != null) {
|
||||
Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer());
|
||||
if (userCount == null) { userCount = 0; }
|
||||
if (userCount >= 200) {
|
||||
actionBarSubtitleTextView.setText("200+ members");
|
||||
subtitleTextView.setText("200+ members");
|
||||
} else {
|
||||
actionBarSubtitleTextView.setText(userCount + " members");
|
||||
subtitleTextView.setText(userCount + " members");
|
||||
}
|
||||
} else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) {
|
||||
actionBarSubtitleTextView.setText(recipient.getAddress().toString());
|
||||
subtitleTextView.setText(recipient.getAddress().toString());
|
||||
} else {
|
||||
actionBarSubtitleTextView.setVisibility(View.GONE);
|
||||
subtitleTextView.setVisibility(View.GONE);
|
||||
}
|
||||
} else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) {
|
||||
actionBarSubtitleTextView.setText(recipient.getAddress().toString());
|
||||
subtitleTextView.setText(recipient.getAddress().toString());
|
||||
} else {
|
||||
actionBarSubtitleTextView.setVisibility(View.GONE);
|
||||
subtitleTextView.setVisibility(View.GONE);
|
||||
}
|
||||
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension((actionBarSubtitleTextView.getVisibility() == View.GONE) ? R.dimen.very_large_font_size : R.dimen.large_font_size));
|
||||
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension((subtitleTextView.getVisibility() == View.GONE) ? R.dimen.very_large_font_size : R.dimen.large_font_size));
|
||||
}
|
||||
|
||||
private void setMessageStatusProgressAnimatedIfPossible(int progress) {
|
||||
@ -3284,18 +3286,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void acceptFriendRequest(@NotNull MessageRecord friendRequest) {
|
||||
// Send the accept to the original friend request thread id
|
||||
// Send the accept to the original friend request thread ID
|
||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(this);
|
||||
long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id);
|
||||
long threadId = originalThreadID < 0 ? this.threadId : originalThreadID;
|
||||
|
||||
Recipient contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId);
|
||||
long threadID = originalThreadID < 0 ? this.threadId : originalThreadID;
|
||||
Recipient contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID);
|
||||
Address address = contact.getAddress();
|
||||
String contactPubKey = address.serialize();
|
||||
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS);
|
||||
String contactHexEncodedPublicKey = address.serialize();
|
||||
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
|
||||
lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
|
||||
DatabaseFactory.getRecipientDatabase(this).setProfileSharing(contact, true);
|
||||
MessageSender.sendBackgroundMessageToAllDevices(this, contactPubKey);
|
||||
MessageSender.sendBackgroundMessageToAllDevices(this, contactHexEncodedPublicKey);
|
||||
MessageSender.syncContact(this, address);
|
||||
updateInputPanel();
|
||||
}
|
||||
@ -3304,10 +3305,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
public void rejectFriendRequest(@NotNull MessageRecord friendRequest) {
|
||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(this);
|
||||
long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id);
|
||||
long threadId = originalThreadID < 0 ? this.threadId : originalThreadID;
|
||||
|
||||
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.NONE);
|
||||
String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString();
|
||||
long threadID = originalThreadID < 0 ? this.threadId : originalThreadID;
|
||||
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.NONE);
|
||||
String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID).getAddress().toString();
|
||||
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(contactID);
|
||||
updateInputPanel();
|
||||
}
|
||||
@ -3315,20 +3315,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
public boolean isNoteToSelf() {
|
||||
return TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize());
|
||||
}
|
||||
// endregion
|
||||
|
||||
public void restoreSession() {
|
||||
// Loki - User clicked restore session
|
||||
if (recipient.isGroupRecipient()) { return; }
|
||||
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(this);
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(this);
|
||||
Set<String> devices = lokiThreadDatabase.getSessionRestoreDevices(threadId);
|
||||
for (String device : devices) { MessageSender.sendRestoreSessionMessage(this, device); }
|
||||
long messageId = smsDatabase.insertMessageOutbox(threadId, new OutgoingTextMessage(recipient,"", 0, 0), false, System.currentTimeMillis(), null);
|
||||
if (messageId > -1) {
|
||||
smsDatabase.markAsLokiSessionRestoreSent(messageId);
|
||||
long messageID = smsDatabase.insertMessageOutbox(threadId, new OutgoingTextMessage(recipient,"", 0, 0), false, System.currentTimeMillis(), null);
|
||||
if (messageID > -1) {
|
||||
smsDatabase.markAsLokiSessionRestoreSent(messageID);
|
||||
}
|
||||
lokiThreadDatabase.removeAllSessionRestoreDevices(threadId);
|
||||
updateSessionRestoreBanner();
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
@ -10,11 +10,11 @@ import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiSessionDatabaseProtocol;
|
||||
import org.whispersystems.libsignal.state.SessionStore;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TextSecureSessionStore implements LokiSessionDatabaseProtocol {
|
||||
public class TextSecureSessionStore implements SessionStore {
|
||||
|
||||
private static final String TAG = TextSecureSessionStore.class.getSimpleName();
|
||||
|
||||
|
@ -135,7 +135,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(LokiAPIDatabase.getCreateGroupChatAuthTokenTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateDeviceLinkTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateUserCountTableCommand());
|
||||
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
|
||||
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
|
||||
@ -518,7 +518,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV3) {
|
||||
db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateDeviceLinkTableCommand());
|
||||
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
||||
|
||||
db.execSQL("ALTER TABLE groups ADD COLUMN avatar_url TEXT");
|
||||
|
@ -11,13 +11,14 @@ import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities;
|
||||
import org.thoughtcrime.securesms.util.AsyncLoader;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
|
||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class DeviceListLoader extends AsyncLoader<List<Device>> {
|
||||
|
||||
@ -33,7 +34,7 @@ public class DeviceListLoader extends AsyncLoader<List<Device>> {
|
||||
public List<Device> loadInBackground() {
|
||||
try {
|
||||
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();
|
||||
Collections.sort(devices, new DeviceComparator());
|
||||
return devices;
|
||||
|
@ -7,7 +7,6 @@ import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.CreateProfileActivity;
|
||||
import org.thoughtcrime.securesms.DeviceListFragment;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
|
||||
import org.thoughtcrime.securesms.gcm.FcmService;
|
||||
@ -48,6 +47,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation;
|
||||
import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob;
|
||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
|
||||
@ -160,7 +160,7 @@ public class SignalCommunicationModule {
|
||||
DatabaseFactory.getLokiThreadDatabase(context),
|
||||
DatabaseFactory.getLokiMessageDatabase(context),
|
||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context),
|
||||
new TextSecureSessionStore(context),
|
||||
new LokiSessionResetImplementation(context),
|
||||
DatabaseFactory.getLokiUserDatabase(context),
|
||||
((ApplicationContext)context.getApplicationContext()).broadcaster);
|
||||
} else {
|
||||
|
@ -36,7 +36,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
|
||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -318,7 +318,7 @@ public class GroupMessageProcessor {
|
||||
try {
|
||||
String masterHexEncodedPublicKey = hexEncodedPublicKey.equalsIgnoreCase(ourPublicKey)
|
||||
? TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
: PromiseUtil.timeout(LokiStorageAPI.shared.getPrimaryDevicePublicKey(hexEncodedPublicKey), 5000).get();
|
||||
: PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey), 5000).get();
|
||||
return masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : hexEncodedPublicKey;
|
||||
} catch (Exception e) {
|
||||
return hexEncodedPublicKey;
|
||||
@ -329,7 +329,7 @@ public class GroupMessageProcessor {
|
||||
String ourNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
for (String member : members) {
|
||||
// Make sure we have session with all of the members secondary devices
|
||||
LokiStorageAPI.shared.getAllDevicePublicKeys(member).success(devices -> {
|
||||
LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(member).success(devices -> {
|
||||
if (devices.contains(ourNumber)) { return Unit.INSTANCE; }
|
||||
for (String device : devices) {
|
||||
SignalProtocolAddress protocolAddress = new SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||
|
@ -27,7 +27,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -68,13 +68,13 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.FriendRequestHandler;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities;
|
||||
import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyBundleDatabase;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyRecordDatabase;
|
||||
import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation;
|
||||
import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
|
||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||
import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyBundleDatabase;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
|
||||
@ -101,6 +101,8 @@ import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.loki.LokiSessionResetProtocol;
|
||||
import org.whispersystems.libsignal.loki.LokiSessionResetStatus;
|
||||
import org.whispersystems.libsignal.state.PreKeyBundle;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
@ -129,15 +131,14 @@ import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOper
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.loki.api.DeviceLink;
|
||||
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession;
|
||||
import org.whispersystems.signalservice.loki.api.LokiAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
|
||||
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus;
|
||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
||||
|
||||
import java.io.InputStream;
|
||||
@ -270,9 +271,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
||||
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
|
||||
LokiPreKeyRecordDatabase lokiPreKeyRecordDatabase = DatabaseFactory.getLokiPreKeyRecordDatabase(context);
|
||||
LokiSessionResetProtocol lokiSessionResetProtocol = new LokiSessionResetImplementation(context);
|
||||
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
|
||||
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiThreadDatabase, lokiPreKeyRecordDatabase, UnidentifiedAccessUtil.getCertificateValidator());
|
||||
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiSessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
|
||||
|
||||
SignalServiceContent content = cipher.decrypt(envelope);
|
||||
|
||||
@ -282,21 +283,21 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (shouldIgnore(content)) {
|
||||
Log.i(TAG, "Ignoring message.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Loki - Handle friend request acceptance if needed
|
||||
acceptFriendRequestIfNeeded(content);
|
||||
if (!content.isFriendRequest() && !isGroupChatMessage(content)) {
|
||||
becomeFriendsWithContactIfNeeded(content.getSender(), true, false);
|
||||
}
|
||||
|
||||
// Loki - Session requests
|
||||
// Loki - Handle session request if needed
|
||||
handleSessionRequestIfNeeded(content);
|
||||
|
||||
// Loki - Store pre key bundle
|
||||
// We shouldn't store it if it's a pairing message
|
||||
if (!content.getPairingAuthorisation().isPresent()) {
|
||||
// Loki - Store pre key bundle if needed
|
||||
if (!content.getDeviceLink().isPresent()) {
|
||||
storePreKeyBundleIfNeeded(content);
|
||||
}
|
||||
|
||||
@ -310,35 +311,35 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
// Loki - Store the sender display name if needed
|
||||
Optional<String> rawSenderDisplayName = content.senderDisplayName;
|
||||
if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) {
|
||||
// If we got a name from our primary device then we set our profile name to match it
|
||||
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
if (ourPrimaryDevice != null && content.getSender().equals(ourPrimaryDevice)) {
|
||||
// If we got a name from our master device then set our display name to match
|
||||
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
if (ourMasterDevice != null && content.getSender().equals(ourMasterDevice)) {
|
||||
TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get());
|
||||
}
|
||||
|
||||
// If we receive a message from our device then don't set the display name in the database (as we probably have a alias set for them)
|
||||
MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success(isOneOfOurDevice -> {
|
||||
if (!isOneOfOurDevice) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); }
|
||||
MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success( isOneOfOurDevices -> {
|
||||
if (!isOneOfOurDevices) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); }
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
|
||||
if (content.getPairingAuthorisation().isPresent()) {
|
||||
handlePairingMessage(content.getPairingAuthorisation().get(), content);
|
||||
if (content.getDeviceLink().isPresent()) {
|
||||
handleDeviceLinkMessage(content.getDeviceLink().get(), content);
|
||||
} else if (content.getDataMessage().isPresent()) {
|
||||
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
||||
|
||||
if (!content.isFriendRequest() && message.isUnpairingRequest()) {
|
||||
// Make sure we got the request from our primary device
|
||||
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) {
|
||||
if (!content.isFriendRequest() && message.isUnlinkingRequest()) {
|
||||
// Make sure we got the request from our master device
|
||||
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
if (ourMasterDevice != null && ourMasterDevice.equals(content.getSender())) {
|
||||
TextSecurePreferences.setDatabaseResetFromUnpair(context, true);
|
||||
MultiDeviceUtilities.checkForRevocation(context);
|
||||
MultiDeviceUtilities.checkIsRevokedSlaveDevice(context);
|
||||
}
|
||||
} else {
|
||||
// Loki - Don't process session restore message any further
|
||||
if (message.isSessionRestore() || message.isSessionRequest()) { return; }
|
||||
if (message.isSessionRestorationRequest() || message.isSessionRequest()) { return; }
|
||||
|
||||
if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
||||
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
||||
@ -362,6 +363,18 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
handleNeedsDeliveryReceipt(content, message);
|
||||
}
|
||||
|
||||
// If we received a friend request, but we were already friends with the user, reset the session
|
||||
if (content.isFriendRequest() && !message.isGroupMessage()) {
|
||||
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
|
||||
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
|
||||
resetSession(content.getSender());
|
||||
// Let our other devices know that we have reset the session
|
||||
MessageSender.syncContact(context, sender.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
// Loki - Handle friend request logic if needed
|
||||
updateFriendRequestStatusIfNeeded(content, message);
|
||||
}
|
||||
@ -375,7 +388,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp());
|
||||
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
|
||||
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
|
||||
else if (syncMessage.getContacts().isPresent()) handleSynchronizeContactMessage(syncMessage.getContacts().get());
|
||||
else if (syncMessage.getContacts().isPresent()) handleContactSyncMessage(syncMessage.getContacts().get());
|
||||
else Log.w(TAG, "Contains no known sync types...");
|
||||
} else if (content.getCallMessage().isPresent()) {
|
||||
Log.i(TAG, "Got call message...");
|
||||
@ -402,11 +415,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
if (envelope.isPreKeySignalMessage()) {
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob());
|
||||
}
|
||||
|
||||
// Loki - Handle session reset logic
|
||||
if (!content.isFriendRequest()) {
|
||||
cipher.handleSessionResetRequestIfNeeded(content, cipher.getSessionStatus(content));
|
||||
}
|
||||
} catch (ProtocolInvalidVersionException e) {
|
||||
Log.w(TAG, e);
|
||||
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||
@ -539,19 +547,19 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
|
||||
if (threadId != null) {
|
||||
resetSession(content.getSender(), threadId);
|
||||
resetSession(content.getSender());
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetSession(String hexEncodedPublicKey, long threadId) {
|
||||
private void resetSession(String hexEncodedPublicKey) {
|
||||
TextSecureSessionStore sessionStore = new TextSecureSessionStore(context);
|
||||
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
|
||||
|
||||
Log.d("Loki", "Received a session reset request from: " + hexEncodedPublicKey + "; archiving the session.");
|
||||
|
||||
sessionStore.archiveAllSessions(hexEncodedPublicKey);
|
||||
lokiThreadDatabase.setSessionResetStatus(threadId, LokiThreadSessionResetStatus.REQUEST_RECEIVED);
|
||||
lokiThreadDatabase.setSessionResetStatus(hexEncodedPublicKey, LokiSessionResetStatus.REQUEST_RECEIVED);
|
||||
|
||||
Log.d("Loki", "Sending a ping back to " + hexEncodedPublicKey + ".");
|
||||
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
|
||||
@ -593,7 +601,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
{
|
||||
GroupMessageProcessor.process(context, content, message, false);
|
||||
|
||||
if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(content, message).getExpireMessages()) {
|
||||
if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getRecipientForMessage(content, message).getExpireMessages()) {
|
||||
handleExpirationUpdate(content, message, Optional.absent());
|
||||
}
|
||||
|
||||
@ -619,7 +627,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
{
|
||||
try {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Recipient recipient = getMessageDestination(content, message);
|
||||
Recipient recipient = getRecipientForMessage(content, message);
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromSerialized(content.getSender()),
|
||||
message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000L, true,
|
||||
@ -670,45 +678,44 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeContactMessage(@NonNull ContactsMessage contactsMessage) {
|
||||
if (contactsMessage.getContactsStream().isStream()) {
|
||||
Log.d("Loki", "Received contact sync message");
|
||||
private void handleContactSyncMessage(@NonNull ContactsMessage contactsMessage) {
|
||||
if (!contactsMessage.getContactsStream().isStream()) { return; }
|
||||
Log.d("Loki", "Received contact sync message.");
|
||||
|
||||
try {
|
||||
InputStream in = contactsMessage.getContactsStream().asStream().getInputStream();
|
||||
DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in);
|
||||
List<DeviceContact> devices = contactsInputStream.readAll();
|
||||
for (DeviceContact deviceContact : devices) {
|
||||
// Check if we have the contact as a friend and that we're not trying to sync our own device
|
||||
String pubKey = deviceContact.getNumber();
|
||||
Address address = Address.fromSerialized(pubKey);
|
||||
if (!address.isPhone() || address.toPhoneString().equals(TextSecurePreferences.getLocalNumber(context))) { continue; }
|
||||
try {
|
||||
InputStream in = contactsMessage.getContactsStream().asStream().getInputStream();
|
||||
DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in);
|
||||
List<DeviceContact> deviceContacts = contactsInputStream.readAll();
|
||||
for (DeviceContact deviceContact : deviceContacts) {
|
||||
// Check if we have the contact as a friend and that we're not trying to sync our own device
|
||||
String hexEncodedPublicKey = deviceContact.getNumber();
|
||||
Address address = Address.fromSerialized(hexEncodedPublicKey);
|
||||
if (!address.isPhone() || address.toPhoneString().equals(TextSecurePreferences.getLocalNumber(context))) { continue; }
|
||||
|
||||
/*
|
||||
If we're not friends with the contact we received or our friend request expired then we should send them a friend request
|
||||
otherwise if we have received a friend request with from them then we should automatically accept the friend request
|
||||
*/
|
||||
Recipient recipient = Recipient.from(context, address, false);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||
LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId);
|
||||
if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
||||
MessageSender.sendBackgroundFriendRequest(context, pubKey, "Please accept to enable messages to be synced across devices");
|
||||
Log.d("Loki", "Sent friend request to " + pubKey);
|
||||
} else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
|
||||
// Accept the incoming friend request
|
||||
becomeFriendsWithContact(pubKey, false, false);
|
||||
// Send them an accept message back
|
||||
MessageSender.sendBackgroundMessage(context, pubKey);
|
||||
Log.d("Loki", "Became friends with " + deviceContact.getNumber());
|
||||
}
|
||||
|
||||
// TODO: Handle blocked - If user is not blocked then we should do the friend request logic otherwise add them to our block list
|
||||
// TODO: Handle expiration timer - Update expiration timer?
|
||||
// TODO: Handle avatar - Download and set avatar?
|
||||
/*
|
||||
If we're not friends with the contact we received or our friend request expired then we should send them a friend request.
|
||||
Otherwise, if we have received a friend request from them, automatically accept the friend request.
|
||||
*/
|
||||
Recipient recipient = Recipient.from(context, address, false);
|
||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||
LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
|
||||
if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
||||
MessageSender.sendBackgroundFriendRequest(context, hexEncodedPublicKey, "Please accept to enable messages to be synced across devices");
|
||||
Log.d("Loki", "Sent friend request to " + hexEncodedPublicKey);
|
||||
} else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
|
||||
// Accept the incoming friend request
|
||||
becomeFriendsWithContactIfNeeded(hexEncodedPublicKey, false, false);
|
||||
// Send them an accept message back
|
||||
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
|
||||
Log.d("Loki", "Became friends with " + deviceContact.getNumber());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d("Loki", "Failed to sync contact: " + e);
|
||||
|
||||
// TODO: Handle blocked - If user is not blocked then we should do the friend request logic otherwise add them to our block list
|
||||
// TODO: Handle expiration timer - Update expiration timer?
|
||||
// TODO: Handle avatar - Download and set avatar?
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d("Loki", "Failed to sync contact: " + e + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@ -751,7 +758,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
|
||||
}
|
||||
|
||||
// Loki - If we received a sync message from our master device then we need to extract the avatar url
|
||||
// Loki - If we received a sync message from our master device then we need to extract the profile picture url
|
||||
if (isSenderMasterDevice) {
|
||||
handleProfileKey(content, message.getMessage());
|
||||
}
|
||||
@ -841,27 +848,27 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
@NonNull Optional<Long> messageServerIDOrNull)
|
||||
throws StorageFailedException
|
||||
{
|
||||
Recipient originalRecipient = getMessageDestination(content, message);
|
||||
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
|
||||
Recipient originalRecipient = getRecipientForMessage(content, message);
|
||||
Recipient masterRecipient = getMasterRecipientForMessage(content, message);
|
||||
|
||||
notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice());
|
||||
notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice());
|
||||
|
||||
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
|
||||
Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts());
|
||||
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
|
||||
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.isGroupMessage()) {
|
||||
sender = getPrimaryDeviceRecipient(content.getSender()).getAddress();
|
||||
sender = getMasterRecipient(content.getSender()).getAddress();
|
||||
}
|
||||
|
||||
// Ignore messages from ourselves
|
||||
if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; }
|
||||
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(sender, message.getTimestamp(), -1,
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(sender, message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
|
||||
quote, sharedContacts, linkPreviews, sticker);
|
||||
|
||||
@ -905,12 +912,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
|
||||
// Loki - Run db updates in the background, we should look into fixing this in the future
|
||||
// Loki - Run database updates in the background, we should look into fixing this in the future
|
||||
AsyncTask.execute(() -> {
|
||||
// Loki - Store message server ID
|
||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||
|
||||
// Loki - Update mapping of message to original thread id
|
||||
// Loki - Update mapping of message to original thread ID
|
||||
if (insertResult.isPresent()) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||
@ -1028,10 +1035,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
@NonNull Optional<Long> messageServerIDOrNull)
|
||||
throws StorageFailedException
|
||||
{
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
String body = message.getBody().isPresent() ? message.getBody().get() : "";
|
||||
Recipient originalRecipient = getMessageDestination(content, message);
|
||||
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
String body = message.getBody().isPresent() ? message.getBody().get() : "";
|
||||
Recipient originalRecipient = getRecipientForMessage(content, message);
|
||||
Recipient masterRecipient = getMasterRecipientForMessage(content, message);
|
||||
|
||||
if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) {
|
||||
handleExpirationUpdate(content, message, Optional.absent());
|
||||
@ -1042,26 +1049,26 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) {
|
||||
threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second;
|
||||
} else {
|
||||
notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice());
|
||||
notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice());
|
||||
|
||||
Address sender = primaryDeviceRecipient.getAddress();
|
||||
Address sender = masterRecipient.getAddress();
|
||||
|
||||
// If message is from group then we need to map it to get the sender of the message
|
||||
if (message.isGroupMessage()) {
|
||||
sender = getPrimaryDeviceRecipient(content.getSender()).getAddress();
|
||||
sender = getMasterRecipient(content.getSender()).getAddress();
|
||||
}
|
||||
|
||||
// Ignore messages from ourselves
|
||||
if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; }
|
||||
|
||||
IncomingTextMessage _textMessage = new IncomingTextMessage(sender,
|
||||
content.getSenderDevice(),
|
||||
message.getTimestamp(), body,
|
||||
message.getGroupInfo(),
|
||||
message.getExpiresInSeconds() * 1000L,
|
||||
content.isNeedsReceipt());
|
||||
IncomingTextMessage tm = new IncomingTextMessage(sender,
|
||||
content.getSenderDevice(),
|
||||
message.getTimestamp(), body,
|
||||
message.getGroupInfo(),
|
||||
message.getExpiresInSeconds() * 1000L,
|
||||
content.isNeedsReceipt());
|
||||
|
||||
IncomingEncryptedMessage textMessage = new IncomingEncryptedMessage(_textMessage, body);
|
||||
IncomingEncryptedMessage textMessage = new IncomingEncryptedMessage(tm, body);
|
||||
|
||||
// Ignore the message if the body is empty
|
||||
if (textMessage.getMessageBody().length() == 0) { return; }
|
||||
@ -1079,7 +1086,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
|
||||
// Loki - Run db updates in background, we should look into fixing this in the future
|
||||
// Loki - Run database updates in background, we should look into fixing this in the future
|
||||
AsyncTask.execute(() -> {
|
||||
if (insertResult.isPresent()) {
|
||||
InsertResult result = insertResult.get();
|
||||
@ -1090,7 +1097,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
// Loki - Store message server ID
|
||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||
|
||||
// Loki - Update mapping of message to original thread id
|
||||
// Loki - Update mapping of message to original thread ID
|
||||
if (result.getMessageId() > -1) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||
@ -1102,95 +1109,86 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidPairingMessage(@NonNull PairingAuthorisation authorisation) {
|
||||
private boolean isValidDeviceLinkMessage(@NonNull DeviceLink authorisation) {
|
||||
boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
boolean isRequest = (authorisation.getType() == PairingAuthorisation.Type.REQUEST);
|
||||
boolean isRequest = (authorisation.getType() == DeviceLink.Type.REQUEST);
|
||||
if (authorisation.getRequestSignature() == null) {
|
||||
Log.d("Loki", "Ignoring pairing request message without a request signature.");
|
||||
return false;
|
||||
} else if (isRequest && isSecondaryDevice) {
|
||||
Log.d("Loki", "Ignoring unexpected pairing request message (the device is already paired as a secondary device).");
|
||||
return false;
|
||||
} else if (isRequest && !authorisation.getPrimaryDevicePublicKey().equals(userHexEncodedPublicKey)) {
|
||||
} else if (isRequest && !authorisation.getMasterHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
|
||||
Log.d("Loki", "Ignoring pairing request message addressed to another user.");
|
||||
return false;
|
||||
} else if (isRequest && authorisation.getSecondaryDevicePublicKey().equals(userHexEncodedPublicKey)) {
|
||||
} else if (isRequest && authorisation.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
|
||||
Log.d("Loki", "Ignoring pairing request message from self.");
|
||||
return false;
|
||||
}
|
||||
return authorisation.verify();
|
||||
}
|
||||
|
||||
private void handleProfileAvatar(SignalServiceContent content, String url) {
|
||||
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(primaryDevice, url));
|
||||
}
|
||||
|
||||
private void handlePairingMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceContent content) {
|
||||
private void handleDeviceLinkMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
if (authorisation.getType() == PairingAuthorisation.Type.REQUEST) {
|
||||
handlePairingRequestMessage(authorisation, content);
|
||||
} else if (authorisation.getSecondaryDevicePublicKey().equals(userHexEncodedPublicKey)) {
|
||||
handlePairingAuthorisationMessage(authorisation, content);
|
||||
if (deviceLink.getType() == DeviceLink.Type.REQUEST) {
|
||||
handleDeviceLinkRequestMessage(deviceLink, content);
|
||||
} else if (deviceLink.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
|
||||
handleDeviceLinkAuthorizedMessage(deviceLink, content);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePairingRequestMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceContent content) {
|
||||
boolean isValid = isValidPairingMessage(authorisation);
|
||||
private void handleDeviceLinkRequestMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
|
||||
boolean isValid = isValidDeviceLinkMessage(deviceLink);
|
||||
DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared();
|
||||
if (isValid && linkingSession.isListeningForLinkingRequests()) {
|
||||
// Loki - If we successfully received a request then we should store the PreKeyBundle
|
||||
storePreKeyBundleIfNeeded(content);
|
||||
linkingSession.processLinkingRequest(authorisation);
|
||||
}
|
||||
if (!isValid || !linkingSession.isListeningForLinkingRequests()) { return; }
|
||||
storePreKeyBundleIfNeeded(content);
|
||||
linkingSession.processLinkingRequest(deviceLink);
|
||||
}
|
||||
|
||||
private void handlePairingAuthorisationMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceContent content) {
|
||||
// Prepare
|
||||
boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
|
||||
if (isSecondaryDevice) {
|
||||
Log.d("Loki", "Ignoring unexpected pairing authorisation message (the device is already paired as a secondary device).");
|
||||
private void handleDeviceLinkAuthorizedMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
|
||||
// Check preconditions
|
||||
boolean hasExistingDeviceLink = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
|
||||
if (hasExistingDeviceLink) {
|
||||
Log.d("Loki", "Ignoring unexpected device link message (the device is already linked as a slave device).");
|
||||
return;
|
||||
}
|
||||
boolean isValid = isValidPairingMessage(authorisation);
|
||||
boolean isValid = isValidDeviceLinkMessage(deviceLink);
|
||||
if (!isValid) {
|
||||
Log.d("Loki", "Ignoring invalid pairing authorisation message.");
|
||||
Log.d("Loki", "Ignoring invalid device link message.");
|
||||
return;
|
||||
}
|
||||
if (!DeviceLinkingSession.Companion.getShared().isListeningForLinkingRequests()) {
|
||||
Log.d("Loki", "Ignoring pairing authorisation message.");
|
||||
Log.d("Loki", "Ignoring device link message.");
|
||||
return;
|
||||
}
|
||||
if (authorisation.getType() != PairingAuthorisation.Type.GRANT) { return; }
|
||||
Log.d("Loki", "Received pairing authorisation message from: " + authorisation.getPrimaryDevicePublicKey() + ".");
|
||||
// Save PreKeyBundle if for whatever reason we got one
|
||||
if (deviceLink.getType() != DeviceLink.Type.AUTHORIZATION) { return; }
|
||||
Log.d("Loki", "Received device link authorized message from: " + deviceLink.getMasterHexEncodedPublicKey() + ".");
|
||||
// Save pre key bundle if we somehow got one
|
||||
storePreKeyBundleIfNeeded(content);
|
||||
// Process
|
||||
DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(authorisation);
|
||||
// Store the primary device's public key
|
||||
DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(deviceLink);
|
||||
// Store the master device's ID
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(userHexEncodedPublicKey);
|
||||
DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(authorisation);
|
||||
TextSecurePreferences.setMasterHexEncodedPublicKey(context, authorisation.getPrimaryDevicePublicKey());
|
||||
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userHexEncodedPublicKey);
|
||||
DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink);
|
||||
TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.getMasterHexEncodedPublicKey());
|
||||
TextSecurePreferences.setMultiDevice(context, true);
|
||||
// Send a background message to the primary device
|
||||
MessageSender.sendBackgroundMessage(context, authorisation.getPrimaryDevicePublicKey());
|
||||
// Propagate the updates to the file server
|
||||
LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared();
|
||||
storageAPI.updateUserDeviceMappings();
|
||||
// Update display names
|
||||
// Send a background message to the master device
|
||||
MessageSender.sendBackgroundMessage(context, deviceLink.getMasterHexEncodedPublicKey());
|
||||
// Update display name if needed
|
||||
if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
|
||||
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
|
||||
}
|
||||
// Profile avatar updates
|
||||
// Update profile picture if needed
|
||||
if (content.getDataMessage().isPresent()) {
|
||||
handleProfileKey(content, content.getDataMessage().get());
|
||||
}
|
||||
// Contact sync
|
||||
// Handle contact sync if needed
|
||||
if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) {
|
||||
handleSynchronizeContactMessage(content.getSyncMessage().get().getContacts().get());
|
||||
handleContactSyncMessage(content.getSyncMessage().get().getContacts().get());
|
||||
}
|
||||
// The device link is propagated to the file server in LandingActivity.onDeviceLinkAuthorized because we can handle the error there
|
||||
}
|
||||
|
||||
private void setDisplayName(String hexEncodedPublicKey, String profileName) {
|
||||
@ -1199,95 +1197,69 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
|
||||
private void updateGroupChatMessageServerID(Optional<Long> messageServerIDOrNull, Optional<InsertResult> insertResult) {
|
||||
if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) {
|
||||
long messageID = insertResult.get().getMessageId();
|
||||
long messageServerID = messageServerIDOrNull.get();
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID);
|
||||
}
|
||||
if (!insertResult.isPresent() || !messageServerIDOrNull.isPresent()) { return; }
|
||||
long messageID = insertResult.get().getMessageId();
|
||||
long messageServerID = messageServerIDOrNull.get();
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID);
|
||||
}
|
||||
|
||||
private void storePreKeyBundleIfNeeded(@NonNull SignalServiceContent content) {
|
||||
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
|
||||
if (!sender.isGroupRecipient() && content.lokiServiceMessage.isPresent()) {
|
||||
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
||||
if (lokiMessage.getPreKeyBundleMessage() != null) {
|
||||
int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
|
||||
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
|
||||
if (sender.isGroupRecipient() || !content.lokiServiceMessage.isPresent()) { return; }
|
||||
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
||||
if (lokiMessage.getPreKeyBundleMessage() == null) { return; }
|
||||
int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
|
||||
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
|
||||
if (registrationID <= 0) { return; }
|
||||
Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
|
||||
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
|
||||
lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle);
|
||||
|
||||
// Loki - Store the latest pre key bundle
|
||||
if (registrationID > 0) {
|
||||
Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
|
||||
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
|
||||
lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle);
|
||||
|
||||
// Loki - If we received a friend request, but we were already friends with this user, then reset the session
|
||||
if (content.isFriendRequest()) {
|
||||
long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
|
||||
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
|
||||
resetSession(content.getSender(), threadID);
|
||||
// Let our other devices know that we have reset the session
|
||||
MessageSender.syncContact(context, sender.getAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptFriendRequestIfNeeded(@NonNull SignalServiceContent content) {
|
||||
// If we get anything other than a friend request, we can assume that we have a session with the other user
|
||||
if (content.isFriendRequest() || isGroupChatMessage(content)) { return; }
|
||||
becomeFriendsWithContact(content.getSender(), true, false);
|
||||
}
|
||||
|
||||
private void handleSessionRequestIfNeeded(@NonNull SignalServiceContent content) {
|
||||
if (content.isFriendRequest() && isSessionRequest(content)) {
|
||||
// Check if the session request from a member in one of our groups or our friend
|
||||
LokiStorageAPI.shared.getPrimaryDevicePublicKey(content.getSender()).success(primaryDevicePublicKey -> {
|
||||
String sender = primaryDevicePublicKey != null ? primaryDevicePublicKey : content.getSender();
|
||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false));
|
||||
LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
|
||||
boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
|
||||
boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender);
|
||||
boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups;
|
||||
if (shouldAcceptSessionRequest) {
|
||||
// Send a background message to acknowledge session request
|
||||
MessageSender.sendBackgroundMessage(context, content.getSender());
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
if (!content.isFriendRequest() || !isSessionRequest(content)) { return; }
|
||||
// Check if the session request came from a member in one of our groups or one of our friends
|
||||
LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()).success( masterHexEncodedPublicKey -> {
|
||||
String sender = masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : content.getSender();
|
||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false));
|
||||
LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
|
||||
boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
|
||||
boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender);
|
||||
boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups;
|
||||
if (shouldAcceptSessionRequest) {
|
||||
MessageSender.sendBackgroundMessage(context, content.getSender()); // Send a background message to acknowledge
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
|
||||
private void becomeFriendsWithContact(String pubKey, boolean syncContact, boolean force) {
|
||||
private void becomeFriendsWithContactIfNeeded(String hexEncodedPublicKey, boolean requiresContactSync, boolean canSkip) {
|
||||
// Ignore friend requests to group recipients
|
||||
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
|
||||
Recipient contactID = Recipient.from(context, Address.fromSerialized(pubKey), false);
|
||||
Recipient contactID = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
|
||||
if (contactID.isGroupRecipient()) return;
|
||||
|
||||
// Ignore friend requests to recipients we're already friends with
|
||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(contactID);
|
||||
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
|
||||
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) { return; }
|
||||
|
||||
// We shouldn't be able to skip from None -> Friends in normal circumstances.
|
||||
// Multi-device is the exception to this rule because we want to automatically be friends with a secondary device
|
||||
if (!force && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; }
|
||||
|
||||
// If the thread's friend request status is not `FRIENDS`, but we're receiving a message,
|
||||
// We shouldn't be able to skip from NONE to FRIENDS under normal circumstances.
|
||||
// Multi-device is the one exception to this rule because we want to automatically become friends with slave devices.
|
||||
if (!canSkip && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; }
|
||||
// If the thread's friend request status is not `FRIENDS` or `NONE`, but we're receiving a message,
|
||||
// it must be a friend request accepted message. Declining a friend request doesn't send a message.
|
||||
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
|
||||
// Send out a contact sync message
|
||||
if (syncContact) {
|
||||
// Send out a contact sync message if needed
|
||||
if (requiresContactSync) {
|
||||
MessageSender.syncContact(context, contactID.getAddress());
|
||||
}
|
||||
// Allow profile sharing with contact
|
||||
// Enable profile sharing with the recipient
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(contactID, true);
|
||||
// Update the last message if needed
|
||||
LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> {
|
||||
LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey).success( masterHexEncodedPublicKey -> {
|
||||
Util.runOnMain(() -> {
|
||||
long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false));
|
||||
FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
|
||||
long masterThreadID = (masterHexEncodedPublicKey == null) ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(masterHexEncodedPublicKey), false));
|
||||
FriendRequestHandler.updateLastFriendRequestMessage(context, masterThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
@ -1295,25 +1267,24 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
|
||||
private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
|
||||
if (!content.isFriendRequest() || message.isGroupMessage() || message.isSessionRequest()) { return; }
|
||||
// This handles the case where another user sends us a regular message without authorisation
|
||||
Promise<Boolean, Exception> promise = PromiseUtil.timeout(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), 8000);
|
||||
boolean shouldBecomeFriends = PromiseUtil.get(promise, false);
|
||||
if (shouldBecomeFriends) {
|
||||
// Become friends AND update the message they sent
|
||||
becomeFriendsWithContact(content.getSender(), true, true);
|
||||
becomeFriendsWithContactIfNeeded(content.getSender(), true, true);
|
||||
// Send them an accept message back
|
||||
MessageSender.sendBackgroundMessage(context, content.getSender());
|
||||
} else {
|
||||
// Do regular friend request logic checks
|
||||
Recipient originalRecipient = getMessageDestination(content, message);
|
||||
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
|
||||
Recipient originalRecipient = getRecipientForMessage(content, message);
|
||||
Recipient masterRecipient = getMasterRecipientForMessage(content, message);
|
||||
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
|
||||
|
||||
// Loki - Friend requests only work in direct chats
|
||||
if (!originalRecipient.getAddress().isPhone()) { return; }
|
||||
|
||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient);
|
||||
long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient);
|
||||
long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(masterRecipient);
|
||||
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
|
||||
|
||||
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
||||
@ -1482,7 +1453,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
|
||||
private void triggerSessionRestorePrompt(@NonNull String sender) {
|
||||
Recipient primaryRecipient = getPrimaryDeviceRecipient(sender);
|
||||
Recipient primaryRecipient = getMasterRecipient(sender);
|
||||
if (!primaryRecipient.isGroupRecipient()) {
|
||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient);
|
||||
DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, sender);
|
||||
@ -1537,12 +1508,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) {
|
||||
database.setProfileKey(recipient, message.getProfileKey().get());
|
||||
database.setUnidentifiedAccessMode(recipient, RecipientDatabase.UnidentifiedAccessMode.UNKNOWN);
|
||||
String url = content.senderProfileAvatarUrl.or("");
|
||||
String url = content.senderProfilePictureURL.or("");
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(recipient, url));
|
||||
|
||||
// Loki - If the recipient is our master device then we need to go and update our avatar mappings on the public chats
|
||||
if (recipient.isOurMasterDevice()) {
|
||||
ApplicationContext.getInstance(context).updatePublicChatProfileAvatarIfNeeded();
|
||||
ApplicationContext.getInstance(context).updatePublicChatProfilePictureIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1559,11 +1530,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
private void handleDeliveryReceipt(@NonNull SignalServiceContent content,
|
||||
@NonNull SignalServiceReceiptMessage message)
|
||||
{
|
||||
// Redirect message to primary device conversation
|
||||
// Redirect message to master device conversation
|
||||
Address sender = Address.fromSerialized(content.getSender());
|
||||
if (sender.isPhone()) {
|
||||
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
|
||||
sender = primaryDevice.getAddress();
|
||||
Recipient masterDevice = getMasterRecipient(content.getSender());
|
||||
sender = masterDevice.getAddress();
|
||||
}
|
||||
|
||||
for (long timestamp : message.getTimestamps()) {
|
||||
@ -1579,11 +1550,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
{
|
||||
if (TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
|
||||
// Redirect message to primary device conversation
|
||||
// Redirect message to master device conversation
|
||||
Address sender = Address.fromSerialized(content.getSender());
|
||||
if (sender.isPhone()) {
|
||||
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
|
||||
sender = primaryDevice.getAddress();
|
||||
Recipient masterDevice = getMasterRecipient(content.getSender());
|
||||
sender = masterDevice.getAddress();
|
||||
}
|
||||
|
||||
for (long timestamp : message.getTimestamps()) {
|
||||
@ -1614,7 +1585,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient);
|
||||
} else {
|
||||
// See if we need to redirect the message
|
||||
author = getPrimaryDeviceRecipient(content.getSender());
|
||||
author = getMasterRecipient(content.getSender());
|
||||
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(author);
|
||||
}
|
||||
|
||||
@ -1748,9 +1719,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
|
||||
private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) {
|
||||
Recipient primaryDevice = getPrimaryDeviceRecipient(sender);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(primaryDevice.getAddress(),
|
||||
Recipient masterDevice = getMasterRecipient(sender);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(masterDevice.getAddress(),
|
||||
senderDevice, timestamp, "",
|
||||
Optional.absent(), 0, false);
|
||||
|
||||
@ -1770,11 +1741,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
if (message.getMessage().isGroupMessage()) {
|
||||
return getSyncMessageDestination(message);
|
||||
} else {
|
||||
return getPrimaryDeviceRecipient(message.getDestination().get());
|
||||
return getMasterRecipient(message.getDestination().get());
|
||||
}
|
||||
}
|
||||
|
||||
private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) {
|
||||
private Recipient getRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
|
||||
if (message.isGroupMessage()) {
|
||||
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())), false);
|
||||
} else {
|
||||
@ -1782,34 +1753,34 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
}
|
||||
|
||||
private Recipient getMessagePrimaryDestination(SignalServiceContent content, SignalServiceDataMessage message) {
|
||||
private Recipient getMasterRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
|
||||
if (message.isGroupMessage()) {
|
||||
return getMessageDestination(content, message);
|
||||
return getRecipientForMessage(content, message);
|
||||
} else {
|
||||
return getPrimaryDeviceRecipient(content.getSender());
|
||||
return getMasterRecipient(content.getSender());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary device recipient of the passed in device.
|
||||
* Get the master device recipient of the provided device.
|
||||
*
|
||||
* If the device doesn't have a primary device then it will return the same device.
|
||||
* If the device is our primary device then it will return our current device.
|
||||
* Otherwise it will return the primary device.
|
||||
* If the device doesn't have a master device this will return the same device.
|
||||
* If the device is our master device then it will return our current device.
|
||||
* Otherwise it will return the master device.
|
||||
*/
|
||||
private Recipient getPrimaryDeviceRecipient(String pubKey) {
|
||||
private Recipient getMasterRecipient(String hexEncodedPublicKey) {
|
||||
try {
|
||||
String primaryDevice = PromiseUtil.timeout(LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey), 5000).get();
|
||||
String publicKey = (primaryDevice != null) ? primaryDevice : pubKey;
|
||||
// If the public key matches our primary device then we need to forward the message to ourselves (Note to self)
|
||||
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) {
|
||||
publicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
String masterHexEncodedPublicKey = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey), 5000).get();
|
||||
String targetHexEncodedPublicKey = (masterHexEncodedPublicKey != null) ? masterHexEncodedPublicKey : hexEncodedPublicKey;
|
||||
// If the public key matches our master device then we need to forward the message to ourselves (note to self)
|
||||
String ourMasterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
if (ourMasterHexEncodedPublicKey != null && ourMasterHexEncodedPublicKey.equals(targetHexEncodedPublicKey)) {
|
||||
targetHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
}
|
||||
return Recipient.from(context, Address.fromSerialized(publicKey), false);
|
||||
return Recipient.from(context, Address.fromSerialized(targetHexEncodedPublicKey), false);
|
||||
} catch (Exception e) {
|
||||
Log.d("Loki", "Failed to get primary device public key for " + pubKey + ". " + e.getMessage());
|
||||
return Recipient.from(context, Address.fromSerialized(pubKey), false);
|
||||
Log.d("Loki", "Failed to get master device for: " + hexEncodedPublicKey + ". " + e.getMessage());
|
||||
return Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1831,11 +1802,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
|
||||
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
|
||||
|
||||
if (content.getPairingAuthorisation().isPresent()) {
|
||||
if (content.getDeviceLink().isPresent()) {
|
||||
return false;
|
||||
} else if (content.getDataMessage().isPresent()) {
|
||||
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||
Recipient conversation = getMessageDestination(content, message);
|
||||
Recipient conversation = getRecipientForMessage(content, message);
|
||||
|
||||
if (conversation.isGroupRecipient() && conversation.isBlocked()) {
|
||||
return true;
|
||||
|
@ -50,8 +50,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -368,7 +368,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
if (!member.isPhone() || member.serialize().equalsIgnoreCase(localNumber)) { continue; }
|
||||
|
||||
try {
|
||||
List<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 -> {
|
||||
// Loki - Calling .map(Address::fromSerialized) is causing errors, thus we use the long method :(
|
||||
return Address.fromSerialized(string);
|
||||
|
@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@ -43,13 +42,12 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage;
|
||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -291,11 +289,11 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
} else {
|
||||
LokiSyncMessage syncMessage = null;
|
||||
if (shouldSendSyncMessage) {
|
||||
// Set the sync message destination the primary device, this way it will show that we sent a message to the primary device and not a secondary device
|
||||
String primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null);
|
||||
SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice);
|
||||
// We also need to use the original message id and not -1
|
||||
syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId);
|
||||
// Set the sync message destination to the master device, this way it will show that we sent a message to the master device and not the slave device
|
||||
String masterDevice = PromiseUtil.get(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(address.getNumber()), null);
|
||||
SignalServiceAddress masterAddress = masterDevice == null ? address : new SignalServiceAddress(masterDevice);
|
||||
// We also need to use the original message ID and not -1
|
||||
syncMessage = new LokiSyncMessage(masterAddress, templateMessageId);
|
||||
}
|
||||
return messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified();
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
@ -30,7 +29,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage;
|
||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
||||
|
||||
@ -201,7 +200,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
{
|
||||
try {
|
||||
// rotateSenderCertificateIfNecessary();
|
||||
Recipient recipient = Recipient.from(context, destination, false);
|
||||
Recipient recipient = Recipient.from(context, destination, false);
|
||||
SignalServiceAddress address = getPushAddress(recipient.getAddress());
|
||||
Optional<byte[]> profileKey = getProfileKey(recipient);
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
@ -236,11 +235,11 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
} else {
|
||||
LokiSyncMessage syncMessage = null;
|
||||
if (shouldSendSyncMessage) {
|
||||
// Set the sync message destination to the primary device, this way it will show that we sent a message to the primary device and not a secondary device
|
||||
String primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null);
|
||||
SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice);
|
||||
// We also need to use the original message id and not -1
|
||||
syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId);
|
||||
// Set the sync message destination to the master device, this way it will show that we sent a message to the master device and not the slave device
|
||||
String masterDevice = PromiseUtil.get(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(address.getNumber()), null);
|
||||
SignalServiceAddress masterAddress = masterDevice == null ? address : new SignalServiceAddress(masterDevice);
|
||||
// We also need to use the original message ID and not -1
|
||||
syncMessage = new LokiSyncMessage(masterAddress, templateMessageId);
|
||||
}
|
||||
return messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified();
|
||||
}
|
||||
|
@ -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." }
|
||||
}
|
||||
}
|
@ -10,11 +10,11 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.*
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChat
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus
|
||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
||||
|
||||
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
|
||||
@ -23,11 +23,11 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
||||
companion object {
|
||||
private val friendRequestTableName = "loki_thread_friend_request_database"
|
||||
private val sessionResetTableName = "loki_thread_session_reset_database"
|
||||
public val publicChatTableName = "loki_public_chat_database"
|
||||
public val threadID = "thread_id"
|
||||
val publicChatTableName = "loki_public_chat_database"
|
||||
val threadID = "thread_id"
|
||||
private val friendRequestStatus = "friend_request_status"
|
||||
private val sessionResetStatus = "session_reset_status"
|
||||
public val publicChat = "public_chat"
|
||||
val publicChat = "public_chat"
|
||||
@JvmStatic val createFriendRequestTableCommand = "CREATE TABLE $friendRequestTableName ($threadID INTEGER PRIMARY KEY, $friendRequestStatus INTEGER DEFAULT 0);"
|
||||
@JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTableName ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
|
||||
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTableName ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
|
||||
@ -79,19 +79,21 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
||||
|| friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED
|
||||
}
|
||||
|
||||
override fun getSessionResetStatus(threadID: Long): LokiThreadSessionResetStatus {
|
||||
fun getSessionResetStatus(hexEncodedPublicKey: String): LokiSessionResetStatus {
|
||||
val threadID = getThreadID(hexEncodedPublicKey)
|
||||
val database = databaseHelper.readableDatabase
|
||||
val result = database.get(sessionResetTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
|
||||
cursor.getInt(sessionResetStatus)
|
||||
}
|
||||
return if (result != null) {
|
||||
LokiThreadSessionResetStatus.values().first { it.rawValue == result }
|
||||
LokiSessionResetStatus.values().first { it.rawValue == result }
|
||||
} else {
|
||||
LokiThreadSessionResetStatus.NONE
|
||||
LokiSessionResetStatus.NONE
|
||||
}
|
||||
}
|
||||
|
||||
override fun setSessionResetStatus(threadID: Long, sessionResetStatus: LokiThreadSessionResetStatus) {
|
||||
fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: LokiSessionResetStatus) {
|
||||
val threadID = getThreadID(hexEncodedPublicKey)
|
||||
val database = databaseHelper.writableDatabase
|
||||
val contentValues = ContentValues(2)
|
||||
contentValues.put(Companion.threadID, threadID)
|
||||
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.loki
|
||||
|
||||
import android.content.Context
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.all
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import nl.komponents.kovenant.toFailVoid
|
||||
@ -16,40 +15,36 @@ import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.logging.Log
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||
import org.whispersystems.signalservice.loki.api.DeviceLink
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities
|
||||
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.utilities.recover
|
||||
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
fun checkForRevocation(context: Context) {
|
||||
val primaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return
|
||||
val ourDevice = TextSecurePreferences.getLocalNumber(context)
|
||||
|
||||
LokiStorageAPI.shared.fetchDeviceMappings(primaryDevice).bind { mappings ->
|
||||
val ourMapping = mappings.find { it.secondaryDevicePublicKey == ourDevice }
|
||||
if (ourMapping != null) throw Error("Device has not been revoked")
|
||||
// remove pairing authorisations for our device
|
||||
DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(ourDevice)
|
||||
LokiStorageAPI.shared.updateUserDeviceMappings()
|
||||
fun checkIsRevokedSlaveDevice(context: Context) {
|
||||
val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return
|
||||
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
LokiFileServerAPI.shared.getDeviceLinks(masterHexEncodedPublicKey, true).bind { deviceLinks ->
|
||||
val deviceLink = deviceLinks.find { it.masterHexEncodedPublicKey == masterHexEncodedPublicKey && it.slaveHexEncodedPublicKey == hexEncodedPublicKey }
|
||||
if (deviceLink != null) throw Error("Device hasn't been revoked.")
|
||||
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(hexEncodedPublicKey)
|
||||
LokiFileServerAPI.shared.setDeviceLinks(setOf())
|
||||
}.successUi {
|
||||
TextSecurePreferences.setNeedsRevocationCheck(context, false)
|
||||
TextSecurePreferences.setNeedsIsRevokedSlaveDeviceCheck(context, false)
|
||||
ApplicationContext.getInstance(context).clearData()
|
||||
}.fail { error ->
|
||||
TextSecurePreferences.setNeedsRevocationCheck(context, true)
|
||||
Log.d("Loki", "Revocation check failed: ${error.message ?: error}")
|
||||
TextSecurePreferences.setNeedsIsRevokedSlaveDeviceCheck(context, true)
|
||||
Log.d("Loki", "Revocation check failed due to error: ${error.message ?: error}.")
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise<Map<String, LokiThreadFriendRequestStatus>, Exception> {
|
||||
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys ->
|
||||
return LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(hexEncodedPublicKey).map { keys ->
|
||||
val map = mutableMapOf<String, LokiThreadFriendRequestStatus>()
|
||||
for (devicePublicKey in keys) {
|
||||
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> {
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys ->
|
||||
return LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(hexEncodedPublicKey).map { keys ->
|
||||
val devices = keys.toMutableSet()
|
||||
if (hexEncodedPublicKey != userHexEncodedPublicKey) {
|
||||
devices.remove(userHexEncodedPublicKey)
|
||||
@ -92,7 +87,7 @@ fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Conte
|
||||
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 (primaryDevicePublicKey == null) {
|
||||
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 address = SignalServiceAddress(contactHexEncodedPublicKey)
|
||||
val message = SignalServiceDataMessage.newBuilder().withPairingAuthorisation(authorisation)
|
||||
// A REQUEST should always act as a friend request. A GRANT should always be replying back as a normal message.
|
||||
if (authorisation.type == PairingAuthorisation.Type.REQUEST) {
|
||||
val address = SignalServiceAddress(hexEncodedPublicKey)
|
||||
val message = SignalServiceDataMessage.newBuilder().withDeviceLink(deviceLink)
|
||||
// A REQUEST should always act as a friend request. An AUTHORIZATION should always be a normal message.
|
||||
if (deviceLink.type == DeviceLink.Type.REQUEST) {
|
||||
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
|
||||
message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
|
||||
} else {
|
||||
// Send over our profile key so that our linked device can get our profile picture
|
||||
message.withProfileKey(ProfileKeyUtil.getProfileKey(context))
|
||||
}
|
||||
|
||||
return try {
|
||||
Log.d("Loki", "Sending authorisation message to: $contactHexEncodedPublicKey.")
|
||||
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(contactHexEncodedPublicKey), false))
|
||||
Log.d("Loki", "Sending device link message to: $hexEncodedPublicKey.")
|
||||
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false))
|
||||
val result = messageSender.sendMessage(0, address, udAccess, message.build())
|
||||
if (result.success == null) {
|
||||
val exception = when {
|
||||
result.isNetworkFailure -> "Failed to send authorisation message due to a network error."
|
||||
else -> "Failed to send authorisation message."
|
||||
result.isNetworkFailure -> "Failed to send device link message due to a network error."
|
||||
else -> "Failed to send device link message."
|
||||
}
|
||||
throw Exception(exception)
|
||||
}
|
||||
Promise.ofSuccess(Unit)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisation: PairingAuthorisation) {
|
||||
fun signAndSendDeviceLinkMessage(context: Context, deviceLink: DeviceLink): Promise<Unit, Exception> {
|
||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||
val signedPairingAuthorisation = pairingAuthorisation.sign(PairingAuthorisation.Type.GRANT, userPrivateKey)
|
||||
if (signedPairingAuthorisation == null || signedPairingAuthorisation.type != PairingAuthorisation.Type.GRANT) {
|
||||
Log.d("Loki", "Failed to sign pairing authorization.")
|
||||
return
|
||||
val signedDeviceLink = deviceLink.sign(DeviceLink.Type.AUTHORIZATION, userPrivateKey)
|
||||
if (signedDeviceLink == null || signedDeviceLink.type != DeviceLink.Type.AUTHORIZATION) {
|
||||
return Promise.ofFail(Exception("Failed to sign device link."))
|
||||
}
|
||||
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)
|
||||
return LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).map { devices ->
|
||||
return LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(ourPublicKey).map { devices ->
|
||||
devices.contains(address.serialize())
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.BaseJob
|
||||
import org.thoughtcrime.securesms.logging.Log
|
||||
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.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
@ -99,7 +97,7 @@ class PushBackgroundMessageSendJob private constructor(
|
||||
}
|
||||
|
||||
if (message.get("sessionRestore", false)) {
|
||||
dataMessage.asSessionRestore(true)
|
||||
dataMessage.asSessionRestorationRequest(true)
|
||||
}
|
||||
|
||||
if (message.get("sessionRequest", false)) {
|
||||
|
@ -104,9 +104,9 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
|
||||
val recipients = selectedMembers.map {
|
||||
Recipient.from(this, Address.fromSerialized(it), false)
|
||||
}.toSet()
|
||||
val ourNumber = TextSecurePreferences.getMasterHexEncodedPublicKey(this) ?: TextSecurePreferences.getLocalNumber(this)
|
||||
val local = Recipient.from(this, Address.fromSerialized(ourNumber), false)
|
||||
CreateClosedGroupTask(WeakReference(this), null, name.toString(), recipients, setOf(local)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
|
||||
val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this) ?: TextSecurePreferences.getLocalNumber(this)
|
||||
val admin = Recipient.from(this, Address.fromSerialized(masterHexEncodedPublicKey), false)
|
||||
CreateClosedGroupTask(WeakReference(this), null, name.toString(), recipients, setOf( admin )).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
|
||||
}
|
||||
|
||||
private fun handleOpenConversation(threadId: Long, recipient: Recipient) {
|
||||
@ -122,7 +122,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
|
||||
// region Tasks
|
||||
internal class CreateClosedGroupTask(
|
||||
private val activity: WeakReference<CreateClosedGroupActivity>,
|
||||
private val avatar: Bitmap?,
|
||||
private val profilePicture: Bitmap?,
|
||||
private val name: String?,
|
||||
private val members: Set<Recipient>,
|
||||
private val admins: Set<Recipient>
|
||||
@ -130,24 +130,18 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
|
||||
|
||||
override fun doInBackground(vararg params: Void?): Optional<GroupManager.GroupActionResult> {
|
||||
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>) {
|
||||
val activity = activity.get()
|
||||
if (activity == null) {
|
||||
super.onPostExecute(result)
|
||||
return
|
||||
}
|
||||
|
||||
val activity = activity.get() ?: return super.onPostExecute(result)
|
||||
if (result.isPresent && result.get().threadId > -1) {
|
||||
if (!activity.isFinishing) {
|
||||
activity.handleOpenConversation(result.get().threadId, result.get().groupRecipient)
|
||||
}
|
||||
} else {
|
||||
super.onPostExecute(result)
|
||||
Toast.makeText(activity.applicationContext,
|
||||
R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(activity.applicationContext, "One of the members of your group has an invalid Session ID.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ class CreateClosedGroupLoader(context: Context) : AsyncLoader<List<String>>(cont
|
||||
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
|
||||
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val deviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getPairingAuthorisations(userHexEncodedPublicKey)
|
||||
val deviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getDeviceLinks(userHexEncodedPublicKey)
|
||||
val userLinkedDeviceHexEncodedPublicKeys = deviceLinks.flatMap {
|
||||
listOf( it.primaryDevicePublicKey.toLowerCase(), it.secondaryDevicePublicKey.toLowerCase() )
|
||||
listOf( it.masterHexEncodedPublicKey.toLowerCase(), it.slaveHexEncodedPublicKey.toLowerCase() )
|
||||
}.toMutableSet()
|
||||
userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey.toLowerCase())
|
||||
val cursor = threadDatabase.conversationList
|
||||
|
@ -4,7 +4,7 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
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 org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
@ -19,7 +19,7 @@ class DisplayNameActivity : BaseActionBarActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setUpActionBarSessionLogo()
|
||||
setContentView(R.layout.activity_display_name_v2)
|
||||
setContentView(R.layout.activity_display_name)
|
||||
displayNameEditText.imeOptions = displayNameEditText.imeOptions or 16777216 // Always use incognito keyboard
|
||||
registerButton.setOnClickListener { register() }
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
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.ScanQRCodeWrapperFragmentDelegate
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
@ -72,6 +73,8 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
|
||||
val displayName = TextSecurePreferences.getProfileName(this)
|
||||
val lokiPublicChatAPI = application.lokiPublicChatAPI!!
|
||||
application.lokiPublicChatManager.addChat(url, channel).successUi {
|
||||
DatabaseFactory.getLokiAPIDatabase(this).removeLastMessageServerID(channel, url)
|
||||
DatabaseFactory.getLokiAPIDatabase(this).removeLastDeletionServerID(channel, url)
|
||||
lokiPublicChatAPI.getMessages(channel, url)
|
||||
lokiPublicChatAPI.setDisplayName(displayName, url)
|
||||
lokiPublicChatAPI.join(channel, url)
|
||||
@ -133,7 +136,10 @@ class EnterChatURLFragment : Fragment() {
|
||||
private fun joinPublicChatIfPossible() {
|
||||
val inputMethodManager = context!!.getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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.setUpActionBarSessionLogo
|
||||
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.Hex
|
||||
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.ECKeyPair
|
||||
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.retryIfNeeded
|
||||
|
||||
@ -92,11 +93,11 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
|
||||
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
||||
TextSecurePreferences.setPromptedPushRegistration(this, true)
|
||||
val authorisation = PairingAuthorisation(hexEncodedPublicKey, userHexEncodedPublicKey).sign(PairingAuthorisation.Type.REQUEST, keyPair!!.privateKey.serialize())
|
||||
if (authorisation == null) {
|
||||
val deviceLink = DeviceLink(hexEncodedPublicKey, userHexEncodedPublicKey).sign(DeviceLink.Type.REQUEST, keyPair!!.privateKey.serialize())
|
||||
if (deviceLink == null) {
|
||||
Log.d("Loki", "Failed to sign device link request.")
|
||||
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)
|
||||
application.startLongPollingIfNeeded()
|
||||
@ -107,13 +108,14 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
|
||||
linkDeviceDialog.show(supportFragmentManager, "Link Device Dialog")
|
||||
AsyncTask.execute {
|
||||
retryIfNeeded(8) {
|
||||
sendPairingAuthorisationMessage(this@LandingActivity, authorisation.primaryDevicePublicKey, authorisation)
|
||||
sendDeviceLinkMessage(this@LandingActivity, deviceLink.masterHexEncodedPublicKey, deviceLink)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation) {
|
||||
TextSecurePreferences.setMasterHexEncodedPublicKey(this, authorization.primaryDevicePublicKey)
|
||||
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
|
||||
LokiFileServerAPI.shared.addDeviceLink(deviceLink)
|
||||
TextSecurePreferences.setMasterHexEncodedPublicKey(this, deviceLink.masterHexEncodedPublicKey)
|
||||
val intent = Intent(this, HomeActivity::class.java)
|
||||
show(intent)
|
||||
finish()
|
||||
|
@ -12,16 +12,21 @@ import android.view.View
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.activity_linked_devices.*
|
||||
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.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.devicelist.Device
|
||||
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.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||
import org.whispersystems.signalservice.loki.api.DeviceLink
|
||||
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager.LoaderCallbacks<List<Device>>, DeviceClickListener, EditDeviceNameDialogDelegate, LinkDeviceMasterModeDialogDelegate {
|
||||
private var devices = listOf<Device>()
|
||||
@ -118,20 +123,46 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager
|
||||
private fun unlinkDevice(slaveDeviceHexEncodedPublicKey: String) {
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||
val database = DatabaseFactory.getLokiAPIDatabase(this)
|
||||
database.removePairingAuthorisation(userHexEncodedPublicKey, slaveDeviceHexEncodedPublicKey)
|
||||
LokiStorageAPI.shared.updateUserDeviceMappings().success {
|
||||
MessageSender.sendUnpairRequest(this, slaveDeviceHexEncodedPublicKey)
|
||||
val deviceLinks = database.getDeviceLinks(userHexEncodedPublicKey)
|
||||
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)
|
||||
}
|
||||
LoaderManager.getInstance(this).restartLoader(0, null, this)
|
||||
Toast.makeText(this, "Your device was unlinked successfully", Toast.LENGTH_LONG).show()
|
||||
}.fail {
|
||||
Toast.makeText(this, "Couldn't unlink device.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
LoaderManager.getInstance(this).restartLoader(0, null, this)
|
||||
Toast.makeText(this, "Your device was unlinked successfully", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation) {
|
||||
AsyncTask.execute {
|
||||
signAndSendPairingAuthorisationMessage(this, authorization)
|
||||
Util.runOnMain {
|
||||
LoaderManager.getInstance(this).restartLoader(0, null, this)
|
||||
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
|
||||
LokiFileServerAPI.shared.addDeviceLink(deviceLink).success {
|
||||
signAndSendDeviceLinkMessage(this, deviceLink).success {
|
||||
TextSecurePreferences.setMultiDevice(this, true)
|
||||
Util.runOnMain {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import org.thoughtcrime.securesms.devicelist.Device
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities
|
||||
import org.thoughtcrime.securesms.util.AsyncLoader
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
||||
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities
|
||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||
import java.io.File
|
||||
|
||||
@ -20,7 +20,7 @@ class LinkedDevicesLoader(context: Context) : AsyncLoader<List<Device>>(context)
|
||||
override fun loadInBackground(): List<Device>? {
|
||||
try {
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val slaveDeviceHexEncodedPublicKeys = LokiStorageAPI.shared.getSecondaryDevicePublicKeys(userHexEncodedPublicKey).get()
|
||||
val slaveDeviceHexEncodedPublicKeys = LokiDeviceLinkUtilities.getSlaveHexEncodedPublicKeys(userHexEncodedPublicKey).get()
|
||||
return slaveDeviceHexEncodedPublicKeys.map { hexEncodedPublicKey ->
|
||||
val shortID = MnemonicUtilities.getFirst3Words(mnemonicCodec, hexEncodedPublicKey)
|
||||
val name = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
|
||||
|
@ -9,7 +9,7 @@ import android.text.SpannableString
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.widget.LinearLayout
|
||||
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 org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
@ -33,7 +33,7 @@ class SeedActivity : BaseActionBarActivity() {
|
||||
// region Lifecycle
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_seed_v2)
|
||||
setContentView(R.layout.activity_seed)
|
||||
supportActionBar!!.title = "Your Recovery Phrase"
|
||||
val seedReminderViewTitle = SpannableString("You're almost finished! 90%")
|
||||
seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher
|
||||
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.File
|
||||
import java.security.SecureRandom
|
||||
@ -159,7 +159,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
|
||||
val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey)
|
||||
if (isUpdatingProfilePicture && profilePicture != null) {
|
||||
val storageAPI = LokiStorageAPI.shared
|
||||
val storageAPI = LokiFileServerAPI.shared
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
AsyncTask.execute {
|
||||
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)
|
||||
TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt())
|
||||
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
|
||||
ApplicationContext.getInstance(this).updatePublicChatProfileAvatarIfNeeded()
|
||||
ApplicationContext.getInstance(this).updatePublicChatProfilePictureIfNeeded()
|
||||
profilePictureView.update()
|
||||
}
|
||||
profilePictureToBeUploaded = null
|
||||
|
@ -17,15 +17,15 @@ import org.thoughtcrime.securesms.loki.redesign.utilities.QRCodeUtilities
|
||||
import org.thoughtcrime.securesms.loki.toPx
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
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.DeviceLinkingSessionListener
|
||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||
|
||||
class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListener {
|
||||
private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) }
|
||||
private lateinit var contentView: View
|
||||
private var authorization: PairingAuthorisation? = null
|
||||
private var deviceLink: DeviceLink? = null
|
||||
var delegate: LinkDeviceMasterModeDialogDelegate? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
@ -45,10 +45,10 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
||||
return result
|
||||
}
|
||||
|
||||
override fun requestUserAuthorization(authorization: PairingAuthorisation) {
|
||||
if (authorization.type != PairingAuthorisation.Type.REQUEST || authorization.primaryDevicePublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.authorization != null) { return }
|
||||
override fun requestUserAuthorization(deviceLink: DeviceLink) {
|
||||
if (deviceLink.type != DeviceLink.Type.REQUEST || deviceLink.masterHexEncodedPublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
|
||||
Util.runOnMain {
|
||||
this.authorization = authorization
|
||||
this.deviceLink = deviceLink
|
||||
contentView.qrCodeImageView.visibility = View.GONE
|
||||
val titleTextViewLayoutParams = contentView.titleTextView.layoutParams as LinearLayout.LayoutParams
|
||||
titleTextViewLayoutParams.topMargin = toPx(8, resources)
|
||||
@ -56,13 +56,13 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
||||
contentView.titleTextView.text = "Linking Request Received"
|
||||
contentView.explanationTextView.text = "Please check that the words below match those shown on your other device"
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
private fun authorizeDeviceLink() {
|
||||
val authorization = this.authorization ?: return
|
||||
val authorization = this.deviceLink ?: return
|
||||
delegate?.onDeviceLinkRequestAuthorized(authorization)
|
||||
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
||||
DeviceLinkingSession.shared.removeListener(this)
|
||||
@ -72,8 +72,8 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
||||
private fun onDeviceLinkCanceled() {
|
||||
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
||||
DeviceLinkingSession.shared.removeListener(this)
|
||||
if (authorization != null) {
|
||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorization!!.secondaryDevicePublicKey)
|
||||
if (deviceLink != null) {
|
||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(deviceLink!!.slaveHexEncodedPublicKey)
|
||||
}
|
||||
dismiss()
|
||||
delegate?.onDeviceLinkCanceled()
|
||||
@ -82,6 +82,6 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
||||
|
||||
interface LinkDeviceMasterModeDialogDelegate {
|
||||
|
||||
fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation)
|
||||
fun onDeviceLinkRequestAuthorized(authorization: DeviceLink)
|
||||
fun onDeviceLinkCanceled()
|
||||
}
|
||||
|
@ -15,15 +15,15 @@ import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
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.DeviceLinkingSessionListener
|
||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||
|
||||
class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener {
|
||||
private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) }
|
||||
private lateinit var contentView: View
|
||||
private var authorization: PairingAuthorisation? = null
|
||||
private var deviceLink: DeviceLink? = null
|
||||
var delegate: LinkDeviceSlaveModeDialogDelegate? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
@ -40,10 +40,10 @@ class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener
|
||||
return result
|
||||
}
|
||||
|
||||
override fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation) {
|
||||
if (authorization.type != PairingAuthorisation.Type.GRANT || authorization.secondaryDevicePublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.authorization != null) { return }
|
||||
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
|
||||
if (deviceLink.type != DeviceLink.Type.AUTHORIZATION || deviceLink.slaveHexEncodedPublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
|
||||
Util.runOnMain {
|
||||
this.authorization = authorization
|
||||
this.deviceLink = deviceLink
|
||||
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
||||
DeviceLinkingSession.shared.removeListener(this)
|
||||
contentView.spinner.visibility = View.GONE
|
||||
@ -56,7 +56,7 @@ class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener
|
||||
contentView.cancelButton.visibility = View.GONE
|
||||
Handler().postDelayed({
|
||||
dismiss()
|
||||
delegate?.onDeviceLinkRequestAuthorized(authorization)
|
||||
delegate?.onDeviceLinkRequestAuthorized(deviceLink)
|
||||
}, 4000)
|
||||
}
|
||||
}
|
||||
@ -71,6 +71,6 @@ class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener
|
||||
|
||||
interface LinkDeviceSlaveModeDialogDelegate {
|
||||
|
||||
fun onDeviceLinkRequestAuthorized(authorization: PairingAuthorisation)
|
||||
fun onDeviceLinkRequestAuthorized(authorization: DeviceLink)
|
||||
fun onDeviceLinkCanceled()
|
||||
}
|
@ -15,7 +15,7 @@ import java.util.concurrent.TimeUnit
|
||||
class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
||||
|
||||
companion object {
|
||||
private val pollInterval = TimeUnit.MINUTES.toMillis(2)
|
||||
private val pollInterval = TimeUnit.MINUTES.toMillis(1)
|
||||
|
||||
@JvmStatic
|
||||
fun schedule(context: Context) {
|
||||
|
@ -10,7 +10,7 @@ import java.util.concurrent.TimeUnit
|
||||
class BackgroundPublicChatPollWorker : PersistentAlarmManagerListener() {
|
||||
|
||||
companion object {
|
||||
private val pollInterval = TimeUnit.MINUTES.toMillis(4)
|
||||
private val pollInterval = TimeUnit.MINUTES.toMillis(2)
|
||||
|
||||
@JvmStatic
|
||||
fun schedule(context: Context) {
|
||||
|
@ -7,9 +7,9 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.*
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
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.LokiAPITarget
|
||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||
|
||||
// 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 lastDeletionServerID = "last_deletion_server_id"
|
||||
@JvmStatic val createLastDeletionServerIDTableCommand = "CREATE TABLE $lastDeletionServerIDCache ($lastDeletionServerIDCacheIndex STRING PRIMARY KEY, $lastDeletionServerID INTEGER DEFAULT 0);"
|
||||
// Pairing authorisation cache
|
||||
private val pairingAuthorisationCache = "loki_pairing_authorisation_cache"
|
||||
private val primaryDevicePublicKey = "primary_device"
|
||||
private val secondaryDevicePublicKey = "secondary_device"
|
||||
// Device link cache
|
||||
private val deviceLinkCache = "loki_pairing_authorisation_cache"
|
||||
private val masterHexEncodedPublicKey = "primary_device"
|
||||
private val slaveHexEncodedPublicKey = "secondary_device"
|
||||
private val requestSignature = "request_signature"
|
||||
private val grantSignature = "grant_signature"
|
||||
@JvmStatic val createPairingAuthorisationTableCommand = "CREATE TABLE $pairingAuthorisationCache ($primaryDevicePublicKey TEXT, $secondaryDevicePublicKey TEXT, " +
|
||||
"$requestSignature TEXT NULLABLE DEFAULT NULL, $grantSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($primaryDevicePublicKey, $secondaryDevicePublicKey));"
|
||||
private val authorizationSignature = "grant_signature"
|
||||
@JvmStatic val createDeviceLinkTableCommand = "CREATE TABLE $deviceLinkCache ($masterHexEncodedPublicKey TEXT, $slaveHexEncodedPublicKey TEXT, " +
|
||||
"$requestSignature TEXT NULLABLE DEFAULT NULL, $authorizationSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($masterHexEncodedPublicKey, $slaveHexEncodedPublicKey));"
|
||||
// User count cache
|
||||
private val userCountCache = "loki_user_count_cache"
|
||||
private val publicChatID = "public_chat_id"
|
||||
@ -179,35 +179,35 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
database.delete(lastDeletionServerIDCache,"$lastDeletionServerIDCacheIndex = ?", wrap(index))
|
||||
}
|
||||
|
||||
override fun getPairingAuthorisations(hexEncodedPublicKey: String): List<PairingAuthorisation> {
|
||||
override fun getDeviceLinks(hexEncodedPublicKey: String): Set<DeviceLink> {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.getAll(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) { cursor ->
|
||||
val primaryDevicePubKey = cursor.getString(primaryDevicePublicKey)
|
||||
val secondaryDevicePubKey = cursor.getString(secondaryDevicePublicKey)
|
||||
return database.getAll(deviceLinkCache, "$masterHexEncodedPublicKey = ? OR $slaveHexEncodedPublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) { cursor ->
|
||||
val masterHexEncodedPublicKey = cursor.getString(masterHexEncodedPublicKey)
|
||||
val slaveHexEncodedPublicKey = cursor.getString(slaveHexEncodedPublicKey)
|
||||
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)
|
||||
PairingAuthorisation(primaryDevicePubKey, secondaryDevicePubKey, requestSignature, grantSignature)
|
||||
}
|
||||
val authorizationSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(authorizationSignature))) null else cursor.getBase64EncodedData(authorizationSignature)
|
||||
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 values = ContentValues()
|
||||
values.put(primaryDevicePublicKey, authorisation.primaryDevicePublicKey)
|
||||
values.put(secondaryDevicePublicKey, authorisation.secondaryDevicePublicKey)
|
||||
if (authorisation.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(authorisation.requestSignature)) }
|
||||
if (authorisation.grantSignature != null) { values.put(grantSignature, Base64.encodeBytes(authorisation.grantSignature)) }
|
||||
database.insertOrUpdate(pairingAuthorisationCache, values, "$primaryDevicePublicKey = ? AND $secondaryDevicePublicKey = ?", arrayOf( authorisation.primaryDevicePublicKey, authorisation.secondaryDevicePublicKey ))
|
||||
values.put(masterHexEncodedPublicKey, deviceLink.masterHexEncodedPublicKey)
|
||||
values.put(slaveHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey)
|
||||
if (deviceLink.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(deviceLink.requestSignature)) }
|
||||
if (deviceLink.authorizationSignature != null) { values.put(authorizationSignature, Base64.encodeBytes(deviceLink.authorizationSignature)) }
|
||||
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
|
||||
database.delete(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey ))
|
||||
}
|
||||
|
||||
fun removePairingAuthorisation(primaryDevicePublicKey: String, secondaryDevicePublicKey: String) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
database.delete(pairingAuthorisationCache, "${Companion.primaryDevicePublicKey} = ? OR ${Companion.secondaryDevicePublicKey} = ?", arrayOf( primaryDevicePublicKey, secondaryDevicePublicKey ))
|
||||
database.delete(deviceLinkCache, "$masterHexEncodedPublicKey = ? OR $slaveHexEncodedPublicKey = ?", arrayOf( deviceLink.masterHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey ))
|
||||
}
|
||||
|
||||
fun getUserCount(group: Long, server: String): Int? {
|
||||
|
@ -21,10 +21,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.loki.api.LokiPublicChat
|
||||
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.api.*
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.utilities.successBackground
|
||||
import java.security.MessageDigest
|
||||
@ -155,36 +152,36 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
||||
|
||||
fun pollForNewMessages() {
|
||||
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
|
||||
val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(message.hexEncodedPublicKey).get()
|
||||
if (primaryDevice == null) {
|
||||
// If the sender of the current message is not a slave device, set the display name in the database
|
||||
val masterHexEncodedPublicKey = LokiDeviceLinkUtilities.getMasterHexEncodedPublicKey(message.hexEncodedPublicKey).get()
|
||||
if (masterHexEncodedPublicKey == null) {
|
||||
val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
|
||||
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)
|
||||
}
|
||||
val senderPublicKey = primaryDevice ?: message.hexEncodedPublicKey
|
||||
val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.hexEncodedPublicKey
|
||||
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) {
|
||||
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
||||
} else {
|
||||
PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
||||
}
|
||||
// Update profile avatar if needed
|
||||
val senderRecipient = Recipient.from(context, Address.fromSerialized(senderPublicKey), false)
|
||||
if (message.avatar != null && message.avatar!!.url.isNotEmpty()) {
|
||||
val profileKey = message.avatar!!.profileKey
|
||||
val url = message.avatar!!.url
|
||||
if (senderRecipient.profileKey == null || !MessageDigest.isEqual(senderRecipient.profileKey, profileKey)) {
|
||||
// Update profile picture if needed
|
||||
val senderAsRecipient = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false)
|
||||
if (message.profilePicture != null && message.profilePicture!!.url.isNotEmpty()) {
|
||||
val profileKey = message.profilePicture!!.profileKey
|
||||
val url = message.profilePicture!!.url
|
||||
if (senderAsRecipient.profileKey == null || !MessageDigest.isEqual(senderAsRecipient.profileKey, profileKey)) {
|
||||
val database = DatabaseFactory.getRecipientDatabase(context)
|
||||
database.setProfileKey(senderRecipient, profileKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderRecipient, url))
|
||||
database.setProfileKey(senderAsRecipient, profileKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url))
|
||||
}
|
||||
} else if (senderRecipient.profileAvatar.orEmpty().isNotEmpty()) {
|
||||
// Unset the avatar if we had an avatar before and we're not friends with the person
|
||||
val threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(senderRecipient)
|
||||
val friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId)
|
||||
} else if (senderAsRecipient.profileAvatar.orEmpty().isNotEmpty()) {
|
||||
// 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(senderAsRecipient)
|
||||
val friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
|
||||
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
|
||||
if (isDuplicate) { 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 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
|
||||
if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) {
|
||||
PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript)
|
||||
} else {
|
||||
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)
|
||||
if (recipient.isOurMasterDevice && message.avatar != null) {
|
||||
val profileKey = message.avatar!!.profileKey
|
||||
val url = message.avatar!!.url
|
||||
if (recipient.isOurMasterDevice && message.profilePicture != null) {
|
||||
val profileKey = message.profilePicture!!.profileKey
|
||||
val url = message.profilePicture!!.url
|
||||
if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, profileKey)) {
|
||||
val database = DatabaseFactory.getRecipientDatabase(context)
|
||||
database.setProfileKey(recipient, profileKey)
|
||||
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>()
|
||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||
val database = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
LokiStorageAPI.configure(false, userHexEncodedPublicKey, userPrivateKey, database)
|
||||
LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).bind { devices ->
|
||||
LokiFileServerAPI.configure(false, userHexEncodedPublicKey, userPrivateKey, database)
|
||||
LokiDeviceLinkUtilities.getAllLinkedDeviceHexEncodedPublicKeys(userHexEncodedPublicKey).bind { devices ->
|
||||
userDevices = devices
|
||||
api.getMessages(group.channel, group.server)
|
||||
}.bind { messages ->
|
||||
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()
|
||||
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()) {
|
||||
return@bind LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).then { messages }
|
||||
return@bind LokiFileServerAPI.shared.getDeviceLinks(devicesToUpdate.toSet()).then { messages }
|
||||
}
|
||||
}
|
||||
Promise.of(messages)
|
||||
}.successBackground {
|
||||
// Get the set of primary device pubKeys FROM the secondary devices in uniqueDevices
|
||||
val newDisplayNameUpdatees = uniqueDevices.mapNotNull {
|
||||
// This will return null if current device is primary
|
||||
// So if it's non-null then we know the device is a secondary device
|
||||
val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get()
|
||||
primaryDevice
|
||||
// This will return null if the current device is a master device
|
||||
LokiDeviceLinkUtilities.getMasterHexEncodedPublicKey(it).get()
|
||||
}.toSet()
|
||||
// Fetch the display names of the primary devices
|
||||
// Fetch the display names of the master devices
|
||||
displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees)
|
||||
}.successBackground { messages ->
|
||||
// Process messages in the background
|
||||
|
@ -14,7 +14,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
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 java.text.SimpleDateFormat
|
||||
import java.util.regex.Pattern
|
||||
@ -48,7 +48,7 @@ class LokiRSSFeedPoller(private val context: Context, private val feed: LokiRSSF
|
||||
}
|
||||
|
||||
private fun poll() {
|
||||
LokiRSSProxy.fetch(feed.url).successBackground { xml ->
|
||||
LokiRSSFeedProxy.fetch(feed.url).successBackground { xml ->
|
||||
val items = XMLParser(xml).call()
|
||||
items.reversed().forEach { item ->
|
||||
val title = item.title ?: return@forEach
|
||||
|
@ -6,6 +6,11 @@ import android.support.v4.content.LocalBroadcastManager
|
||||
|
||||
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) {
|
||||
val intent = Intent(event)
|
||||
intent.putExtra("long", long)
|
||||
|
@ -53,7 +53,7 @@ object MentionUtilities {
|
||||
}
|
||||
}
|
||||
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)
|
||||
for (mention in mentions) {
|
||||
if (!userLinkedDeviceHexEncodedPublicKeys.contains(mention.second)) { continue }
|
||||
|
@ -12,7 +12,7 @@ import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.getColorWithID
|
||||
import org.thoughtcrime.securesms.loki.toPx
|
||||
|
||||
class SeparatorView : RelativeLayout {
|
||||
class LabeledSeparatorView : RelativeLayout {
|
||||
|
||||
private val path = Path()
|
||||
|
@ -3,13 +3,12 @@ package org.thoughtcrime.securesms.mms;
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
|
||||
|
||||
public class PushMediaConstraints extends MediaConstraints {
|
||||
|
||||
private static final int MAX_IMAGE_DIMEN_LOWMEM = 768;
|
||||
private static final int MAX_IMAGE_DIMEN = 4096;
|
||||
private static final int KB = 1024;
|
||||
private static final int MB = 1024 * KB;
|
||||
|
||||
@Override
|
||||
public int getImageMaxWidth(Context context) {
|
||||
@ -23,26 +22,26 @@ public class PushMediaConstraints extends MediaConstraints {
|
||||
|
||||
@Override
|
||||
public int getImageMaxSize(Context context) {
|
||||
return 6 * MB;
|
||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGifMaxSize(Context context) {
|
||||
return 25 * MB;
|
||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVideoMaxSize(Context context) {
|
||||
return 100 * MB;
|
||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioMaxSize(Context context) {
|
||||
return 100 * MB;
|
||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDocumentMaxSize(Context context) {
|
||||
return 100 * MB;
|
||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
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.List;
|
||||
|
@ -9,14 +9,10 @@ fun getOpenGroupDisplayName(recipient: Recipient, threadRecipient: Recipient, co
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient)
|
||||
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||
val hexEncodedPublicKey = recipient.address.toString()
|
||||
val displayName: String?
|
||||
displayName = if (publicChat != null) {
|
||||
val displayName = if (publicChat != null) {
|
||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
|
||||
} else {
|
||||
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
|
||||
}
|
||||
if (displayName == null) {
|
||||
return hexEncodedPublicKey
|
||||
}
|
||||
return displayName
|
||||
return displayName ?: hexEncodedPublicKey
|
||||
}
|
@ -38,7 +38,7 @@ public class MessageSenderEventListener implements SignalServiceMessageSender.Ev
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
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.utilities.PromiseUtil;
|
||||
|
||||
@ -99,7 +99,7 @@ public class MessageSender {
|
||||
sendBackgroundMessage(context, contactHexEncodedPublicKey);
|
||||
|
||||
// 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(() -> {
|
||||
for (String device : devices) {
|
||||
// Don't send message to the device we already have sent to
|
||||
@ -234,7 +234,7 @@ public class MessageSender {
|
||||
final int ttl) {
|
||||
String ourPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).success(devices -> {
|
||||
LokiDeviceLinkUtilities.INSTANCE.getAllLinkedDeviceHexEncodedPublicKeys(ourPublicKey).success(devices -> {
|
||||
Util.runOnMain(() -> {
|
||||
for (String device : devices) {
|
||||
// Don't send to ourselves
|
||||
@ -306,8 +306,8 @@ public class MessageSender {
|
||||
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
|
||||
String recipientPublicKey = recipient.getAddress().serialize();
|
||||
if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey) || PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false)) {
|
||||
String recipientHexEncodedPublicKey = recipient.getAddress().serialize();
|
||||
if (GeneralUtilitiesKt.isPublicChat(context, recipientHexEncodedPublicKey) || PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false)) {
|
||||
if (type == MessageType.MEDIA) {
|
||||
PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false);
|
||||
} 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
|
||||
boolean[] hasSentSyncMessage = { false };
|
||||
MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientPublicKey).success(devices -> {
|
||||
MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientHexEncodedPublicKey).success(devices -> {
|
||||
int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet());
|
||||
Util.runOnMain(() -> {
|
||||
ArrayList<Job> jobs = new ArrayList<>();
|
||||
for (Map.Entry<String, Boolean> entry : devices.entrySet()) {
|
||||
String devicePublicKey = entry.getKey();
|
||||
String deviceHexEncodedPublicKey = entry.getKey();
|
||||
boolean isFriend = entry.getValue();
|
||||
|
||||
Address address = Address.fromSerialized(devicePublicKey);
|
||||
long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L;
|
||||
Address address = Address.fromSerialized(deviceHexEncodedPublicKey);
|
||||
long messageIDToUse = recipientHexEncodedPublicKey.equals(deviceHexEncodedPublicKey) ? messageId : -1L;
|
||||
|
||||
if (isFriend) {
|
||||
// Send a normal message if the user is friends with the recipient
|
||||
@ -340,7 +340,7 @@ public class MessageSender {
|
||||
}
|
||||
if (shouldSendSyncMessage) { hasSentSyncMessage[0] = true; }
|
||||
} 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.
|
||||
boolean isFriendsWithAny = (friendCount > 0);
|
||||
String defaultFriendRequestMessage = isFriendsWithAny ? "Please accept to enable messages to be synced across devices" : null;
|
||||
|
@ -1235,11 +1235,11 @@ public class TextSecurePreferences {
|
||||
return getBooleanPreference(context, "database_reset_unpair", false);
|
||||
}
|
||||
|
||||
public static void setNeedsRevocationCheck(Context context, boolean needsCheck) {
|
||||
setBooleanPreference(context, "needs_revocation", needsCheck);
|
||||
public static void setNeedsIsRevokedSlaveDeviceCheck(Context context, boolean value) {
|
||||
setBooleanPreference(context, "needs_revocation", value);
|
||||
}
|
||||
|
||||
public static boolean needsRevocationCheck(Context context) {
|
||||
public static boolean getNeedsIsRevokedSlaveDeviceCheck(Context context) {
|
||||
return getBooleanPreference(context, "needs_revocation", false);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user