mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 20:15:21 +00:00
Merge pull request #260 from loki-project/localization
Integrate Persian Translation
This commit is contained in:
commit
3e8e4326bf
@ -73,7 +73,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="@dimen/medium_button_height"
|
android:layout_height="@dimen/medium_button_height"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:text="@string/share" />
|
android:text="@string/share" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -150,7 +150,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="@dimen/medium_button_height"
|
android:layout_height="@dimen/medium_button_height"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:text="@string/share" />
|
android:text="@string/share" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -96,6 +96,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textColorHint="#99FFFFFF"
|
android:textColorHint="#99FFFFFF"
|
||||||
android:textSize="@dimen/small_font_size"
|
android:textSize="@dimen/small_font_size"
|
||||||
@ -137,7 +138,7 @@
|
|||||||
android:id="@+id/recorder_view"
|
android:id="@+id/recorder_view"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="36dp"
|
android:layout_width="36dp"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:clipToPadding="false">
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
@ -208,12 +208,6 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.loki.views.FriendRequestView
|
|
||||||
android:id="@+id/friend_request_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.AlertView
|
<org.thoughtcrime.securesms.components.AlertView
|
||||||
@ -225,4 +219,5 @@
|
|||||||
android:gravity="center_vertical"/>
|
android:gravity="center_vertical"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</org.thoughtcrime.securesms.conversation.ConversationItem>
|
</org.thoughtcrime.securesms.conversation.ConversationItem>
|
@ -161,12 +161,6 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.loki.views.FriendRequestView
|
|
||||||
android:id="@+id/friend_request_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.AlertView
|
<org.thoughtcrime.securesms.components.AlertView
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="@dimen/small_button_height"
|
android:layout_height="@dimen/small_button_height"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:text="@string/delete" />
|
android:text="@string/delete" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -87,7 +87,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="@dimen/small_button_height"
|
android:layout_height="@dimen/small_button_height"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:text="@string/dialog_link_device_master_mode_authorize_button_title"
|
android:text="@string/dialog_link_device_master_mode_authorize_button_title"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="@dimen/small_button_height"
|
android:layout_height="@dimen/small_button_height"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:text="@string/copy" />
|
android:text="@string/copy" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
28
res/layout/fragment_conversation_bottom_sheet.xml
Normal file
28
res/layout/fragment_conversation_bottom_sheet.xml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<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="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:behavior_hideable="true"
|
||||||
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
|
||||||
|
android:background="@color/dialog_background">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/blockOrUnblockTextView"
|
||||||
|
style="@style/ActionItem"
|
||||||
|
android:drawableStart="@drawable/ic_block_white_24dp"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:text="@string/RecipientPreferenceActivity_block"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/deleteTextView"
|
||||||
|
style="@style/ActionItem"
|
||||||
|
android:drawableStart="@drawable/ic_delete_white_24dp"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:text="@string/delete" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -73,7 +73,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="@dimen/medium_button_height"
|
android:layout_height="@dimen/medium_button_height"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:text="@string/share" />
|
android:text="@string/share" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -8,11 +8,11 @@
|
|||||||
android:id="@+id/quick_audio_toggle"
|
android:id="@+id/quick_audio_toggle"
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center"
|
||||||
android:layout_marginEnd="2dp"
|
|
||||||
android:background="@null"
|
android:background="@null"
|
||||||
android:contentDescription="@string/conversation_activity__quick_attachment_drawer_record_and_send_audio_description"
|
android:contentDescription="@string/conversation_activity__quick_attachment_drawer_record_and_send_audio_description"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
android:tint="@color/text"
|
android:tint="@color/text"
|
||||||
app:srcCompat="@drawable/ic_microphone" />
|
app:srcCompat="@drawable/ic_microphone" />
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/unreadMessagesIndicatorView"
|
android:id="@+id/accentView"
|
||||||
android:layout_width="@dimen/accent_line_thickness"
|
android:layout_width="@dimen/accent_line_thickness"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/accent" />
|
android:background="@color/accent" />
|
||||||
@ -18,14 +18,14 @@
|
|||||||
android:layout_width="@dimen/medium_profile_picture_size"
|
android:layout_width="@dimen/medium_profile_picture_size"
|
||||||
android:layout_height="@dimen/medium_profile_picture_size"
|
android:layout_height="@dimen/medium_profile_picture_size"
|
||||||
android:layout_marginTop="@dimen/medium_spacing"
|
android:layout_marginTop="@dimen/medium_spacing"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:layout_marginBottom="@dimen/medium_spacing" />
|
android:layout_marginBottom="@dimen/medium_spacing" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:layout_marginRight="@dimen/medium_spacing"
|
android:layout_marginEnd="@dimen/medium_spacing"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -54,7 +54,7 @@
|
|||||||
android:id="@+id/timestampTextView"
|
android:id="@+id/timestampTextView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:textSize="@dimen/small_font_size"
|
android:textSize="@dimen/small_font_size"
|
||||||
@ -76,7 +76,7 @@
|
|||||||
android:layout_height="12dp"
|
android:layout_height="12dp"
|
||||||
android:src="@drawable/ic_mute"
|
android:src="@drawable/ic_mute"
|
||||||
android:layout_marginTop="1dp"
|
android:layout_marginTop="1dp"
|
||||||
android:layout_marginRight="4dp" />
|
android:layout_marginEnd="4dp" />
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -110,7 +110,7 @@
|
|||||||
android:id="@+id/statusIndicatorImageView"
|
android:id="@+id/statusIndicatorImageView"
|
||||||
android:layout_width="@dimen/conversation_view_status_indicator_size"
|
android:layout_width="@dimen/conversation_view_status_indicator_size"
|
||||||
android:layout_height="@dimen/conversation_view_status_indicator_size"
|
android:layout_height="@dimen/conversation_view_status_indicator_size"
|
||||||
android:layout_marginLeft="@dimen/medium_spacing" />
|
android:layout_marginStart="@dimen/medium_spacing" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
8
res/menu/conversation_block.xml
Normal file
8
res/menu/conversation_block.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item android:title="@string/recipient_preferences__block"
|
||||||
|
android:id="@+id/menu_block"
|
||||||
|
android:icon="@drawable/ic_block_white_24dp" />
|
||||||
|
|
||||||
|
</menu>
|
8
res/menu/conversation_unblock.xml
Normal file
8
res/menu/conversation_unblock.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item android:title="@string/ConversationActivity_unblock"
|
||||||
|
android:id="@+id/menu_unblock"
|
||||||
|
android:icon="@drawable/ic_check_white_24dp" />
|
||||||
|
|
||||||
|
</menu>
|
@ -1214,5 +1214,215 @@
|
|||||||
<string name="prompt_passphrase_activity__signal_is_locked">Session قفل شده است</string>
|
<string name="prompt_passphrase_activity__signal_is_locked">Session قفل شده است</string>
|
||||||
<string name="RegistrationLockDialog_reminder">یادآور:</string>
|
<string name="RegistrationLockDialog_reminder">یادآور:</string>
|
||||||
<string name="recipient_preferences__about">درباره ی ما</string>
|
<string name="recipient_preferences__about">درباره ی ما</string>
|
||||||
<!--EOF-->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Session -->
|
||||||
|
<string name="continue_2">ادامه </string>
|
||||||
|
<string name="copy">رونویسی کردن</string>
|
||||||
|
<string name="invalid_url">URL نامعتبر است</string>
|
||||||
|
<string name="copied_to_clipboard">در کلیپ بورد کپی شد</string>
|
||||||
|
<string name="device_linking_failed">پیوند دستگاه امکان پذیر نیست.</string>
|
||||||
|
<string name="next">بعد</string>
|
||||||
|
<string name="share">اشتراک گذاری</string>
|
||||||
|
<string name="invalid_session_id">شناسه نامعتبر جلسه</string>
|
||||||
|
<string name="cancel">لغو</string>
|
||||||
|
<string name="your_session_id">شناسه جلسه شما</string>
|
||||||
|
|
||||||
|
<string name="activity_landing_title_2">جلسه شما از اینجا شروع می شود...</string>
|
||||||
|
<string name="activity_landing_register_button_title">شناسه جلسه را ایجاد کنید</string>
|
||||||
|
<string name="activity_landing_restore_button_title">جلسه خود را ادامه دهید</string>
|
||||||
|
<string name="activity_landing_link_button_title">پیوند به یک حساب کاربری موجود</string>
|
||||||
|
<string name="activity_landing_device_unlinked_dialog_title">ارتباط دستگاه شما با موفقیت انجام نشد</string>
|
||||||
|
|
||||||
|
<string name="view_fake_chat_bubble_1">جلسه چیست؟</string>
|
||||||
|
<string name="view_fake_chat_bubble_2">این یک برنامه پیام رسانی غیرمتمرکز و رمزگذاری شده است</string>
|
||||||
|
<string name="view_fake_chat_bubble_3">بنابراین اطلاعات شخصی یا ابرداده گفتگوی من جمع نمی شود؟ چگونه کار می کند؟</string>
|
||||||
|
<string name="view_fake_chat_bubble_4">استفاده از ترکیبی از فن آوری های رمزگذاری پیشرفته مسیریابی ناشناس و پایان به پایان.</string>
|
||||||
|
<string name="view_fake_chat_bubble_5">دوستان اجازه نمی دهند تا دوستان از پیام رسان های مصالحه استفاده کنند. خواهش میکنم.</string>
|
||||||
|
|
||||||
|
<string name="activity_register_title">سلام به شناسه جلسه خود</string>
|
||||||
|
<string name="activity_register_explanation">شناسه جلسه شما آدرس منحصر به فردی است که افراد می توانند از آنها برای تماس با شما در جلسه استفاده کنند. بدون ارتباط با هویت واقعی شما ، شناسه جلسه شما کاملاً ناشناس و خصوصی است.</string>
|
||||||
|
<string name="activity_register_public_key_copied_message">در کلیپ بورد کپی شد</string>
|
||||||
|
|
||||||
|
<string name="activity_restore_title">حساب خود را بازیابی کنید</string>
|
||||||
|
<string name="activity_restore_explanation">عبارت بازیابی را هنگام ثبت نام برای بازیابی حساب خود وارد کنید.</string>
|
||||||
|
<string name="activity_restore_seed_edit_text_hint">عبارت بازیابی خود را وارد کنید</string>
|
||||||
|
|
||||||
|
<string name="activity_link_device_title">دستگاه پیوند</string>
|
||||||
|
<string name="activity_link_device_enter_session_id_tab_title">شناسه جلسه را وارد کنید</string>
|
||||||
|
<string name="activity_link_device_scan_qr_code_tab_title">اسکن کد QR</string>
|
||||||
|
<string name="activity_link_device_scan_qr_code_explanation">به تنظیمات > دستگاه ها > پیوند دستگاه در دستگاه دیگر خود بروید و سپس کد QR را که برای شروع کار پیوند داده است ، اسکن کنید.</string>
|
||||||
|
|
||||||
|
<string name="fragment_enter_session_id_title">دستگاه خود را پیوند دهید</string>
|
||||||
|
<string name="fragment_enter_session_id_explanation">به تنظیمات > دستگاه ها > پیوند دستگاه در دستگاه دیگر خود بروید و سپس شناسه جلسه خود را در اینجا وارد کنید تا فرایند پیوند آغاز شود.</string>
|
||||||
|
<string name="fragment_enter_session_id_edit_text_hint">شناسه جلسه خود را وارد کنید</string>
|
||||||
|
|
||||||
|
<string name="activity_display_name_title_2">نام صفحه نمایش خود را انتخاب کنید</string>
|
||||||
|
<string name="activity_display_name_explanation">این نام شما هنگام استفاده از جلسه خواهد بود. این می تواند نام واقعی شما باشد ، نام مستعار یا هر چیز دیگری که دوست دارید.</string>
|
||||||
|
<string name="activity_display_name_edit_text_hint">نام نمایشگر را وارد کنید</string>
|
||||||
|
<string name="activity_display_name_display_name_missing_error">لطفاً نام نمایشگر را انتخاب کنید</string>
|
||||||
|
<string name="activity_display_name_display_name_invalid_error">لطفاً یک نام نمایشگر را انتخاب کنید که فقط از نویسه های Az ، AZ ، 0-9 و _ تشکیل شده باشد</string>
|
||||||
|
<string name="activity_display_name_display_name_too_long_error">لطفاً نام نمایشگر کوتاه تری انتخاب کنید</string>
|
||||||
|
|
||||||
|
<string name="activity_pn_mode_title">اعلانهای فشار</string>
|
||||||
|
<string name="activity_pn_mode_explanation">دو راه وجود دارد که جلسه می تواند اعلان های فشار را کنترل کند. حتما قبل از انتخاب توضیحات را با دقت بخوانید.</string>
|
||||||
|
<string name="activity_pn_mode_fcm_option_title">پیام ابری فایربیس</string>
|
||||||
|
<string name="activity_pn_mode_fcm_option_explanation">جلسه برای دریافت اعلان های فشار از سرویس پیام ابری فایربیس استفاده می کند. با اطمینان و بلافاصله از پیامهای جدید مطلع خواهید شد. استفاده از FCM بدان معنا است که آدرس IP و نشانه دستگاه شما در معرض گوگل قرار خواهد گرفت. اگر از اعلانهای فشار برای سایر برنامه ها استفاده می کنید ، این مورد در حال حاضر اینگونه خواهد بود. آدرس IP و نشانه دستگاه شما نیز در معرض Loki قرار خواهد گرفت ، اما پیام های شما هنوز هم از طریق رمزگذاری شده توسط اونیون و رمزگذاری نهایی به پایان می رسد ، بنابراین محتوای پیام های شما کاملاً خصوصی خواهد بود.</string>
|
||||||
|
<string name="activity_pn_mode_background_polling_option_title">زمینه رای گیری</string>
|
||||||
|
<string name="activity_pn_mode_background_polling_option_explanation">جلسه گاه به گاه پیام های جدید را در پس زمینه بررسی می کند. این محافظت کامل از ابرداده را تضمین می کند ، اما اعلان های پیام ممکن است به میزان قابل توجهی به تأخیر بیفتند.</string>
|
||||||
|
<string name="activity_pn_mode_recommended_option_tag">توصیه شده</string>
|
||||||
|
<string name="activity_pn_mode_no_option_picked_dialog_title">لطفا گزینه ای را انتخاب کنید</string>
|
||||||
|
|
||||||
|
<string name="activity_home_empty_state_message">شما هنوز هیچ تماسی ندارید</string>
|
||||||
|
<string name="activity_home_empty_state_button_title">شروع جلسه</string>
|
||||||
|
<string name="activity_home_leave_group_dialog_message">آیا مطمئن هستید که می خواهید این گروه را ترک کنید؟</string>
|
||||||
|
<string name="activity_home_leaving_group_failed_message">نمی توان گروه را ترک کرد</string>
|
||||||
|
<string name="activity_home_delete_conversation_dialog_message">آیا مطمئن هستید که می خواهید این مکالمه را حذف کنید؟</string>
|
||||||
|
<string name="activity_home_conversation_deleted_message">مکالمه حذف شد</string>
|
||||||
|
|
||||||
|
<string name="sheet_pn_mode_title">اعلانهای فشار</string>
|
||||||
|
<string name="sheet_pn_mode_explanation">جلسه اکنون دو راه برای رسیدگی به اعلان های فشار دارد. حتما قبل از انتخاب توضیحات را با دقت بخوانید.</string>
|
||||||
|
<string name="sheet_pn_mode_fcm_option_title">پیام ابری فایربیس</string>
|
||||||
|
<string name="sheet_pn_mode_fcm_option_explanation">جلسه برای دریافت اعلان های فشار از سرویس پیام ابری فایربیس استفاده می کند. با اطمینان و بلافاصله از پیامهای جدید مطلع خواهید شد. استفاده از FCM بدان معنا است که آدرس IP و نشانه دستگاه شما در معرض گوگل قرار خواهد گرفت. اگر از اعلانهای فشار برای سایر برنامه ها استفاده می کنید ، این مورد در حال حاضر اینگونه خواهد بود. آدرس IP و نشانه دستگاه شما نیز در معرض Loki قرار خواهد گرفت ، اما پیام های شما هنوز هم از طریق رمزگذاری شده توسط پیاز و رمزگذاری نهایی به پایان می رسد ، بنابراین محتوای پیام های شما کاملاً خصوصی خواهد بود.</string>
|
||||||
|
<string name="sheet_pn_mode_background_polling_option_title">زمینه رای گیری</string>
|
||||||
|
<string name="sheet_pn_mode_background_polling_option_explanation">جلسه گاه به گاه پیام های جدید را در پس زمینه بررسی می کند. این محافظت کامل از ابرداده را تضمین می کند ، اما اعلان های پیام ممکن است به میزان قابل توجهی به تأخیر بیفتند.</string>
|
||||||
|
<string name="sheet_pn_mode_recommended_option_tag">توصیه شده</string>
|
||||||
|
<string name="sheet_pn_mode_no_option_picked_dialog_title">لطفا گزینه ای را انتخاب کنید</string>
|
||||||
|
<string name="sheet_pn_mode_confirm_button_title">تایید</string>
|
||||||
|
<string name="sheet_pn_mode_skip_button_title">رد</string>
|
||||||
|
|
||||||
|
<string name="activity_seed_title">عبارت بازیابی شما</string>
|
||||||
|
<string name="activity_seed_title_2">با عبارت بازیابی خود مطابقت داشته باشید</string>
|
||||||
|
<string name="activity_seed_explanation">عبارت بازیابی شما کلید اصلی شناسه جلسه شما است - در صورت عدم دسترسی به دستگاه خود می توانید از آن برای بازگرداندن شناسه جلسه استفاده کنید. عبارت بازیابی خود را در مکانی امن ذخیره کنید و آن را به کسی ندهید.</string>
|
||||||
|
<string name="activity_seed_reveal_button_title">نگه دارید تا فاش شود</string>
|
||||||
|
|
||||||
|
<string name="view_seed_reminder_subtitle_1">با ذخیره کردن عبارت بازیابی ، حساب خود را ایمن کنید</string>
|
||||||
|
<string name="view_seed_reminder_subtitle_2">برای فاش کردن عبارت بازیابی ، بر روی کلمات redacted ضربه زده و نگه دارید ، سپس با خیال راحت آن را ذخیره کنید تا شناسه جلسه خود را ایمن نمایید.</string>
|
||||||
|
<string name="view_seed_reminder_subtitle_3">حتماً عبارت بازیابی خود را در مکانی امن ذخیره کنید</string>
|
||||||
|
|
||||||
|
<string name="activity_path_title">مسیر</string>
|
||||||
|
<string name="activity_path_explanation">جلسه IP شما را با گزاف گویی پیام های خود از طریق چندین گره سرویس در شبکه غیرمتمرکز جلسه مخفی می کند. اینها کشورهایی هستند که اتصال شما در حال حاضر از طریق آن فراخوانی می شوند:</string>
|
||||||
|
<string name="activity_path_device_row_title">شما</string>
|
||||||
|
<string name="activity_path_guard_node_row_title">گره ورود</string>
|
||||||
|
<string name="activity_path_service_node_row_title">گره سرویس</string>
|
||||||
|
<string name="activity_path_destination_row_title">مقصد</string>
|
||||||
|
<string name="activity_path_learn_more_button_title">بیشتر بدانید</string>
|
||||||
|
|
||||||
|
<string name="activity_create_private_chat_title">جلسه جدید</string>
|
||||||
|
<string name="activity_create_private_chat_enter_session_id_tab_title">شناسه جلسه را وارد کنید</string>
|
||||||
|
<string name="activity_create_private_chat_scan_qr_code_tab_title">اسکن کد QR</string>
|
||||||
|
<string name="activity_create_private_chat_scan_qr_code_explanation">برای شروع جلسه ، کد QR کاربر را اسکن کنید. با ضربه زدن روی نماد کد QR در تنظیمات حساب ، کدهای QR را می توان یافت.</string>
|
||||||
|
|
||||||
|
<string name="fragment_enter_public_key_edit_text_hint">شناسه گیرنده را وارد کنید</string>
|
||||||
|
<string name="fragment_enter_public_key_explanation">کاربران می توانند با وارد کردن به تنظیمات حساب خود و ضربه زدن به Share Share Session ID یا با به اشتراک گذاشتن کد QR خود ، شناسه جلسه خود را به اشتراک بگذارند.</string>
|
||||||
|
|
||||||
|
<string name="fragment_scan_qr_code_camera_access_explanation">جلسه برای اسکن کدهای QR به دوربین دسترسی دارد</string>
|
||||||
|
<string name="fragment_scan_qr_code_grant_camera_access_button_title">دسترسی به کمک دوربین</string>
|
||||||
|
|
||||||
|
<string name="activity_create_closed_group_title">گروه بسته شده جدید</string>
|
||||||
|
<string name="activity_create_closed_group_edit_text_hint">نام گروه را وارد کنید</string>
|
||||||
|
<string name="activity_create_closed_group_explanation">گروه های بسته تا 10 عضو را پشتیبانی می کنند و همان محافظت از حریم خصوصی را به عنوان جلسات یک به یک ارائه می دهند.</string>
|
||||||
|
<string name="activity_create_closed_group_empty_state_message">شما هنوز هیچ تماسی ندارید</string>
|
||||||
|
<string name="activity_create_closed_group_empty_state_button_title">شروع جلسه</string>
|
||||||
|
<string name="activity_create_closed_group_group_name_missing_error">لطفاً یک نام گروه وارد کنید</string>
|
||||||
|
<string name="activity_create_closed_group_group_name_too_long_error">لطفاً نام گروه کوتاه تری وارد کنید</string>
|
||||||
|
<string name="activity_create_closed_group_not_enough_group_members_error">لطفا حداقل 2 عضو گروه را انتخاب کنید</string>
|
||||||
|
<string name="activity_create_closed_group_too_many_group_members_error">یک گروه بسته نمی تواند بیش از 10 عضو داشته باشد</string>
|
||||||
|
<string name="activity_create_closed_group_invalid_session_id_error">یکی از اعضای گروه شما دارای شناسه نامعتبر است</string>
|
||||||
|
|
||||||
|
<string name="activity_join_public_chat_title">به گروه باز بپیوندید</string>
|
||||||
|
<string name="activity_join_public_chat_error">امکان پیوستن به گروه نیست</string>
|
||||||
|
<string name="activity_join_public_chat_enter_group_url_tab_title">آدرس اینترنتی گروه را باز کنید</string>
|
||||||
|
<string name="activity_join_public_chat_scan_qr_code_tab_title">اسکن کد QR</string>
|
||||||
|
<string name="activity_join_public_chat_scan_qr_code_explanation">کد QR گروه باز را که می خواهید بپیوندید اسکن کنید</string>
|
||||||
|
|
||||||
|
<string name="fragment_enter_chat_url_edit_text_hint">یک URL گروه باز وارد کنید</string>
|
||||||
|
<string name="fragment_enter_chat_url_privacy_warning">گروه های باز می توانند توسط هر کسی بپیوندند و محافظت کامل از حریم خصوصی ارائه نمی دهند</string>
|
||||||
|
|
||||||
|
<string name="activity_settings_title">تنظیمات</string>
|
||||||
|
<string name="activity_settings_display_name_edit_text_hint">نام نمایشگر را وارد کنید</string>
|
||||||
|
<string name="activity_settings_display_name_missing_error">لطفاً نام نمایشگر را انتخاب کنید</string>
|
||||||
|
<string name="activity_settings_invalid_display_name_error">لطفاً یک نام نمایشگر را انتخاب کنید که فقط از نویسه های Az ، AZ ، 0-9 و _ تشکیل شده باشد</string>
|
||||||
|
<string name="activity_settings_display_name_too_long_error">لطفاً نام نمایشگر کوتاه تری انتخاب کنید</string>
|
||||||
|
<string name="activity_settings_privacy_button_title">حریم خصوصی</string>
|
||||||
|
<string name="activity_settings_notifications_button_title">اطلاعیه</string>
|
||||||
|
<string name="activity_settings_chats_button_title">چت</string>
|
||||||
|
<string name="activity_settings_devices_button_title">دستگاه ها</string>
|
||||||
|
<string name="activity_settings_recovery_phrase_button_title">عبارت بازیابی</string>
|
||||||
|
<string name="activity_settings_clear_all_data_button_title">اطلاعات روشن</string>
|
||||||
|
|
||||||
|
<string name="activity_notification_settings_title">اطلاعیه</string>
|
||||||
|
<string name="activity_notification_settings_style_section_title">سبک اطلاع رسانی</string>
|
||||||
|
<string name="activity_notification_settings_content_section_title">محتوای اطلاع رسانی</string>
|
||||||
|
|
||||||
|
<string name="activity_privacy_settings_title">حریم خصوصی</string>
|
||||||
|
|
||||||
|
<string name="activity_chat_settings_title">چت</string>
|
||||||
|
|
||||||
|
<string name="activity_linked_devices_title">دستگاه ها</string>
|
||||||
|
<string name="activity_linked_devices_multi_device_limit_reached_dialog_title">دستگاه محدود شد</string>
|
||||||
|
<string name="activity_linked_devices_multi_device_limit_reached_dialog_explanation">در حال حاضر مجاز به پیوند بیش از یک دستگاه نیست.</string>
|
||||||
|
<string name="activity_linked_devices_unlinking_failed_message">دستگاه را نمی توان ارتباط برقرار کرد.</string>
|
||||||
|
<string name="activity_linked_devices_unlinking_successful_message">دستگاه شما با موفقیت ارتباط برقرار نشد</string>
|
||||||
|
<string name="activity_linked_devices_linking_failed_message">پیوند دستگاه امکان پذیر نیست.</string>
|
||||||
|
<string name="activity_linked_devices_empty_state_message">شما هنوز هیچ دستگاهی را پیوند نداده اید</string>
|
||||||
|
<string name="activity_linked_devices_empty_state_button_title">پیوند یک دستگاه (بتا)</string>
|
||||||
|
|
||||||
|
<string name="preferences_notifications_strategy_category_title">استراتژی اعلان</string>
|
||||||
|
<string name="preferences_notifications_use_fcm_option_title">از FCM استفاده کنید</string>
|
||||||
|
<string name="preferences_notifications_use_fcm_option_explanation">با استفاده از پیام ابری فایربیس، اعلان های فشار قابل اطمینان تر امکان پذیر است ، اما نشانگر IP و دستگاه شما را در گوگل و لوکی قرار می دهد.</string>
|
||||||
|
|
||||||
|
<string name="dialog_link_device_slave_mode_title_1">در انتظار مجوز</string>
|
||||||
|
<string name="dialog_link_device_slave_mode_title_2">پیوند دستگاه مجاز است</string>
|
||||||
|
<string name="dialog_link_device_slave_mode_explanation_1">لطفاً بررسی کنید که کلمات زیر با کلمات نشان داده شده در دستگاه دیگر شما مطابقت دارند.</string>
|
||||||
|
<string name="dialog_link_device_slave_mode_explanation_2">دستگاه شما با موفقیت پیوند خورده است</string>
|
||||||
|
|
||||||
|
<string name="dialog_link_device_master_mode_title_1">منتظر دستگاه</string>
|
||||||
|
<string name="dialog_link_device_master_mode_title_2">درخواست پیوند دریافت شد</string>
|
||||||
|
<string name="dialog_link_device_master_mode_title_3">پیوند دستگاه مجاز</string>
|
||||||
|
<string name="dialog_link_device_master_mode_explanation_1">جلسه را در دستگاه دیگر خود دانلود کرده و روی پیوند به یک حساب موجود در پایین صفحه فرود ضربه بزنید. اگر در حال حاضر دارای یک حساب کاربری در دستگاه دیگر خود هستید ، ابتدا باید آن حساب را حذف کنید.</string>
|
||||||
|
<string name="dialog_link_device_master_mode_explanation_2">لطفاً بررسی کنید که کلمات زیر با کلمات نشان داده شده در دستگاه دیگر شما مطابقت دارند.</string>
|
||||||
|
<string name="dialog_link_device_master_mode_explanation_3">لطفاً منتظر بمانید تا پیوند دستگاه ایجاد شود. این ممکن است تا یک دقیقه طول بکشد.</string>
|
||||||
|
<string name="dialog_link_device_master_mode_authorize_button_title">اجازه دهید</string>
|
||||||
|
|
||||||
|
<string name="fragment_device_list_bottom_sheet_change_name_button_title">تغییر نام</string>
|
||||||
|
<string name="fragment_device_list_bottom_sheet_unlink_device_button_title">دستگاه را جدا کنید</string>
|
||||||
|
|
||||||
|
<string name="dialog_edit_device_name_edit_text_hint">یک نام وارد کنید</string>
|
||||||
|
|
||||||
|
<string name="dialog_seed_title">عبارت بازیابی شما</string>
|
||||||
|
<string name="dialog_seed_explanation">این عبارت بازیابی شماست. با استفاده از آن ، می توانید شناسه جلسه خود را به دستگاه جدید بازیابی یا انتقال دهید.</string>
|
||||||
|
|
||||||
|
<string name="dialog_clear_all_data_title">پاک کردن همه داده ها</string>
|
||||||
|
<string name="dialog_clear_all_data_explanation">این به طور دائم پیام ها، جلسات و مخاطبین شما را حذف می کند.</string>
|
||||||
|
|
||||||
|
<string name="activity_qr_code_title">کد QR</string>
|
||||||
|
<string name="activity_qr_code_view_my_qr_code_tab_title">مشاهده کد QR من</string>
|
||||||
|
<string name="activity_qr_code_view_scan_qr_code_tab_title">اسکن کد QR</string>
|
||||||
|
<string name="activity_qr_code_view_scan_qr_code_explanation">برای شروع مکالمه با آنها ، کد QR شخصی را اسکن کنید</string>
|
||||||
|
|
||||||
|
<string name="fragment_view_my_qr_code_explanation">این کد QR شماست. سایر کاربران می توانند برای شروع جلسه با شما آن را اسکن کنند.</string>
|
||||||
|
<string name="fragment_view_my_qr_code_share_title">کد QR را به اشتراک بگذارید</string>
|
||||||
|
|
||||||
|
<string name="view_friend_request_accept_button_title">تایید کنید</string>
|
||||||
|
<string name="view_friend_request_reject_button_title">کاهش می یابد</string>
|
||||||
|
<string name="view_friend_request_incoming_pending_message">%1$s برای شما درخواست جلسه ارسال کرد</string>
|
||||||
|
<string name="view_friend_request_incoming_accepted_message">شما قبول کرده اید %1$s درخواست جلسه</string>
|
||||||
|
<string name="view_friend_request_incoming_declined_message">شما کاهش داده اید %1$s درخواست جلسه</string>
|
||||||
|
<string name="view_friend_request_incoming_expired_message">%1$s درخواست جلسه منقضی شده است</string>
|
||||||
|
<string name="view_friend_request_outgoing_pending_message">شما ارسال کرده اید %1$s درخواست جلسه</string>
|
||||||
|
<string name="view_friend_request_outgoing_accepted_message">%1$s درخواست جلسه خود را پذیرفته اید</string>
|
||||||
|
<string name="view_friend_request_outgoing_expired_message">درخواست جلسه شما به %1$s منقضی شده است</string>
|
||||||
|
|
||||||
|
<string name="session_reset_banner_message">آیا می خواهید جلسه خود را با آن بازیابی کنید %s ؟</string>
|
||||||
|
<string name="session_reset_banner_dismiss_button_title">رد</string>
|
||||||
|
<string name="session_reset_banner_restore_button_title">بازگرداندن</string>
|
||||||
|
|
||||||
|
<string name="fragment_contact_selection_contacts_title">مخاطبین</string>
|
||||||
|
<string name="fragment_contact_selection_closed_groups_title">گروه های بسته</string>
|
||||||
|
<string name="fragment_contact_selection_open_groups_title">باز کردن گروه ها</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1561,4 +1561,5 @@ Otrzymano wiadomość wymiany klucz dla niepoprawnej wersji protokołu.</string>
|
|||||||
<string name="fragment_contact_selection_contacts_title">Łączność</string>
|
<string name="fragment_contact_selection_contacts_title">Łączność</string>
|
||||||
<string name="fragment_contact_selection_closed_groups_title">Grupy zamknięte</string>
|
<string name="fragment_contact_selection_closed_groups_title">Grupy zamknięte</string>
|
||||||
<string name="fragment_contact_selection_open_groups_title">Otwórz grupy</string>
|
<string name="fragment_contact_selection_open_groups_title">Otwórz grupy</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1358,6 +1358,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Session -->
|
||||||
<string name="continue_2">Продолжить</string>
|
<string name="continue_2">Продолжить</string>
|
||||||
<string name="copy">Копировать</string>
|
<string name="copy">Копировать</string>
|
||||||
<string name="invalid_url">Неверная ссылка</string>
|
<string name="invalid_url">Неверная ссылка</string>
|
||||||
|
@ -1655,7 +1655,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- Session -->
|
<!-- Session -->
|
||||||
|
|
||||||
<string name="continue_2">Continue</string>
|
<string name="continue_2">Continue</string>
|
||||||
<string name="copy">Copy</string>
|
<string name="copy">Copy</string>
|
||||||
<string name="invalid_url">Invalid URL</string>
|
<string name="invalid_url">Invalid URL</string>
|
||||||
|
@ -139,6 +139,7 @@
|
|||||||
<item name="android:textSize">@dimen/large_font_size</item>
|
<item name="android:textSize">@dimen/large_font_size</item>
|
||||||
<item name="android:textColor">@color/text</item>
|
<item name="android:textColor">@color/text</item>
|
||||||
<item name="android:fontFamily">@font/space_mono_regular</item>
|
<item name="android:fontFamily">@font/space_mono_regular</item>
|
||||||
|
<item name="android:textAlignment">viewStart</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SessionEditText">
|
<style name="SessionEditText">
|
||||||
@ -150,6 +151,7 @@
|
|||||||
<item name="android:textSize">@dimen/small_font_size</item>
|
<item name="android:textSize">@dimen/small_font_size</item>
|
||||||
<item name="android:textColor">@color/text</item>
|
<item name="android:textColor">@color/text</item>
|
||||||
<item name="android:textCursorDrawable">@drawable/session_edit_text_cursor</item>
|
<item name="android:textCursorDrawable">@drawable/session_edit_text_cursor</item>
|
||||||
|
<item name="android:textAlignment">viewStart</item>
|
||||||
<item name="android:maxLines">1</item>
|
<item name="android:maxLines">1</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -162,6 +164,7 @@
|
|||||||
<item name="android:textSize">@dimen/small_font_size</item>
|
<item name="android:textSize">@dimen/small_font_size</item>
|
||||||
<item name="android:textColor">@color/text</item>
|
<item name="android:textColor">@color/text</item>
|
||||||
<item name="android:textCursorDrawable">@drawable/session_edit_text_cursor</item>
|
<item name="android:textCursorDrawable">@drawable/session_edit_text_cursor</item>
|
||||||
|
<item name="android:textAlignment">viewStart</item>
|
||||||
<item name="android:maxLines">1</item>
|
<item name="android:maxLines">1</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -61,14 +61,13 @@ import org.thoughtcrime.securesms.logging.PersistentLogger;
|
|||||||
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
||||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker;
|
import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker;
|
||||||
import org.thoughtcrime.securesms.loki.api.LokiPublicChatManager;
|
|
||||||
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
|
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
|
||||||
|
import org.thoughtcrime.securesms.loki.api.PublicChatManager;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.EphemeralMessage;
|
import org.thoughtcrime.securesms.loki.protocol.PushSessionRequestMessageSendJob;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
|
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
|
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
|
||||||
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
|
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
@ -102,14 +101,14 @@ import org.whispersystems.signalservice.loki.api.Poller;
|
|||||||
import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement;
|
import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement;
|
||||||
import org.whispersystems.signalservice.loki.api.SnodeAPI;
|
import org.whispersystems.signalservice.loki.api.SnodeAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.SwarmAPI;
|
import org.whispersystems.signalservice.loki.api.SwarmAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.shelved.p2p.LokiP2PAPI;
|
import org.whispersystems.signalservice.loki.api.shelved.p2p.LokiP2PAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.shelved.p2p.LokiP2PAPIDelegate;
|
import org.whispersystems.signalservice.loki.api.shelved.p2p.LokiP2PAPIDelegate;
|
||||||
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol;
|
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol;
|
||||||
import org.whispersystems.signalservice.loki.protocol.friendrequests.FriendRequestProtocol;
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
||||||
|
import org.whispersystems.signalservice.loki.protocol.meta.TTLUtilities;
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink;
|
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink;
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
|
||||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
||||||
@ -154,9 +153,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
|
|
||||||
// Loki
|
// Loki
|
||||||
public MessageNotifier messageNotifier = null;
|
public MessageNotifier messageNotifier = null;
|
||||||
public Poller lokiPoller = null;
|
public Poller poller = null;
|
||||||
public LokiPublicChatManager lokiPublicChatManager = null;
|
public PublicChatManager publicChatManager = null;
|
||||||
private LokiPublicChatAPI lokiPublicChatAPI = null;
|
private PublicChatAPI publicChatAPI = null;
|
||||||
public Broadcaster broadcaster = null;
|
public Broadcaster broadcaster = null;
|
||||||
public SignalCommunicationModule communicationModule;
|
public SignalCommunicationModule communicationModule;
|
||||||
|
|
||||||
@ -185,11 +184,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
|
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
|
||||||
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
||||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
LokiSessionResetImplementation sessionResetImpl = new LokiSessionResetImplementation(this);
|
SessionResetImplementation sessionResetImpl = new SessionResetImplementation(this);
|
||||||
if (userPublicKey != null) {
|
if (userPublicKey != null) {
|
||||||
SwarmAPI.Companion.configureIfNeeded(apiDB);
|
SwarmAPI.Companion.configureIfNeeded(apiDB);
|
||||||
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
||||||
FriendRequestProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
|
||||||
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
|
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
|
||||||
SessionMetaProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
SessionMetaProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
||||||
SyncMessagesProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
SyncMessagesProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
||||||
@ -201,11 +199,11 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
if (setUpStorageAPIIfNeeded()) {
|
if (setUpStorageAPIIfNeeded()) {
|
||||||
if (userPublicKey != null) {
|
if (userPublicKey != null) {
|
||||||
Set<DeviceLink> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey);
|
Set<DeviceLink> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey);
|
||||||
LokiFileServerAPI.shared.setDeviceLinks(deviceLinks);
|
FileServerAPI.shared.setDeviceLinks(deviceLinks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resubmitProfilePictureIfNeeded();
|
resubmitProfilePictureIfNeeded();
|
||||||
lokiPublicChatManager = new LokiPublicChatManager(this);
|
publicChatManager = new PublicChatManager(this);
|
||||||
updateOpenGroupProfilePicturesIfNeeded();
|
updateOpenGroupProfilePicturesIfNeeded();
|
||||||
registerForFCMIfNeeded(false);
|
registerForFCMIfNeeded(false);
|
||||||
// ========
|
// ========
|
||||||
@ -229,10 +227,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
executePendingContactSync();
|
executePendingContactSync();
|
||||||
KeyCachingService.onAppForegrounded(this);
|
KeyCachingService.onAppForegrounded(this);
|
||||||
// Loki
|
// Loki
|
||||||
if (lokiPoller != null) { lokiPoller.setCaughtUp(false); }
|
if (poller != null) { poller.setCaughtUp(false); }
|
||||||
startPollingIfNeeded();
|
startPollingIfNeeded();
|
||||||
lokiPublicChatManager.markAllAsNotCaughtUp();
|
publicChatManager.markAllAsNotCaughtUp();
|
||||||
lokiPublicChatManager.startPollersIfNeeded();
|
publicChatManager.startPollersIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -242,8 +240,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
KeyCachingService.onAppBackgrounded(this);
|
KeyCachingService.onAppBackgrounded(this);
|
||||||
messageNotifier.setVisibleThread(-1);
|
messageNotifier.setVisibleThread(-1);
|
||||||
// Loki
|
// Loki
|
||||||
if (lokiPoller != null) { lokiPoller.stopIfNeeded(); }
|
if (poller != null) { poller.stopIfNeeded(); }
|
||||||
if (lokiPublicChatManager != null) { lokiPublicChatManager.stopPollers(); }
|
if (publicChatManager != null) { publicChatManager.stopPollers(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -284,15 +282,15 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loki
|
// Loki
|
||||||
public @Nullable LokiPublicChatAPI getLokiPublicChatAPI() {
|
public @Nullable PublicChatAPI getPublicChatAPI() {
|
||||||
if (lokiPublicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) { return lokiPublicChatAPI; }
|
if (publicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) { return publicChatAPI; }
|
||||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
if (userPublicKey== null) { return lokiPublicChatAPI; }
|
if (userPublicKey== null) { return publicChatAPI; }
|
||||||
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
|
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
|
||||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
|
||||||
lokiPublicChatAPI = new LokiPublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB);
|
publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB);
|
||||||
return lokiPublicChatAPI;
|
return publicChatAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeSecurityProvider() {
|
private void initializeSecurityProvider() {
|
||||||
@ -446,8 +444,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base)));
|
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ProviderInitializationException extends RuntimeException {
|
private static class ProviderInitializationException extends RuntimeException { }
|
||||||
}
|
|
||||||
|
|
||||||
// region Loki
|
// region Loki
|
||||||
public boolean setUpStorageAPIIfNeeded() {
|
public boolean setUpStorageAPIIfNeeded() {
|
||||||
@ -456,7 +453,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
boolean isDebugMode = BuildConfig.DEBUG;
|
boolean isDebugMode = BuildConfig.DEBUG;
|
||||||
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
|
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
|
||||||
LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
LokiFileServerAPI.Companion.configure(isDebugMode, userPublicKey, userPrivateKey, apiDB);
|
FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,16 +492,16 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
private void setUpPollingIfNeeded() {
|
private void setUpPollingIfNeeded() {
|
||||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
if (userPublicKey == null) return;
|
if (userPublicKey == null) return;
|
||||||
if (lokiPoller != null) {
|
if (poller != null) {
|
||||||
SnodeAPI.shared.setUserPublicKey(userPublicKey);
|
SnodeAPI.shared.setUserPublicKey(userPublicKey);
|
||||||
lokiPoller.setUserPublicKey(userPublicKey);
|
poller.setUserPublicKey(userPublicKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
Context context = this;
|
Context context = this;
|
||||||
SwarmAPI.Companion.configureIfNeeded(apiDB);
|
SwarmAPI.Companion.configureIfNeeded(apiDB);
|
||||||
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
||||||
lokiPoller = new Poller(userPublicKey, apiDB, protos -> {
|
poller = new Poller(userPublicKey, apiDB, protos -> {
|
||||||
for (SignalServiceProtos.Envelope proto : protos) {
|
for (SignalServiceProtos.Envelope proto : protos) {
|
||||||
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto), false);
|
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto), false);
|
||||||
}
|
}
|
||||||
@ -514,12 +511,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
|
|
||||||
public void startPollingIfNeeded() {
|
public void startPollingIfNeeded() {
|
||||||
setUpPollingIfNeeded();
|
setUpPollingIfNeeded();
|
||||||
if (lokiPoller != null) { lokiPoller.startIfNeeded(); }
|
if (poller != null) { poller.startIfNeeded(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopPolling() {
|
public void stopPolling() {
|
||||||
if (lokiPoller == null) { return; }
|
if (poller == null) { return; }
|
||||||
lokiPoller.stopIfNeeded();
|
poller.stopIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resubmitProfilePictureIfNeeded() {
|
private void resubmitProfilePictureIfNeeded() {
|
||||||
@ -534,7 +531,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
try {
|
try {
|
||||||
File profilePicture = AvatarHelper.getAvatarFile(this, Address.fromSerialized(userPublicKey));
|
File profilePicture = AvatarHelper.getAvatarFile(this, Address.fromSerialized(userPublicKey));
|
||||||
StreamDetails stream = new StreamDetails(new FileInputStream(profilePicture), "image/jpeg", profilePicture.length());
|
StreamDetails stream = new StreamDetails(new FileInputStream(profilePicture), "image/jpeg", profilePicture.length());
|
||||||
LokiFileServerAPI.shared.uploadProfilePicture(LokiFileServerAPI.shared.getServer(), profileKey, stream, () -> {
|
FileServerAPI.shared.uploadProfilePicture(FileServerAPI.shared.getServer(), profileKey, stream, () -> {
|
||||||
TextSecurePreferences.setLastProfilePictureUpload(this, new Date().getTime());
|
TextSecurePreferences.setLastProfilePictureUpload(this, new Date().getTime());
|
||||||
TextSecurePreferences.setProfileAvatarId(this, new SecureRandom().nextInt());
|
TextSecurePreferences.setProfileAvatarId(this, new SecureRandom().nextInt());
|
||||||
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey);
|
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey);
|
||||||
@ -548,9 +545,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
|
|
||||||
public void updateOpenGroupProfilePicturesIfNeeded() {
|
public void updateOpenGroupProfilePicturesIfNeeded() {
|
||||||
AsyncTask.execute(() -> {
|
AsyncTask.execute(() -> {
|
||||||
LokiPublicChatAPI publicChatAPI = null;
|
PublicChatAPI publicChatAPI = null;
|
||||||
try {
|
try {
|
||||||
publicChatAPI = getLokiPublicChatAPI();
|
publicChatAPI = getPublicChatAPI();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
@ -589,8 +586,19 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
Runtime.getRuntime().exit(0);
|
Runtime.getRuntime().exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasSentSessionRequestExpired(@NotNull String publicKey) {
|
||||||
|
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
|
Long timestamp = apiDB.getSessionRequestSentTimestamp(publicKey);
|
||||||
|
if (timestamp != null) {
|
||||||
|
long expiration = timestamp + TTLUtilities.getTTL(TTLUtilities.MessageType.SessionRequest);
|
||||||
|
return new Date().getTime() > expiration;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendSessionRequest(@NotNull String publicKey) {
|
public void sendSessionRequestIfNeeded(@NotNull String publicKey) {
|
||||||
// It's never necessary to establish a session with self
|
// It's never necessary to establish a session with self
|
||||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
if (publicKey.equals(userPublicKey)) { return; }
|
if (publicKey.equals(userPublicKey)) { return; }
|
||||||
@ -598,14 +606,19 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
SignalProtocolAddress address = new SignalProtocolAddress(publicKey, SignalServiceAddress.DEFAULT_DEVICE_ID);
|
SignalProtocolAddress address = new SignalProtocolAddress(publicKey, SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||||
boolean hasSession = new TextSecureSessionStore(this).containsSession(address);
|
boolean hasSession = new TextSecureSessionStore(this).containsSession(address);
|
||||||
if (hasSession) { return; }
|
if (hasSession) { return; }
|
||||||
// Check that we didn't already send or process a session request
|
// Check that we didn't already send a session request
|
||||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
|
||||||
boolean hasSentOrProcessedSessionRequest = (apiDB.getSessionRequestTimestamp(publicKey) != null);
|
boolean hasSentSessionRequest = (apiDB.getSessionRequestSentTimestamp(publicKey) != null);
|
||||||
if (hasSentOrProcessedSessionRequest) { return; }
|
boolean hasSentSessionRequestExpired = hasSentSessionRequestExpired(publicKey);
|
||||||
|
if (hasSentSessionRequestExpired) {
|
||||||
|
apiDB.setSessionRequestSentTimestamp(publicKey, 0);
|
||||||
|
}
|
||||||
|
if (hasSentSessionRequest && !hasSentSessionRequestExpired) { return; }
|
||||||
// Send the session request
|
// Send the session request
|
||||||
DatabaseFactory.getLokiAPIDatabase(this).setSessionRequestTimestamp(publicKey, new Date().getTime());
|
long timestamp = new Date().getTime();
|
||||||
EphemeralMessage sessionRequest = EphemeralMessage.createSessionRequest(publicKey);
|
apiDB.setSessionRequestSentTimestamp(publicKey, timestamp);
|
||||||
jobManager.add(new PushEphemeralMessageSendJob(sessionRequest));
|
PushSessionRequestMessageSendJob job = new PushSessionRequestMessageSendJob(publicKey, timestamp);
|
||||||
|
jobManager.add(job);
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,8 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
|||||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
|
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -386,7 +386,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
|||||||
Context context = CreateProfileActivity.this;
|
Context context = CreateProfileActivity.this;
|
||||||
|
|
||||||
TextSecurePreferences.setProfileName(context, name);
|
TextSecurePreferences.setProfileName(context, name);
|
||||||
LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(context).getLokiPublicChatAPI();
|
PublicChatAPI publicChatAPI = ApplicationContext.getInstance(context).getPublicChatAPI();
|
||||||
if (publicChatAPI != null) {
|
if (publicChatAPI != null) {
|
||||||
Set<String> servers = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChatServers();
|
Set<String> servers = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChatServers();
|
||||||
for (String server : servers) {
|
for (String server : servers) {
|
||||||
@ -409,7 +409,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
|||||||
// Loki - Upload the profile photo here
|
// Loki - Upload the profile photo here
|
||||||
if (avatar != null) {
|
if (avatar != null) {
|
||||||
Log.d("Loki", "Start uploading profile photo");
|
Log.d("Loki", "Start uploading profile photo");
|
||||||
LokiFileServerAPI storageAPI = LokiFileServerAPI.shared;
|
FileServerAPI storageAPI = FileServerAPI.shared;
|
||||||
LokiDotNetAPI.UploadResult result = storageAPI.uploadProfilePicture(storageAPI.getServer(), profileKey, avatar, () -> {
|
LokiDotNetAPI.UploadResult result = storageAPI.uploadProfilePicture(storageAPI.getServer(), profileKey, avatar, () -> {
|
||||||
TextSecurePreferences.setLastProfilePictureUpload(CreateProfileActivity.this, new Date().getTime());
|
TextSecurePreferences.setLastProfilePictureUpload(CreateProfileActivity.this, new Date().getTime());
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
|
@ -322,7 +322,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
|
Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
|
||||||
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_FRIENDS);
|
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_CONTACTS);
|
||||||
startActivityForResult(intent, PICK_CONTACT);
|
startActivityForResult(intent, PICK_CONTACT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_FRIENDS);
|
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_CONTACTS);
|
||||||
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
|
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
|
||||||
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
|||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener
|
|||||||
|
|
||||||
long threadID = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(conversationRecipient);
|
long threadID = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(conversationRecipient);
|
||||||
String senderHexEncodedPublicKey = author.getAddress().serialize();
|
String senderHexEncodedPublicKey = author.getAddress().serialize();
|
||||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadID);
|
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadID);
|
||||||
if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) {
|
if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) {
|
||||||
quoteeDisplayName = TextSecurePreferences.getProfileName(getContext());
|
quoteeDisplayName = TextSecurePreferences.getProfileName(getContext());
|
||||||
} else if (publicChat != null) {
|
} else if (publicChat != null) {
|
||||||
|
@ -84,7 +84,7 @@ public class TypingStatusSender {
|
|||||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
Recipient recipient = threadDatabase.getRecipientForThreadId(threadId);
|
Recipient recipient = threadDatabase.getRecipientForThreadId(threadId);
|
||||||
// Loki - Check whether we want to send a typing indicator to this user
|
// Loki - Check whether we want to send a typing indicator to this user
|
||||||
if (!SessionMetaProtocol.shouldSendTypingIndicator(recipient, context)) { return; }
|
if (recipient != null && !SessionMetaProtocol.shouldSendTypingIndicator(recipient.getAddress())) { return; }
|
||||||
// Loki - Take into account multi device
|
// Loki - Take into account multi device
|
||||||
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.getAddress().serialize());
|
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.getAddress().serialize());
|
||||||
for (String device : linkedDevices) {
|
for (String device : linkedDevices) {
|
||||||
|
@ -68,7 +68,6 @@ import android.view.View.OnFocusChangeListener;
|
|||||||
import android.view.View.OnKeyListener;
|
import android.view.View.OnKeyListener;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
@ -81,7 +80,6 @@ import com.annimon.stream.Stream;
|
|||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.ExpirationDialog;
|
import org.thoughtcrime.securesms.ExpirationDialog;
|
||||||
import org.thoughtcrime.securesms.GroupCreateActivity;
|
import org.thoughtcrime.securesms.GroupCreateActivity;
|
||||||
@ -159,10 +157,8 @@ import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
|||||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate;
|
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
||||||
import org.thoughtcrime.securesms.loki.views.FriendRequestViewDelegate;
|
|
||||||
import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView;
|
import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView;
|
||||||
import org.thoughtcrime.securesms.loki.views.SessionRestoreBannerView;
|
import org.thoughtcrime.securesms.loki.views.SessionRestoreBannerView;
|
||||||
import org.thoughtcrime.securesms.mediasend.Media;
|
import org.thoughtcrime.securesms.mediasend.Media;
|
||||||
@ -187,7 +183,6 @@ import org.thoughtcrime.securesms.mms.StickerSlide;
|
|||||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||||
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
|
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
|
||||||
@ -227,12 +222,11 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
|||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
import org.whispersystems.libsignal.InvalidMessageException;
|
import org.whispersystems.libsignal.InvalidMessageException;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.Mention;
|
import org.whispersystems.signalservice.loki.protocol.mentions.Mention;
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus;
|
|
||||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation;
|
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -273,8 +267,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
ComposeText.CursorPositionChangedListener,
|
ComposeText.CursorPositionChangedListener,
|
||||||
ConversationSearchBottomBar.EventListener,
|
ConversationSearchBottomBar.EventListener,
|
||||||
StickerKeyboardProvider.StickerEventListener,
|
StickerKeyboardProvider.StickerEventListener,
|
||||||
LokiThreadDatabaseDelegate,
|
LokiThreadDatabaseDelegate
|
||||||
FriendRequestViewDelegate
|
|
||||||
{
|
{
|
||||||
private static final String TAG = ConversationActivity.class.getSimpleName();
|
private static final String TAG = ConversationActivity.class.getSimpleName();
|
||||||
|
|
||||||
@ -387,7 +380,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
getWindow().getDecorView().setBackgroundColor(color);
|
getWindow().getDecorView().setBackgroundColor(color);
|
||||||
|
|
||||||
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale());
|
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale());
|
||||||
fragment.friendRequestViewDelegate = this;
|
|
||||||
|
|
||||||
registerMessageStatusObserver("calculatingPoW");
|
registerMessageStatusObserver("calculatingPoW");
|
||||||
registerMessageStatusObserver("contactingNetwork");
|
registerMessageStatusObserver("contactingNetwork");
|
||||||
@ -465,9 +457,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this);
|
MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this);
|
||||||
|
|
||||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
||||||
if (publicChat != null) {
|
if (publicChat != null) {
|
||||||
ApplicationContext.getInstance(this).getLokiPublicChatAPI().getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success( displayName -> {
|
ApplicationContext.getInstance(this).getPublicChatAPI().getChannelInfo(publicChat.getChannel(), publicChat.getServer()).success(displayName -> {
|
||||||
updateSubtitleTextView();
|
updateSubtitleTextView();
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
@ -555,7 +547,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
DatabaseFactory.getLokiThreadDatabase(this).setDelegate(this);
|
DatabaseFactory.getLokiThreadDatabase(this).setDelegate(this);
|
||||||
|
|
||||||
updateInputPanel();
|
inputPanel.setHint("Message");
|
||||||
|
|
||||||
updateSessionRestoreBanner();
|
updateSessionRestoreBanner();
|
||||||
|
|
||||||
@ -761,10 +753,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isSingleConversation()) {
|
if (isSingleConversation()) {
|
||||||
/*
|
if (recipient.isBlocked()) {
|
||||||
if (isSecureText) inflater.inflate(R.menu.conversation_callable_secure, menu);
|
inflater.inflate(R.menu.conversation_unblock, menu);
|
||||||
else inflater.inflate(R.menu.conversation_callable_insecure, menu);
|
} else {
|
||||||
*/
|
inflater.inflate(R.menu.conversation_block, menu);
|
||||||
|
}
|
||||||
} else if (isGroupConversation() && !isOpenGroupOrRSSFeed) {
|
} else if (isGroupConversation() && !isOpenGroupOrRSSFeed) {
|
||||||
inflater.inflate(R.menu.conversation_group_options, menu);
|
inflater.inflate(R.menu.conversation_group_options, menu);
|
||||||
|
|
||||||
@ -870,6 +863,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_call_secure: handleDial(getRecipient(), true); return true;
|
case R.id.menu_call_secure: handleDial(getRecipient(), true); return true;
|
||||||
case R.id.menu_call_insecure: handleDial(getRecipient(), false); return true;
|
case R.id.menu_call_insecure: handleDial(getRecipient(), false); return true;
|
||||||
|
case R.id.menu_unblock: handleUnblock(); return true;
|
||||||
|
case R.id.menu_block: handleBlock(); return true;
|
||||||
case R.id.menu_view_media: handleViewMedia(); return true;
|
case R.id.menu_view_media: handleViewMedia(); return true;
|
||||||
case R.id.menu_add_shortcut: handleAddShortcut(); return true;
|
case R.id.menu_add_shortcut: handleAddShortcut(); return true;
|
||||||
case R.id.menu_search: handleSearch(); return true;
|
case R.id.menu_search: handleSearch(); return true;
|
||||||
@ -994,12 +989,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
int titleRes = R.string.ConversationActivity_unblock_this_contact_question;
|
int titleRes = R.string.ConversationActivity_unblock_this_contact_question;
|
||||||
int bodyRes = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact;
|
int bodyRes = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact;
|
||||||
|
|
||||||
if (recipient.isGroupRecipient()) {
|
|
||||||
titleRes = R.string.ConversationActivity_unblock_this_group_question;
|
|
||||||
bodyRes = R.string.ConversationActivity_unblock_this_group_description;
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection CodeBlock2Expr
|
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle(titleRes)
|
.setTitle(titleRes)
|
||||||
.setMessage(bodyRes)
|
.setMessage(bodyRes)
|
||||||
@ -1078,6 +1067,33 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleBlock() {
|
||||||
|
int titleRes = R.string.RecipientPreferenceActivity_block_this_contact_question;
|
||||||
|
int bodyRes = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact;
|
||||||
|
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle(titleRes)
|
||||||
|
.setMessage(bodyRes)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.RecipientPreferenceActivity_block, (dialog, which) -> {
|
||||||
|
new AsyncTask<Void, Void, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
DatabaseFactory.getRecipientDatabase(ConversationActivity.this)
|
||||||
|
.setBlocked(recipient, true);
|
||||||
|
|
||||||
|
ApplicationContext.getInstance(ConversationActivity.this)
|
||||||
|
.getJobManager()
|
||||||
|
.add(new MultiDeviceBlockedUpdateJob());
|
||||||
|
|
||||||
|
Util.runOnMain(() -> finish());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
|
||||||
private void handleViewMedia() {
|
private void handleViewMedia() {
|
||||||
Intent intent = new Intent(this, MediaOverviewActivity.class);
|
Intent intent = new Intent(this, MediaOverviewActivity.class);
|
||||||
intent.putExtra(MediaOverviewActivity.ADDRESS_EXTRA, recipient.getAddress());
|
intent.putExtra(MediaOverviewActivity.ADDRESS_EXTRA, recipient.getAddress());
|
||||||
@ -2172,7 +2188,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
try {
|
try {
|
||||||
int startIndex = result.indexOf("@" + mention.getDisplayName());
|
int startIndex = result.indexOf("@" + mention.getDisplayName());
|
||||||
int endIndex = startIndex + mention.getDisplayName().length() + 1; // + 1 to include the @
|
int endIndex = startIndex + mention.getDisplayName().length() + 1; // + 1 to include the @
|
||||||
result = result.substring(0, startIndex) + "@" + mention.getHexEncodedPublicKey() + result.substring(endIndex);
|
result = result.substring(0, startIndex) + "@" + mention.getPublicKey() + result.substring(endIndex);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
Log.d("Loki", "Couldn't process mention due to error: " + exception.toString() + ".");
|
Log.d("Loki", "Couldn't process mention due to error: " + exception.toString() + ".");
|
||||||
}
|
}
|
||||||
@ -2253,26 +2269,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
updateLinkPreviewState();
|
updateLinkPreviewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleThreadFriendRequestStatusChanged(long threadID) {
|
|
||||||
if (recipient.isGroupRecipient()) { return; }
|
|
||||||
boolean isUpdateNeeded = false;
|
|
||||||
if (threadID == this.threadId) {
|
|
||||||
isUpdateNeeded = true;
|
|
||||||
} else {
|
|
||||||
String thisThreadPublicKey = recipient.getAddress().serialize();
|
|
||||||
Set<String> thisThreadAssociatedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(thisThreadPublicKey);
|
|
||||||
Recipient changedThreadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID);
|
|
||||||
String changedThreadPublicKey = changedThreadRecipient.getAddress().serialize();
|
|
||||||
for (String device : thisThreadAssociatedDevices) {
|
|
||||||
if (device.equals(changedThreadPublicKey)) { isUpdateNeeded = true; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isUpdateNeeded) {
|
|
||||||
updateInputPanel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleSessionRestoreDevicesChanged(long threadID) {
|
public void handleSessionRestoreDevicesChanged(long threadID) {
|
||||||
if (threadID == this.threadId) {
|
if (threadID == this.threadId) {
|
||||||
@ -2280,21 +2276,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInputPanel() {
|
|
||||||
boolean shouldInputPanelBeEnabled = FriendRequestProtocol.shouldInputPanelBeEnabled(this, recipient);
|
|
||||||
Util.runOnMain(() -> {
|
|
||||||
updateToggleButtonState();
|
|
||||||
String hint = shouldInputPanelBeEnabled ? "Message" : "Pending session request";
|
|
||||||
inputPanel.setHint(hint);
|
|
||||||
inputPanel.setEnabled(shouldInputPanelBeEnabled);
|
|
||||||
if (shouldInputPanelBeEnabled && inputPanel.getVisibility() == View.VISIBLE) {
|
|
||||||
inputPanel.composeText.requestFocus();
|
|
||||||
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
inputMethodManager.showSoftInput(inputPanel.composeText, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessage() {
|
private void sendMessage() {
|
||||||
if (inputPanel.isRecordingInLockedMode()) {
|
if (inputPanel.isRecordingInLockedMode()) {
|
||||||
inputPanel.releaseRecordingLock();
|
inputPanel.releaseRecordingLock();
|
||||||
@ -2396,11 +2377,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
outgoingMessage = outgoingMessageCandidate;
|
outgoingMessage = outgoingMessageCandidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Send a friend request if we're not yet friends with the user in question
|
|
||||||
LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId);
|
|
||||||
outgoingMessage.isFriendRequest = !isGroupConversation() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS
|
|
||||||
&& !SessionMetaProtocol.shared.isNoteToSelf(recipient.getAddress().serialize()); // Needed for stageOutgoingMessage(...)
|
|
||||||
|
|
||||||
if (clearComposeBox) {
|
if (clearComposeBox) {
|
||||||
inputPanel.clearQuote();
|
inputPanel.clearQuote();
|
||||||
attachmentManager.clear(glideRequests, false);
|
attachmentManager.clear(glideRequests, false);
|
||||||
@ -2409,6 +2385,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
final long id = fragment.stageOutgoingMessage(outgoingMessage);
|
final long id = fragment.stageOutgoingMessage(outgoingMessage);
|
||||||
|
|
||||||
|
if (!recipient.isGroupRecipient()) {
|
||||||
|
ApplicationContext.getInstance(this).sendSessionRequestIfNeeded(recipient.getAddress().serialize());
|
||||||
|
}
|
||||||
|
|
||||||
new AsyncTask<Void, Void, Long>() {
|
new AsyncTask<Void, Void, Long>() {
|
||||||
@Override
|
@Override
|
||||||
protected Long doInBackground(Void... param) {
|
protected Long doInBackground(Void... param) {
|
||||||
@ -2449,14 +2429,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
message = new OutgoingTextMessage(recipient, messageBody, expiresIn, subscriptionId);
|
message = new OutgoingTextMessage(recipient, messageBody, expiresIn, subscriptionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Send a friend request if we're not yet friends with the user in question
|
|
||||||
LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId);
|
|
||||||
message.isFriendRequest = !isGroupConversation() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS
|
|
||||||
&& !SessionMetaProtocol.shared.isNoteToSelf(recipient.getAddress().serialize()); // Needed for stageOutgoingMessage(...)
|
|
||||||
|
|
||||||
silentlySetComposeText("");
|
silentlySetComposeText("");
|
||||||
final long id = fragment.stageOutgoingMessage(message);
|
final long id = fragment.stageOutgoingMessage(message);
|
||||||
|
|
||||||
|
if (!recipient.isGroupRecipient()) {
|
||||||
|
ApplicationContext.getInstance(this).sendSessionRequestIfNeeded(recipient.getAddress().serialize());
|
||||||
|
}
|
||||||
|
|
||||||
new AsyncTask<OutgoingTextMessage, Void, Long>() {
|
new AsyncTask<OutgoingTextMessage, Void, Long>() {
|
||||||
@Override
|
@Override
|
||||||
protected Long doInBackground(OutgoingTextMessage... messages) {
|
protected Long doInBackground(OutgoingTextMessage... messages) {
|
||||||
@ -2483,13 +2462,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateToggleButtonState() {
|
private void updateToggleButtonState() {
|
||||||
if (!FriendRequestProtocol.shouldAttachmentButtonBeEnabled(this, recipient)) {
|
|
||||||
buttonToggle.display(sendButton);
|
|
||||||
quickAttachmentToggle.hide();
|
|
||||||
inlineAttachmentToggle.hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputPanel.isRecordingInLockedMode()) {
|
if (inputPanel.isRecordingInLockedMode()) {
|
||||||
buttonToggle.display(sendButton);
|
buttonToggle.display(sendButton);
|
||||||
quickAttachmentToggle.show();
|
quickAttachmentToggle.show();
|
||||||
@ -3107,7 +3079,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
muteIndicatorImageView.setVisibility(View.VISIBLE);
|
muteIndicatorImageView.setVisibility(View.VISIBLE);
|
||||||
subtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()));
|
subtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()));
|
||||||
} else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) {
|
} else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) {
|
||||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
||||||
if (publicChat != null) {
|
if (publicChat != null) {
|
||||||
Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer());
|
Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer());
|
||||||
if (userCount == null) { userCount = 0; }
|
if (userCount == null) { userCount = 0; }
|
||||||
@ -3193,19 +3165,5 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
updateSubtitleTextView();
|
updateSubtitleTextView();
|
||||||
updateMessageStatusProgressBar();
|
updateMessageStatusProgressBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void acceptFriendRequest(@NotNull MessageRecord friendRequest) {
|
|
||||||
if (recipient.isGroupRecipient()) { return; }
|
|
||||||
FriendRequestProtocol.acceptFriendRequest(this, recipient);
|
|
||||||
updateInputPanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void rejectFriendRequest(@NotNull MessageRecord friendRequest) {
|
|
||||||
if (recipient.isGroupRecipient()) { return; }
|
|
||||||
FriendRequestProtocol.rejectFriendRequest(this, recipient);
|
|
||||||
updateInputPanel();
|
|
||||||
}
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
|||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.views.FriendRequestViewDelegate;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
@ -108,8 +107,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
|||||||
private MessageRecord recordToPulseHighlight;
|
private MessageRecord recordToPulseHighlight;
|
||||||
private String searchQuery;
|
private String searchQuery;
|
||||||
|
|
||||||
public FriendRequestViewDelegate friendRequestViewDelegate; // Loki
|
|
||||||
|
|
||||||
protected static class ViewHolder extends RecyclerView.ViewHolder {
|
protected static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
public <V extends View & BindableConversationItem> ViewHolder(final @NonNull V itemView) {
|
public <V extends View & BindableConversationItem> ViewHolder(final @NonNull V itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
@ -202,11 +199,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
|||||||
MessageRecord previousRecord = adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1) ? getRecordForPositionOrThrow(adapterPosition + 1) : null;
|
MessageRecord previousRecord = adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1) ? getRecordForPositionOrThrow(adapterPosition + 1) : null;
|
||||||
MessageRecord nextRecord = adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1) ? getRecordForPositionOrThrow(adapterPosition - 1) : null;
|
MessageRecord nextRecord = adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1) ? getRecordForPositionOrThrow(adapterPosition - 1) : null;
|
||||||
|
|
||||||
BindableConversationItem conversationItem = viewHolder.getView();
|
viewHolder.getView().bind(messageRecord,
|
||||||
if (conversationItem instanceof ConversationItem) {
|
|
||||||
((ConversationItem)conversationItem).friendRequestViewDelegate = this.friendRequestViewDelegate;
|
|
||||||
}
|
|
||||||
conversationItem.bind(messageRecord,
|
|
||||||
Optional.fromNullable(previousRecord),
|
Optional.fromNullable(previousRecord),
|
||||||
Optional.fromNullable(nextRecord),
|
Optional.fromNullable(nextRecord),
|
||||||
glideRequests,
|
glideRequests,
|
||||||
|
@ -79,7 +79,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
|||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.views.FriendRequestViewDelegate;
|
|
||||||
import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
|
import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
|
||||||
import org.thoughtcrime.securesms.mediasend.Media;
|
import org.thoughtcrime.securesms.mediasend.Media;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
@ -101,8 +100,8 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
|||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -151,7 +150,6 @@ public class ConversationFragment extends Fragment
|
|||||||
private View composeDivider;
|
private View composeDivider;
|
||||||
private View scrollToBottomButton;
|
private View scrollToBottomButton;
|
||||||
private TextView scrollDateHeader;
|
private TextView scrollDateHeader;
|
||||||
public FriendRequestViewDelegate friendRequestViewDelegate; // Loki
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
@ -399,7 +397,7 @@ public class ConversationFragment extends Fragment
|
|||||||
boolean isGroupChat = recipient.isGroupRecipient();
|
boolean isGroupChat = recipient.isGroupRecipient();
|
||||||
|
|
||||||
if (isGroupChat) {
|
if (isGroupChat) {
|
||||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
|
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
|
||||||
boolean isPublicChat = (publicChat != null);
|
boolean isPublicChat = (publicChat != null);
|
||||||
int selectedMessageCount = messageRecords.size();
|
int selectedMessageCount = messageRecords.size();
|
||||||
boolean areAllSentByUser = true;
|
boolean areAllSentByUser = true;
|
||||||
@ -409,7 +407,7 @@ public class ConversationFragment extends Fragment
|
|||||||
menu.findItem(R.id.menu_context_copy_public_key).setVisible(isPublicChat && selectedMessageCount == 1 && !areAllSentByUser);
|
menu.findItem(R.id.menu_context_copy_public_key).setVisible(isPublicChat && selectedMessageCount == 1 && !areAllSentByUser);
|
||||||
menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1);
|
menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1);
|
||||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
||||||
boolean userCanModerate = isPublicChat && LokiPublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
|
boolean userCanModerate = isPublicChat && PublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
|
||||||
boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate);
|
boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate);
|
||||||
menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible);
|
menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible);
|
||||||
} else {
|
} else {
|
||||||
@ -502,7 +500,7 @@ public class ConversationFragment extends Fragment
|
|||||||
builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount));
|
builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount));
|
||||||
builder.setCancelable(true);
|
builder.setCancelable(true);
|
||||||
|
|
||||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
|
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
|
||||||
|
|
||||||
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -518,7 +516,7 @@ public class ConversationFragment extends Fragment
|
|||||||
ArrayList<Long> ignoredMessages = new ArrayList<>();
|
ArrayList<Long> ignoredMessages = new ArrayList<>();
|
||||||
ArrayList<Long> failedMessages = new ArrayList<>();
|
ArrayList<Long> failedMessages = new ArrayList<>();
|
||||||
boolean isSentByUser = true;
|
boolean isSentByUser = true;
|
||||||
LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getLokiPublicChatAPI();
|
PublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getPublicChatAPI();
|
||||||
for (MessageRecord messageRecord : messageRecords) {
|
for (MessageRecord messageRecord : messageRecords) {
|
||||||
isSentByUser = isSentByUser && messageRecord.isOutgoing();
|
isSentByUser = isSentByUser && messageRecord.isOutgoing();
|
||||||
Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id);
|
Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id);
|
||||||
@ -706,7 +704,6 @@ public class ConversationFragment extends Fragment
|
|||||||
if (adapter == null) {
|
if (adapter == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
adapter.friendRequestViewDelegate = this.friendRequestViewDelegate;
|
|
||||||
|
|
||||||
if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) {
|
if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) {
|
||||||
adapter.setFooterView(topLoadMoreView);
|
adapter.setFooterView(topLoadMoreView);
|
||||||
|
@ -86,10 +86,7 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
|
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
|
||||||
import org.thoughtcrime.securesms.loki.views.FriendRequestView;
|
|
||||||
import org.thoughtcrime.securesms.loki.views.FriendRequestViewDelegate;
|
|
||||||
import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
|
import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
@ -112,8 +109,8 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -158,7 +155,6 @@ public class ConversationItem extends LinearLayout
|
|||||||
private ViewGroup contactPhotoHolder;
|
private ViewGroup contactPhotoHolder;
|
||||||
private AlertView alertView;
|
private AlertView alertView;
|
||||||
private ViewGroup container;
|
private ViewGroup container;
|
||||||
private FriendRequestView friendRequestView;
|
|
||||||
|
|
||||||
private @NonNull Set<MessageRecord> batchSelected = new HashSet<>();
|
private @NonNull Set<MessageRecord> batchSelected = new HashSet<>();
|
||||||
private Recipient conversationRecipient;
|
private Recipient conversationRecipient;
|
||||||
@ -182,8 +178,6 @@ public class ConversationItem extends LinearLayout
|
|||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
public FriendRequestViewDelegate friendRequestViewDelegate; // Loki
|
|
||||||
|
|
||||||
public ConversationItem(Context context) {
|
public ConversationItem(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
@ -223,7 +217,6 @@ public class ConversationItem extends LinearLayout
|
|||||||
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
||||||
this.quoteView = findViewById(R.id.quote_view);
|
this.quoteView = findViewById(R.id.quote_view);
|
||||||
this.container = findViewById(R.id.container);
|
this.container = findViewById(R.id.container);
|
||||||
this.friendRequestView = findViewById(R.id.friend_request_view);
|
|
||||||
|
|
||||||
setOnClickListener(new ClickListener(null));
|
setOnClickListener(new ClickListener(null));
|
||||||
|
|
||||||
@ -269,7 +262,6 @@ public class ConversationItem extends LinearLayout
|
|||||||
setQuote(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
setQuote(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||||
setMessageSpacing(context, messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
setMessageSpacing(context, messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||||
setFooter(messageRecord, nextMessageRecord, locale, groupThread);
|
setFooter(messageRecord, nextMessageRecord, locale, groupThread);
|
||||||
setFriendRequestView(messageRecord);
|
|
||||||
adjustMarginsIfNeeded(messageRecord);
|
adjustMarginsIfNeeded(messageRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -807,8 +799,8 @@ public class ConversationItem extends LinearLayout
|
|||||||
layoutParams.setMarginStart((groupThread && !isRSSFeed) ? groupThreadMargin : defaultMargin);
|
layoutParams.setMarginStart((groupThread && !isRSSFeed) ? groupThreadMargin : defaultMargin);
|
||||||
bodyBubble.setLayoutParams(layoutParams);
|
bodyBubble.setLayoutParams(layoutParams);
|
||||||
if (profilePictureView == null) return;
|
if (profilePictureView == null) return;
|
||||||
profilePictureView.setHexEncodedPublicKey(recipient.getAddress().toString());
|
profilePictureView.setPublicKey(recipient.getAddress().toString());
|
||||||
profilePictureView.setAdditionalHexEncodedPublicKey(null);
|
profilePictureView.setAdditionalPublicKey(null);
|
||||||
profilePictureView.setRSSFeed(false);
|
profilePictureView.setRSSFeed(false);
|
||||||
profilePictureView.setGlide(glideRequests);
|
profilePictureView.setGlide(glideRequests);
|
||||||
profilePictureView.update();
|
profilePictureView.update();
|
||||||
@ -920,11 +912,6 @@ public class ConversationItem extends LinearLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setFriendRequestView(@NonNull MessageRecord record) {
|
|
||||||
friendRequestView.setDelegate(friendRequestViewDelegate);
|
|
||||||
friendRequestView.update(record);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConversationItemFooter getActiveFooter(@NonNull MessageRecord messageRecord) {
|
private ConversationItemFooter getActiveFooter(@NonNull MessageRecord messageRecord) {
|
||||||
if (hasSticker(messageRecord)) {
|
if (hasSticker(messageRecord)) {
|
||||||
return stickerFooter;
|
return stickerFooter;
|
||||||
@ -1001,9 +988,9 @@ public class ConversationItem extends LinearLayout
|
|||||||
profilePictureView.setVisibility(VISIBLE);
|
profilePictureView.setVisibility(VISIBLE);
|
||||||
int visibility = View.GONE;
|
int visibility = View.GONE;
|
||||||
|
|
||||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId());
|
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId());
|
||||||
if (publicChat != null) {
|
if (publicChat != null) {
|
||||||
boolean isModerator = LokiPublicChatAPI.Companion.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer());
|
boolean isModerator = PublicChatAPI.Companion.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer());
|
||||||
visibility = isModerator ? View.VISIBLE : View.GONE;
|
visibility = isModerator ? View.VISIBLE : View.GONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1066,14 +1053,6 @@ public class ConversationItem extends LinearLayout
|
|||||||
int spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_collapse);
|
int spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_collapse);
|
||||||
int spacingBottom = spacingTop;
|
int spacingBottom = spacingTop;
|
||||||
|
|
||||||
boolean isOutgoingStack = current.isOutgoing() && previous.orNull() != null && previous.get().isOutgoing();
|
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
|
||||||
boolean isPreviousMessageFriendRequest = previous.orNull() != null && lokiMessageDatabase.isFriendRequest(previous.get().id);
|
|
||||||
|
|
||||||
if (isOutgoingStack && isPreviousMessageFriendRequest) {
|
|
||||||
spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_default);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isStartOfMessageCluster(current, previous, isGroupThread)) {
|
if (isStartOfMessageCluster(current, previous, isGroupThread)) {
|
||||||
spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_default);
|
spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_default);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class RecipientDatabase extends Database {
|
public class RecipientDatabase extends Database {
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ public class RecipientDatabase extends Database {
|
|||||||
|
|
||||||
static final String TABLE_NAME = "recipient_preferences";
|
static final String TABLE_NAME = "recipient_preferences";
|
||||||
private static final String ID = "_id";
|
private static final String ID = "_id";
|
||||||
static final String ADDRESS = "recipient_ids";
|
public static final String ADDRESS = "recipient_ids";
|
||||||
private static final String BLOCK = "block";
|
private static final String BLOCK = "block";
|
||||||
private static final String NOTIFICATION = "notification";
|
private static final String NOTIFICATION = "notification";
|
||||||
private static final String VIBRATE = "vibrate";
|
private static final String VIBRATE = "vibrate";
|
||||||
|
@ -885,7 +885,7 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
|
0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
|
||||||
threadId, 0, new LinkedList<IdentityKeyMismatch>(),
|
threadId, 0, new LinkedList<IdentityKeyMismatch>(),
|
||||||
message.getSubscriptionId(), message.getExpiresIn(),
|
message.getSubscriptionId(), message.getExpiresIn(),
|
||||||
System.currentTimeMillis(), 0, false, message.isFriendRequest);
|
System.currentTimeMillis(), 0, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -934,15 +934,12 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
List<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument);
|
List<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument);
|
||||||
Recipient recipient = Recipient.from(context, address, true);
|
Recipient recipient = Recipient.from(context, address, true);
|
||||||
|
|
||||||
// Loki - Check to see if this message was a friend request
|
|
||||||
boolean isFriendRequest = DatabaseFactory.getLokiMessageDatabase(context).isFriendRequest(messageId);
|
|
||||||
|
|
||||||
return new SmsMessageRecord(messageId, body, recipient,
|
return new SmsMessageRecord(messageId, body, recipient,
|
||||||
recipient,
|
recipient,
|
||||||
addressDeviceId,
|
addressDeviceId,
|
||||||
dateSent, dateReceived, deliveryReceiptCount, type,
|
dateSent, dateReceived, deliveryReceiptCount, type,
|
||||||
threadId, status, mismatches, subscriptionId,
|
threadId, status, mismatches, subscriptionId,
|
||||||
expiresIn, expireStarted, readReceiptCount, unidentified, isFriendRequest);
|
expiresIn, expireStarted, readReceiptCount, unidentified);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IdentityKeyMismatch> getMismatches(String document) {
|
private List<IdentityKeyMismatch> getMismatches(String document) {
|
||||||
|
@ -45,7 +45,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
|||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat;
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@ -83,8 +83,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
private static final int lokiV7 = 28;
|
private static final int lokiV7 = 28;
|
||||||
private static final int lokiV8 = 29;
|
private static final int lokiV8 = 29;
|
||||||
private static final int lokiV9 = 30;
|
private static final int lokiV9 = 30;
|
||||||
|
private static final int lokiV10 = 31;
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = lokiV9; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
private static final int DATABASE_VERSION = lokiV10; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||||
private static final String DATABASE_NAME = "signal.db";
|
private static final String DATABASE_NAME = "signal.db";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@ -143,12 +144,13 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL(LokiAPIDatabase.getCreateDeviceLinkCacheCommand());
|
db.execSQL(LokiAPIDatabase.getCreateDeviceLinkCacheCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateUserCountCacheCommand());
|
db.execSQL(LokiAPIDatabase.getCreateUserCountCacheCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateSessionRequestTimestampCacheCommand());
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestTimestampCacheCommand());
|
||||||
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampCacheCommand());
|
||||||
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampCacheCommand());
|
||||||
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
|
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
|
||||||
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
|
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
|
||||||
db.execSQL(LokiMessageDatabase.getCreateMessageFriendRequestTableCommand());
|
db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
|
||||||
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
|
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
|
||||||
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
|
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
|
||||||
db.execSQL(LokiThreadDatabase.getCreateFriendRequestTableCommand());
|
|
||||||
db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand());
|
db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand());
|
||||||
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
||||||
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
|
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
|
||||||
@ -545,7 +547,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
try (Cursor lokiPublicChatCursor = db.rawQuery("SELECT public_chat FROM loki_public_chat_database", null)) {
|
try (Cursor lokiPublicChatCursor = db.rawQuery("SELECT public_chat FROM loki_public_chat_database", null)) {
|
||||||
while (lokiPublicChatCursor != null && lokiPublicChatCursor.moveToNext()) {
|
while (lokiPublicChatCursor != null && lokiPublicChatCursor.moveToNext()) {
|
||||||
String chatString = lokiPublicChatCursor.getString(0);
|
String chatString = lokiPublicChatCursor.getString(0);
|
||||||
LokiPublicChat publicChat = LokiPublicChat.fromJSON(chatString);
|
PublicChat publicChat = PublicChat.fromJSON(chatString);
|
||||||
if (publicChat != null) {
|
if (publicChat != null) {
|
||||||
byte[] groupId = publicChat.getId().getBytes();
|
byte[] groupId = publicChat.getId().getBytes();
|
||||||
String oldId = GroupUtil.getEncodedId(groupId, false);
|
String oldId = GroupUtil.getEncodedId(groupId, false);
|
||||||
@ -590,6 +592,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathCacheCommand());
|
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathCacheCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < lokiV10) {
|
||||||
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampCacheCommand());
|
||||||
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampCacheCommand());
|
||||||
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
|
@ -39,9 +39,6 @@ import network.loki.messenger.R;
|
|||||||
*/
|
*/
|
||||||
public class SmsMessageRecord extends MessageRecord {
|
public class SmsMessageRecord extends MessageRecord {
|
||||||
|
|
||||||
// Loki
|
|
||||||
private final boolean isFriendRequest;
|
|
||||||
|
|
||||||
public SmsMessageRecord(long id,
|
public SmsMessageRecord(long id,
|
||||||
String body, Recipient recipient,
|
String body, Recipient recipient,
|
||||||
Recipient individualRecipient,
|
Recipient individualRecipient,
|
||||||
@ -52,35 +49,17 @@ public class SmsMessageRecord extends MessageRecord {
|
|||||||
int status, List<IdentityKeyMismatch> mismatches,
|
int status, List<IdentityKeyMismatch> mismatches,
|
||||||
int subscriptionId, long expiresIn, long expireStarted,
|
int subscriptionId, long expiresIn, long expireStarted,
|
||||||
int readReceiptCount, boolean unidentified)
|
int readReceiptCount, boolean unidentified)
|
||||||
{
|
|
||||||
this(id, body, recipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, deliveryReceiptCount, type, threadId, status, mismatches, subscriptionId, expiresIn, expireStarted, readReceiptCount, unidentified, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SmsMessageRecord(long id,
|
|
||||||
String body, Recipient recipient,
|
|
||||||
Recipient individualRecipient,
|
|
||||||
int recipientDeviceId,
|
|
||||||
long dateSent, long dateReceived,
|
|
||||||
int deliveryReceiptCount,
|
|
||||||
long type, long threadId,
|
|
||||||
int status, List<IdentityKeyMismatch> mismatches,
|
|
||||||
int subscriptionId, long expiresIn, long expireStarted,
|
|
||||||
int readReceiptCount, boolean unidentified, boolean isFriendRequest)
|
|
||||||
{
|
{
|
||||||
super(id, body, recipient, individualRecipient, recipientDeviceId,
|
super(id, body, recipient, individualRecipient, recipientDeviceId,
|
||||||
dateSent, dateReceived, threadId, status, deliveryReceiptCount, type,
|
dateSent, dateReceived, threadId, status, deliveryReceiptCount, type,
|
||||||
mismatches, new LinkedList<>(), subscriptionId,
|
mismatches, new LinkedList<>(), subscriptionId,
|
||||||
expiresIn, expireStarted, readReceiptCount, unidentified);
|
expiresIn, expireStarted, readReceiptCount, unidentified);
|
||||||
this.isFriendRequest = isFriendRequest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getType() {
|
public long getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki
|
|
||||||
public boolean isFriendRequest() { return isFriendRequest; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpannableString getDisplayBody(@NonNull Context context) {
|
public SpannableString getDisplayBody(@NonNull Context context) {
|
||||||
Recipient recipient = getRecipient();
|
Recipient recipient = getRecipient();
|
||||||
|
@ -45,7 +45,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
|
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
|
||||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
|
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
|
||||||
@ -152,12 +152,11 @@ public class SignalCommunicationModule {
|
|||||||
Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()),
|
Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()),
|
||||||
Optional.of(new MessageSenderEventListener(context)),
|
Optional.of(new MessageSenderEventListener(context)),
|
||||||
TextSecurePreferences.getLocalNumber(context),
|
TextSecurePreferences.getLocalNumber(context),
|
||||||
TextSecurePreferences.getMasterHexEncodedPublicKey(context),
|
|
||||||
DatabaseFactory.getLokiAPIDatabase(context),
|
DatabaseFactory.getLokiAPIDatabase(context),
|
||||||
DatabaseFactory.getLokiThreadDatabase(context),
|
DatabaseFactory.getLokiThreadDatabase(context),
|
||||||
DatabaseFactory.getLokiMessageDatabase(context),
|
DatabaseFactory.getLokiMessageDatabase(context),
|
||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context),
|
DatabaseFactory.getLokiPreKeyBundleDatabase(context),
|
||||||
new LokiSessionResetImplementation(context),
|
new SessionResetImplementation(context),
|
||||||
DatabaseFactory.getLokiUserDatabase(context),
|
DatabaseFactory.getLokiUserDatabase(context),
|
||||||
((ApplicationContext)context.getApplicationContext()).broadcaster);
|
((ApplicationContext)context.getApplicationContext()).broadcaster);
|
||||||
} else {
|
} else {
|
||||||
|
@ -27,7 +27,6 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -14,8 +14,8 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint
|
|||||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
|
import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
|
||||||
|
import org.thoughtcrime.securesms.loki.protocol.PushSessionRequestMessageSendJob;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -72,7 +72,7 @@ public final class JobManagerFactories {
|
|||||||
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
|
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
|
||||||
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
||||||
put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
|
put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
|
||||||
put(PushEphemeralMessageSendJob.KEY, new PushEphemeralMessageSendJob.Factory());
|
put(PushSessionRequestMessageSendJob.KEY, new PushSessionRequestMessageSendJob.Factory());
|
||||||
put(MultiDeviceOpenGroupUpdateJob.KEY, new MultiDeviceOpenGroupUpdateJob.Factory());
|
put(MultiDeviceOpenGroupUpdateJob.KEY, new MultiDeviceOpenGroupUpdateJob.Factory());
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (address == null) generateFullContactUpdate();
|
if (address == null) generateFullContactUpdate();
|
||||||
else if (SyncMessagesProtocol.shouldSyncContact(context, Address.fromSerialized(address))) generateSingleContactUpdate(Address.fromSerialized(address));
|
else if (SyncMessagesProtocol.shouldSyncContact(context, address)) generateSingleContactUpdate(Address.fromSerialized(address));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateSingleContactUpdate(@NonNull Address address)
|
private void generateSingleContactUpdate(@NonNull Address address)
|
||||||
@ -139,8 +139,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy
|
|||||||
Optional<IdentityDatabase.IdentityRecord> identityRecord = DatabaseFactory.getIdentityDatabase(context).getIdentity(address);
|
Optional<IdentityDatabase.IdentityRecord> identityRecord = DatabaseFactory.getIdentityDatabase(context).getIdentity(address);
|
||||||
Optional<VerifiedMessage> verifiedMessage = getVerifiedMessage(recipient, identityRecord);
|
Optional<VerifiedMessage> verifiedMessage = getVerifiedMessage(recipient, identityRecord);
|
||||||
|
|
||||||
// Loki - Only sync contacts we are friends with
|
if (SyncMessagesProtocol.shouldSyncContact(context, address.serialize())) {
|
||||||
if (SyncMessagesProtocol.shouldSyncContact(context, address)) {
|
|
||||||
out.write(new DeviceContact(address.toPhoneString(),
|
out.write(new DeviceContact(address.toPhoneString(),
|
||||||
Optional.fromNullable(recipient.getName()),
|
Optional.fromNullable(recipient.getName()),
|
||||||
getAvatar(recipient.getContactUri()),
|
getAvatar(recipient.getContactUri()),
|
||||||
|
@ -48,7 +48,6 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
|||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
@ -66,16 +65,11 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
|||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.EphemeralMessage;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
||||||
|
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SyncMessagesProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.SyncMessagesProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.PromiseUtilities;
|
import org.thoughtcrime.securesms.loki.utilities.PromiseUtilities;
|
||||||
@ -103,7 +97,7 @@ import org.thoughtcrime.securesms.util.Hex;
|
|||||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.libsignal.loki.LokiSessionResetProtocol;
|
import org.whispersystems.libsignal.loki.SessionResetProtocol;
|
||||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
@ -128,13 +122,11 @@ import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOper
|
|||||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI;
|
||||||
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
|
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus;
|
|
||||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation;
|
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation;
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -146,8 +138,6 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.loki.utilities.RecipientUtilitiesKt.recipient;
|
|
||||||
|
|
||||||
public class PushDecryptJob extends BaseJob implements InjectableType {
|
public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||||
|
|
||||||
public static final String KEY = "PushDecryptJob";
|
public static final String KEY = "PushDecryptJob";
|
||||||
@ -226,9 +216,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCanceled() {
|
public void onCanceled() { }
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void processMessage(@NonNull SignalServiceEnvelope envelope, boolean isPushNotification) {
|
public void processMessage(@NonNull SignalServiceEnvelope envelope, boolean isPushNotification) {
|
||||||
synchronized (PushReceivedJob.RECEIVE_LOCK) {
|
synchronized (PushReceivedJob.RECEIVE_LOCK) {
|
||||||
@ -265,30 +253,19 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
try {
|
try {
|
||||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||||
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
||||||
LokiSessionResetProtocol lokiSessionResetProtocol = new LokiSessionResetImplementation(context);
|
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
|
||||||
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
|
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
|
||||||
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiSessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
|
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
|
||||||
|
|
||||||
SignalServiceContent content = cipher.decrypt(envelope);
|
SignalServiceContent content = cipher.decrypt(envelope);
|
||||||
|
|
||||||
// Loki - Ignore any friend requests from before restoration
|
|
||||||
if (FriendRequestProtocol.isFriendRequestFromBeforeRestoration(context, content)) {
|
|
||||||
Log.d("Loki", "Ignoring friend request from before restoration.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldIgnore(content)) {
|
if (shouldIgnore(content)) {
|
||||||
Log.i(TAG, "Ignoring message.");
|
Log.i(TAG, "Ignoring message.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Handle pre key bundle message if needed
|
|
||||||
SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(context, content);
|
SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(context, content);
|
||||||
|
|
||||||
// Loki - Handle session request if needed
|
|
||||||
if (SessionManagementProtocol.handleSessionRequestIfNeeded(context, content)) { return; } // Don't process the message any further
|
|
||||||
|
|
||||||
// Loki - Handle profile update if needed
|
|
||||||
SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content);
|
SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content);
|
||||||
|
|
||||||
if (content.getDeviceLink().isPresent()) {
|
if (content.getDeviceLink().isPresent()) {
|
||||||
@ -297,15 +274,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
SignalServiceDataMessage message = content.getDataMessage().get();
|
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||||
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
||||||
|
|
||||||
// Loki - Handle unlinking request if needed
|
if (message.isDeviceUnlinkingRequest()) {
|
||||||
if (message.isUnlinkingRequest()) {
|
|
||||||
MultiDeviceProtocol.handleUnlinkingRequestIfNeeded(context, content);
|
MultiDeviceProtocol.handleUnlinkingRequestIfNeeded(context, content);
|
||||||
} else {
|
} else {
|
||||||
// Loki - Don't process session restoration requests any further
|
|
||||||
if (message.isSessionRestorationRequest()) { return; }
|
|
||||||
|
|
||||||
// Loki - Handle friend request acceptance if needed
|
|
||||||
FriendRequestProtocol.handleFriendRequestAcceptanceIfNeeded(context, content.getSender(), content);
|
|
||||||
|
|
||||||
if (message.isEndSession()) {
|
if (message.isEndSession()) {
|
||||||
handleEndSessionMessage(content, smsMessageId);
|
handleEndSessionMessage(content, smsMessageId);
|
||||||
@ -315,14 +286,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
handleExpirationUpdate(content, message, smsMessageId);
|
handleExpirationUpdate(content, message, smsMessageId);
|
||||||
} else if (isMediaMessage) {
|
} else if (isMediaMessage) {
|
||||||
handleMediaMessage(content, message, smsMessageId, Optional.absent());
|
handleMediaMessage(content, message, smsMessageId, Optional.absent());
|
||||||
|
|
||||||
// Loki - Handle friend request message if needed
|
|
||||||
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
|
|
||||||
} else if (message.getBody().isPresent()) {
|
} else if (message.getBody().isPresent()) {
|
||||||
handleTextMessage(content, message, smsMessageId, Optional.absent());
|
handleTextMessage(content, message, smsMessageId, Optional.absent());
|
||||||
|
|
||||||
// Loki - Handle friend request message if needed
|
|
||||||
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
|
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
|
||||||
@ -330,7 +295,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
|
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
|
||||||
handleProfileKey(content, message);
|
SessionMetaProtocol.handleProfileKeyUpdate(context, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.isNeedsReceipt()) {
|
if (content.isNeedsReceipt()) {
|
||||||
@ -350,6 +315,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
else if (syncMessage.getContacts().isPresent()) SyncMessagesProtocol.handleContactSyncMessage(context, content, syncMessage.getContacts().get());
|
else if (syncMessage.getContacts().isPresent()) SyncMessagesProtocol.handleContactSyncMessage(context, content, syncMessage.getContacts().get());
|
||||||
else if (syncMessage.getGroups().isPresent()) SyncMessagesProtocol.handleClosedGroupSyncMessage(context, content, syncMessage.getGroups().get());
|
else if (syncMessage.getGroups().isPresent()) SyncMessagesProtocol.handleClosedGroupSyncMessage(context, content, syncMessage.getGroups().get());
|
||||||
else if (syncMessage.getOpenGroups().isPresent()) SyncMessagesProtocol.handleOpenGroupSyncMessage(context, content, syncMessage.getOpenGroups().get());
|
else if (syncMessage.getOpenGroups().isPresent()) SyncMessagesProtocol.handleOpenGroupSyncMessage(context, content, syncMessage.getOpenGroups().get());
|
||||||
|
else if (syncMessage.getBlockedList().isPresent()) SyncMessagesProtocol.handleBlockedContactsSyncMessage(context, content, syncMessage.getBlockedList().get());
|
||||||
else Log.w(TAG, "Contains no known sync types...");
|
else Log.w(TAG, "Contains no known sync types...");
|
||||||
} else if (content.getCallMessage().isPresent()) {
|
} else if (content.getCallMessage().isPresent()) {
|
||||||
Log.i(TAG, "Got call message...");
|
Log.i(TAG, "Got call message...");
|
||||||
@ -367,29 +333,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
else if (message.isDeliveryReceipt()) handleDeliveryReceipt(content, message);
|
else if (message.isDeliveryReceipt()) handleDeliveryReceipt(content, message);
|
||||||
} else if (content.getTypingMessage().isPresent()) {
|
} else if (content.getTypingMessage().isPresent()) {
|
||||||
handleTypingMessage(content, content.getTypingMessage().get());
|
handleTypingMessage(content, content.getTypingMessage().get());
|
||||||
} else if (content.getNullMessage().isPresent()) {
|
|
||||||
|
|
||||||
// Loki - This is needed for compatibility with refactored desktop clients
|
|
||||||
// ========
|
|
||||||
if (content.isFriendRequest()) {
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushNullMessageSendJob(content.getSender()));
|
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Got unrecognized message...");
|
Log.w(TAG, "Got unrecognized message...");
|
||||||
}
|
}
|
||||||
Recipient recipient = recipient(context, content.getSender());
|
|
||||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
|
||||||
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(context);
|
|
||||||
LokiThreadFriendRequestStatus threadFriendRequestStatus = threadDB.getFriendRequestStatus(threadID);
|
|
||||||
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE || threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
|
||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
|
|
||||||
} else if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
|
||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
|
|
||||||
EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
|
|
||||||
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(content.getSender()));
|
|
||||||
}
|
|
||||||
// ========
|
|
||||||
}
|
|
||||||
|
|
||||||
resetRecipientToPush(Recipient.from(context, Address.fromSerialized(content.getSender()), false));
|
resetRecipientToPush(Recipient.from(context, Address.fromSerialized(content.getSender()), false));
|
||||||
|
|
||||||
@ -685,11 +631,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
|
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Handle profile key update if needed
|
SessionMetaProtocol.handleProfileKeyUpdate(context, content);
|
||||||
handleProfileKey(content, message.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Update profile if needed
|
|
||||||
SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content);
|
SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content);
|
||||||
|
|
||||||
if (threadId != null) {
|
if (threadId != null) {
|
||||||
@ -794,7 +738,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||||
database.beginTransaction();
|
database.beginTransaction();
|
||||||
|
|
||||||
// Loki - Ignore message if it has no body and no attachments
|
// Ignore message if it has no body and no attachments
|
||||||
if (mediaMessage.getBody().isEmpty() && mediaMessage.getAttachments().isEmpty() && mediaMessage.getLinkPreviews().isEmpty()) {
|
if (mediaMessage.getBody().isEmpty() && mediaMessage.getAttachments().isEmpty() && mediaMessage.getLinkPreviews().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -831,20 +775,28 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
messageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
messageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (insertResult.isPresent()) {
|
||||||
|
InsertResult result = insertResult.get();
|
||||||
|
|
||||||
|
// Loki - Cache the user hex encoded public key (for mentions)
|
||||||
|
MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(result.getThreadId(), context);
|
||||||
|
MentionsManager.shared.cache(content.getSender(), result.getThreadId());
|
||||||
|
|
||||||
// Loki - Store message open group server ID if needed
|
// Loki - Store message open group server ID if needed
|
||||||
if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) {
|
if (messageServerIDOrNull.isPresent()) {
|
||||||
long messageID = insertResult.get().getMessageId();
|
long messageID = result.getMessageId();
|
||||||
long messageServerID = messageServerIDOrNull.get();
|
long messageServerID = messageServerIDOrNull.get();
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||||
lokiMessageDatabase.setServerID(messageID, messageServerID);
|
lokiMessageDatabase.setServerID(messageID, messageServerID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Update mapping of message ID to original thread ID
|
// Loki - Update mapping of message ID to original thread ID
|
||||||
if (insertResult.isPresent()) {
|
if (result.getMessageId() > -1) {
|
||||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||||
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
||||||
lokiMessageDatabase.setOriginalThreadID(insertResult.get().getMessageId(), originalThreadId);
|
lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1008,17 +960,17 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
|
|
||||||
// Loki - Cache the user hex encoded public key (for mentions)
|
// Loki - Cache the user hex encoded public key (for mentions)
|
||||||
MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(result.getThreadId(), context);
|
MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(result.getThreadId(), context);
|
||||||
MentionsManager.shared.cache(textMessage.getSender().serialize(), result.getThreadId());
|
MentionsManager.shared.cache(content.getSender(), result.getThreadId());
|
||||||
|
|
||||||
// Loki - Store message server ID
|
// Loki - Store message open group server ID if needed
|
||||||
if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) {
|
if (messageServerIDOrNull.isPresent()) {
|
||||||
long messageID = insertResult.get().getMessageId();
|
long messageID = result.getMessageId();
|
||||||
long messageServerID = messageServerIDOrNull.get();
|
long messageServerID = messageServerIDOrNull.get();
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||||
lokiMessageDatabase.setServerID(messageID, messageServerID);
|
lokiMessageDatabase.setServerID(messageID, messageServerID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Update mapping of message to original thread ID
|
// Loki - Update mapping of message ID to original thread ID
|
||||||
if (result.getMessageId() > -1) {
|
if (result.getMessageId() > -1) {
|
||||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||||
@ -1177,28 +1129,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleProfileKey(@NonNull SignalServiceContent content,
|
|
||||||
@NonNull SignalServiceDataMessage message)
|
|
||||||
{
|
|
||||||
if (!message.getProfileKey().isPresent()) { return; }
|
|
||||||
|
|
||||||
/*
|
|
||||||
If we get a profile key then we don't need to map it to the primary device.
|
|
||||||
For now a profile key is mapped one-to-one to avoid secondary devices setting the incorrect avatar for a primary device.
|
|
||||||
*/
|
|
||||||
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
|
|
||||||
Recipient recipient = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
|
|
||||||
|
|
||||||
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.senderProfilePictureURL.or("");
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(recipient, url));
|
|
||||||
|
|
||||||
SessionMetaProtocol.handleProfileKeyUpdateIfNeeded(context, content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleNeedsDeliveryReceipt(@NonNull SignalServiceContent content,
|
private void handleNeedsDeliveryReceipt(@NonNull SignalServiceContent content,
|
||||||
@NonNull SignalServiceDataMessage message)
|
@NonNull SignalServiceDataMessage message)
|
||||||
{
|
{
|
||||||
@ -1432,7 +1362,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
// TODO: Burn this with fire when we can
|
// TODO: Burn this with fire when we can
|
||||||
PromiseUtilities.timeout(LokiFileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
|
PromiseUtilities.timeout(FileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
|
||||||
String masterPublicKey = org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
|
String masterPublicKey = org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
|
||||||
if (masterPublicKey == null) {
|
if (masterPublicKey == null) {
|
||||||
masterPublicKey = publicKey;
|
masterPublicKey = publicKey;
|
||||||
@ -1464,7 +1394,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
// TODO: Burn this with fire when we can
|
// TODO: Burn this with fire when we can
|
||||||
PromiseUtilities.timeout(LokiFileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
|
PromiseUtilities.timeout(FileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
|
||||||
String masterPublicKey = org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
|
String masterPublicKey = org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
|
||||||
if (masterPublicKey == null) {
|
if (masterPublicKey == null) {
|
||||||
masterPublicKey = publicKey;
|
masterPublicKey = publicKey;
|
||||||
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.jobs;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.annotation.WorkerThread;
|
import android.support.annotation.WorkerThread;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
@ -31,7 +30,6 @@ import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
|||||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.libsignal.state.PreKeyBundle;
|
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||||
@ -64,46 +62,34 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
|||||||
private static final String KEY_TEMPLATE_MESSAGE_ID = "template_message_id";
|
private static final String KEY_TEMPLATE_MESSAGE_ID = "template_message_id";
|
||||||
private static final String KEY_MESSAGE_ID = "message_id";
|
private static final String KEY_MESSAGE_ID = "message_id";
|
||||||
private static final String KEY_DESTINATION = "destination";
|
private static final String KEY_DESTINATION = "destination";
|
||||||
private static final String KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE = "is_friend_request";
|
|
||||||
private static final String KEY_CUSTOM_FR_MESSAGE = "custom_friend_request_message";
|
|
||||||
|
|
||||||
@Inject SignalServiceMessageSender messageSender;
|
@Inject SignalServiceMessageSender messageSender;
|
||||||
|
|
||||||
private long messageId; // The message ID
|
private long messageId;
|
||||||
private long templateMessageId; // The message ID of the message to template this send job from
|
private long templateMessageId;
|
||||||
|
private Address destination;
|
||||||
// Loki - Multi device
|
|
||||||
private Address destination; // Used to check whether this is another device we're sending to
|
|
||||||
private boolean isLokiPreKeyBundleMessage; // Whether this is a friend request / session request / device link message
|
|
||||||
private String customFriendRequestMessage; // If this isn't set then we use the message body
|
|
||||||
|
|
||||||
public PushMediaSendJob(long messageId, Address destination) {
|
public PushMediaSendJob(long messageId, Address destination) {
|
||||||
this(messageId, messageId, destination);
|
this(messageId, messageId, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PushMediaSendJob(long templateMessageId, long messageId, Address destination) {
|
public PushMediaSendJob(long templateMessageId, long messageId, Address destination) {
|
||||||
this(templateMessageId, messageId, destination, false, null);
|
this(constructParameters(destination), templateMessageId, messageId, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PushMediaSendJob(long templateMessageId, long messageId, Address destination, boolean isLokiPreKeyBundleMessage, String customFriendRequestMessage) {
|
private PushMediaSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination) {
|
||||||
this(constructParameters(destination), templateMessageId, messageId, destination, isLokiPreKeyBundleMessage, customFriendRequestMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PushMediaSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination, boolean isLokiPreKeyBundleMessage, String customFriendRequestMessage) {
|
|
||||||
super(parameters);
|
super(parameters);
|
||||||
this.templateMessageId = templateMessageId;
|
this.templateMessageId = templateMessageId;
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
this.isLokiPreKeyBundleMessage = isLokiPreKeyBundleMessage;
|
|
||||||
this.customFriendRequestMessage = customFriendRequestMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination) {
|
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination) {
|
||||||
enqueue(context, jobManager, messageId, messageId, destination, false, null);
|
enqueue(context, jobManager, messageId, messageId, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination, Boolean isFriendRequest, @Nullable String customFriendRequestMessage) {
|
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination) {
|
||||||
enqueue(context, jobManager, Collections.singletonList(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage)));
|
enqueue(context, jobManager, Collections.singletonList(new PushMediaSendJob(templateMessageId, messageId, destination)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
@ -144,14 +130,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Data serialize() {
|
public @NonNull Data serialize() {
|
||||||
Data.Builder builder = new Data.Builder()
|
return new Data.Builder()
|
||||||
.putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
|
.putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
|
||||||
.putLong(KEY_MESSAGE_ID, messageId)
|
.putLong(KEY_MESSAGE_ID, messageId)
|
||||||
.putString(KEY_DESTINATION, destination.serialize())
|
.putString(KEY_DESTINATION, destination.serialize()).build();
|
||||||
.putBoolean(KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE, isLokiPreKeyBundleMessage);
|
|
||||||
|
|
||||||
if (customFriendRequestMessage != null) { builder.putString(KEY_CUSTOM_FR_MESSAGE, customFriendRequestMessage); }
|
|
||||||
return builder.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -270,17 +252,8 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
|||||||
List<SharedContact> sharedContacts = getSharedContactsFor(message);
|
List<SharedContact> sharedContacts = getSharedContactsFor(message);
|
||||||
List<Preview> previews = getPreviewsFor(message);
|
List<Preview> previews = getPreviewsFor(message);
|
||||||
|
|
||||||
// Loki - Include a pre key bundle if needed
|
|
||||||
PreKeyBundle preKeyBundle;
|
|
||||||
if (isLokiPreKeyBundleMessage) {
|
|
||||||
preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.getNumber());
|
|
||||||
} else {
|
|
||||||
preKeyBundle = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String body = (isLokiPreKeyBundleMessage && customFriendRequestMessage != null) ? customFriendRequestMessage : message.getBody();
|
|
||||||
SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder()
|
SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder()
|
||||||
.withBody(body)
|
.withBody(message.getBody())
|
||||||
.withAttachments(serviceAttachments)
|
.withAttachments(serviceAttachments)
|
||||||
.withTimestamp(message.getSentTimeMillis())
|
.withTimestamp(message.getSentTimeMillis())
|
||||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||||
@ -290,8 +263,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
|||||||
.withSharedContacts(sharedContacts)
|
.withSharedContacts(sharedContacts)
|
||||||
.withPreviews(previews)
|
.withPreviews(previews)
|
||||||
.asExpirationUpdate(message.isExpirationUpdate())
|
.asExpirationUpdate(message.isExpirationUpdate())
|
||||||
.withPreKeyBundle(preKeyBundle)
|
|
||||||
.asFriendRequest(isLokiPreKeyBundleMessage)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) {
|
if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) {
|
||||||
@ -327,9 +298,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
|||||||
long templateMessageID = data.getLong(KEY_TEMPLATE_MESSAGE_ID);
|
long templateMessageID = data.getLong(KEY_TEMPLATE_MESSAGE_ID);
|
||||||
long messageID = data.getLong(KEY_MESSAGE_ID);
|
long messageID = data.getLong(KEY_MESSAGE_ID);
|
||||||
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
|
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
|
||||||
boolean isLokiPreKeyBundleMessage = data.getBoolean(KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE);
|
return new PushMediaSendJob(parameters, templateMessageID, messageID, destination);
|
||||||
String customFRMessage = data.hasString(KEY_CUSTOM_FR_MESSAGE) ? data.getString(KEY_CUSTOM_FR_MESSAGE) : null;
|
|
||||||
return new PushMediaSendJob(parameters, templateMessageID, messageID, destination, isLokiPreKeyBundleMessage, customFRMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ public abstract class PushReceivedJob extends BaseJob {
|
|||||||
|
|
||||||
if (envelope.isReceipt()) {
|
if (envelope.isReceipt()) {
|
||||||
handleReceipt(envelope);
|
handleReceipt(envelope);
|
||||||
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender() || envelope.isFriendRequest()) {
|
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender() || envelope.isFallbackMessage()) {
|
||||||
handleMessage(envelope, isPushNotification);
|
handleMessage(envelope, isPushNotification);
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
|
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
|
||||||
|
@ -16,7 +16,6 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
|
|||||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||||
@ -48,50 +47,34 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
|||||||
private static final String KEY_TEMPLATE_MESSAGE_ID = "template_message_id";
|
private static final String KEY_TEMPLATE_MESSAGE_ID = "template_message_id";
|
||||||
private static final String KEY_MESSAGE_ID = "message_id";
|
private static final String KEY_MESSAGE_ID = "message_id";
|
||||||
private static final String KEY_DESTINATION = "destination";
|
private static final String KEY_DESTINATION = "destination";
|
||||||
private static final String KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE = "is_friend_request";
|
|
||||||
private static final String KEY_CUSTOM_FR_MESSAGE = "custom_friend_request_message";
|
|
||||||
|
|
||||||
@Inject SignalServiceMessageSender messageSender;
|
@Inject SignalServiceMessageSender messageSender;
|
||||||
|
|
||||||
private long messageId; // The message ID
|
private long messageId;
|
||||||
private long templateMessageId; // The message ID of the message to template this send job from
|
private long templateMessageId;
|
||||||
|
private Address destination;
|
||||||
// Loki - Multi device
|
|
||||||
private Address destination; // Used to check whether this is another device we're sending to
|
|
||||||
private boolean isLokiPreKeyBundleMessage; // Whether this is a friend request / session request / device link message
|
|
||||||
private String customFriendRequestMessage; // If this isn't set then we use the message body
|
|
||||||
|
|
||||||
public PushTextSendJob(long messageId, Address destination) {
|
public PushTextSendJob(long messageId, Address destination) {
|
||||||
this(messageId, messageId, destination);
|
this(messageId, messageId, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PushTextSendJob(long templateMessageId, long messageId, Address destination) {
|
public PushTextSendJob(long templateMessageId, long messageId, Address destination) {
|
||||||
this(templateMessageId, messageId, destination, false, null);
|
this(constructParameters(destination), templateMessageId, messageId, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PushTextSendJob(long templateMessageId, long messageId, Address destination, boolean isLokiPreKeyBundleMessage, String customFriendRequestMessage) {
|
private PushTextSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination) {
|
||||||
this(constructParameters(destination), templateMessageId, messageId, destination, isLokiPreKeyBundleMessage, customFriendRequestMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PushTextSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination, boolean isLokiPreKeyBundleMessage, String customFriendRequestMessage) {
|
|
||||||
super(parameters);
|
super(parameters);
|
||||||
this.templateMessageId = templateMessageId;
|
this.templateMessageId = templateMessageId;
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
this.isLokiPreKeyBundleMessage = isLokiPreKeyBundleMessage;
|
|
||||||
this.customFriendRequestMessage = customFriendRequestMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Data serialize() {
|
public @NonNull Data serialize() {
|
||||||
Data.Builder builder = new Data.Builder()
|
return new Data.Builder()
|
||||||
.putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
|
.putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
|
||||||
.putLong(KEY_MESSAGE_ID, messageId)
|
.putLong(KEY_MESSAGE_ID, messageId)
|
||||||
.putString(KEY_DESTINATION, destination.serialize())
|
.putString(KEY_DESTINATION, destination.serialize()).build();
|
||||||
.putBoolean(KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE, isLokiPreKeyBundleMessage);
|
|
||||||
|
|
||||||
if (customFriendRequestMessage != null) { builder.putString(KEY_CUSTOM_FR_MESSAGE, customFriendRequestMessage); }
|
|
||||||
return builder.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -214,23 +197,18 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
|||||||
|
|
||||||
log(TAG, "Have access key to use: " + unidentifiedAccess.isPresent());
|
log(TAG, "Have access key to use: " + unidentifiedAccess.isPresent());
|
||||||
|
|
||||||
// Loki - Include a pre key bundle if needed
|
PreKeyBundle preKeyBundle = null;
|
||||||
PreKeyBundle preKeyBundle;
|
if (message.isEndSession()) {
|
||||||
if (isLokiPreKeyBundleMessage || message.isEndSession()) {
|
preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(destination.serialize());
|
||||||
preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.getNumber());
|
|
||||||
} else {
|
|
||||||
preKeyBundle = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String body = (isLokiPreKeyBundleMessage && customFriendRequestMessage != null) ? customFriendRequestMessage : message.getBody();
|
|
||||||
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
|
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
|
||||||
.withTimestamp(message.getDateSent())
|
.withTimestamp(message.getDateSent())
|
||||||
.withBody(body)
|
.withBody(message.getBody())
|
||||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||||
.withProfileKey(profileKey.orNull())
|
.withProfileKey(profileKey.orNull())
|
||||||
.asEndSessionMessage(message.isEndSession())
|
|
||||||
.asFriendRequest(isLokiPreKeyBundleMessage)
|
|
||||||
.withPreKeyBundle(preKeyBundle)
|
.withPreKeyBundle(preKeyBundle)
|
||||||
|
.asEndSessionMessage(message.isEndSession())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) {
|
if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) {
|
||||||
@ -263,9 +241,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
|||||||
long templateMessageID = data.getLong(KEY_TEMPLATE_MESSAGE_ID);
|
long templateMessageID = data.getLong(KEY_TEMPLATE_MESSAGE_ID);
|
||||||
long messageID = data.getLong(KEY_MESSAGE_ID);
|
long messageID = data.getLong(KEY_MESSAGE_ID);
|
||||||
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
|
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
|
||||||
boolean isLokiPreKeyBundleMessage = data.getBoolean(KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE);
|
return new PushTextSendJob(parameters, templateMessageID, messageID, destination);
|
||||||
String customFRMessage = data.hasString(KEY_CUSTOM_FR_MESSAGE) ? data.getString(KEY_CUSTOM_FR_MESSAGE) : null;
|
|
||||||
return new PushTextSendJob(parameters, templateMessageID, messageID, destination, isLokiPreKeyBundleMessage, customFRMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,8 @@ class CreateClosedGroupLoader(context: Context) : AsyncLoader<List<String>>(cont
|
|||||||
|
|
||||||
override fun loadInBackground(): List<String> {
|
override fun loadInBackground(): List<String> {
|
||||||
val contacts = ContactUtilities.getAllContacts(context)
|
val contacts = ContactUtilities.getAllContacts(context)
|
||||||
// Only show the master devices of the users we are friends with
|
|
||||||
return contacts.filter { contact ->
|
return contacts.filter { contact ->
|
||||||
!contact.recipient.isGroupRecipient && contact.isFriend && !contact.isOurDevice && !contact.isSlave
|
!contact.recipient.isGroupRecipient && !contact.isOurDevice && !contact.isSlave
|
||||||
}.map {
|
}.map {
|
||||||
it.recipient.address.toPhoneString()
|
it.recipient.address.toPhoneString()
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
package org.thoughtcrime.securesms.loki.activities
|
package org.thoughtcrime.securesms.loki.activities
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.arch.lifecycle.Observer
|
import android.arch.lifecycle.Observer
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.support.v4.app.LoaderManager
|
import android.support.v4.app.LoaderManager
|
||||||
import android.support.v4.content.Loader
|
import android.support.v4.content.Loader
|
||||||
|
import android.support.v4.content.LocalBroadcastManager
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
import android.support.v7.widget.RecyclerView
|
|
||||||
import android.support.v7.widget.helper.ItemTouchHelper
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
@ -31,36 +29,35 @@ import org.thoughtcrime.securesms.conversation.ConversationActivity
|
|||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
|
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob
|
||||||
|
import org.thoughtcrime.securesms.loki.dialogs.ConversationOptionsBottomSheet
|
||||||
import org.thoughtcrime.securesms.loki.dialogs.PNModeBottomSheet
|
import org.thoughtcrime.securesms.loki.dialogs.PNModeBottomSheet
|
||||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
|
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
|
||||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation
|
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
|
||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
import org.thoughtcrime.securesms.loki.utilities.*
|
||||||
import org.thoughtcrime.securesms.loki.views.ConversationView
|
import org.thoughtcrime.securesms.loki.views.ConversationView
|
||||||
import org.thoughtcrime.securesms.loki.views.NewConversationButtonSetViewDelegate
|
import org.thoughtcrime.securesms.loki.views.NewConversationButtonSetViewDelegate
|
||||||
import org.thoughtcrime.securesms.loki.views.SeedReminderViewDelegate
|
import org.thoughtcrime.securesms.loki.views.SeedReminderViewDelegate
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
import org.thoughtcrime.securesms.util.Util
|
||||||
import org.whispersystems.signalservice.loki.protocol.friendrequests.FriendRequestProtocol
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
|
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol
|
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.syncmessages.SyncMessagesProtocol
|
import org.whispersystems.signalservice.loki.protocol.syncmessages.SyncMessagesProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate {
|
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate {
|
||||||
private lateinit var glide: GlideRequests
|
private lateinit var glide: GlideRequests
|
||||||
|
private var broadcastReceiver: BroadcastReceiver? = null
|
||||||
|
|
||||||
private val hexEncodedPublicKey: String
|
private val publicKey: String
|
||||||
get() {
|
get() {
|
||||||
val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this)
|
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this)
|
||||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||||
return masterHexEncodedPublicKey ?: userHexEncodedPublicKey
|
return masterPublicKey ?: userPublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
@ -96,7 +93,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
glide = GlideApp.with(this)
|
glide = GlideApp.with(this)
|
||||||
// Set up toolbar buttons
|
// Set up toolbar buttons
|
||||||
profileButton.glide = glide
|
profileButton.glide = glide
|
||||||
profileButton.hexEncodedPublicKey = hexEncodedPublicKey
|
profileButton.publicKey = publicKey
|
||||||
profileButton.update()
|
profileButton.update()
|
||||||
profileButton.setOnClickListener { openSettings() }
|
profileButton.setOnClickListener { openSettings() }
|
||||||
pathStatusViewContainer.setOnClickListener { showPath() }
|
pathStatusViewContainer.setOnClickListener { showPath() }
|
||||||
@ -120,7 +117,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
homeAdapter.conversationClickListener = this
|
homeAdapter.conversationClickListener = this
|
||||||
recyclerView.adapter = homeAdapter
|
recyclerView.adapter = homeAdapter
|
||||||
recyclerView.layoutManager = LinearLayoutManager(this)
|
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
ItemTouchHelper(SwipeCallback(this)).attachToRecyclerView(recyclerView)
|
|
||||||
// Set up empty state view
|
// Set up empty state view
|
||||||
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
|
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
|
||||||
// This is a workaround for the fact that CursorRecyclerViewAdapter doesn't actually auto-update (even though it says it will)
|
// This is a workaround for the fact that CursorRecyclerViewAdapter doesn't actually auto-update (even though it says it will)
|
||||||
@ -158,46 +154,32 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
|
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
|
||||||
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||||
val sessionResetImpl = LokiSessionResetImplementation(this)
|
val sessionResetImpl = SessionResetImplementation(this)
|
||||||
if (userPublicKey != null) {
|
if (userPublicKey != null) {
|
||||||
FriendRequestProtocol.configureIfNeeded(apiDB, userPublicKey)
|
|
||||||
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
|
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
|
||||||
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
|
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
|
||||||
SyncMessagesProtocol.configureIfNeeded(apiDB, userPublicKey)
|
SyncMessagesProtocol.configureIfNeeded(apiDB, userPublicKey)
|
||||||
application.lokiPublicChatManager.startPollersIfNeeded()
|
application.publicChatManager.startPollersIfNeeded()
|
||||||
}
|
}
|
||||||
SessionManagementProtocol.configureIfNeeded(sessionResetImpl, threadDB, application)
|
SessionManagementProtocol.configureIfNeeded(sessionResetImpl, threadDB, application)
|
||||||
MultiDeviceProtocol.configureIfNeeded(apiDB)
|
MultiDeviceProtocol.configureIfNeeded(apiDB)
|
||||||
IP2Country.configureIfNeeded(this)
|
IP2Country.configureIfNeeded(this)
|
||||||
// Preload device links to make message sending quicker
|
// Preload device links to make message sending quicker
|
||||||
val publicKeys = ContactUtilities.getAllContacts(this).filter { contact ->
|
val publicKeys = ContactUtilities.getAllContacts(this).filter { contact ->
|
||||||
!contact.recipient.isGroupRecipient && contact.isFriend && !contact.isOurDevice && !contact.isSlave
|
!contact.recipient.isGroupRecipient && !contact.isOurDevice && !contact.isSlave
|
||||||
}.map {
|
}.map {
|
||||||
it.recipient.address.toPhoneString()
|
it.recipient.address.toPhoneString()
|
||||||
}.toSet()
|
}.toSet()
|
||||||
LokiFileServerAPI.shared.getDeviceLinks(publicKeys)
|
FileServerAPI.shared.getDeviceLinks(publicKeys)
|
||||||
// TODO: Temporary hack to unbork existing clients
|
// Observe blocked contacts changed events
|
||||||
val allContacts = DatabaseFactory.getRecipientDatabase(this).allAddresses.map {
|
val broadcastReceiver = object : BroadcastReceiver() {
|
||||||
MultiDeviceProtocol.shared.getMasterDevice(it.serialize()) ?: it.serialize()
|
|
||||||
}.toSet()
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val lokiMessageDB = DatabaseFactory.getLokiMessageDatabase(this)
|
recyclerView.adapter!!.notifyDataSetChanged()
|
||||||
for (contact in allContacts) {
|
|
||||||
val slaveDeviceHasPendingFR = MultiDeviceProtocol.shared.getSlaveDevices(contact).any {
|
|
||||||
val slaveDeviceThreadID = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(recipient(this, it))
|
|
||||||
DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(slaveDeviceThreadID) == LokiThreadFriendRequestStatus.REQUEST_RECEIVED
|
|
||||||
}
|
|
||||||
val masterDeviceThreadID = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(recipient(this, contact))
|
|
||||||
val masterDeviceHasNoPendingFR = (DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(masterDeviceThreadID) == LokiThreadFriendRequestStatus.NONE)
|
|
||||||
if (slaveDeviceHasPendingFR && masterDeviceHasNoPendingFR) {
|
|
||||||
val lastMessageID = org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol.getLastMessageID(this, masterDeviceThreadID)
|
|
||||||
if (lastMessageID != null) {
|
|
||||||
val lastMessageFRStatus = lokiMessageDB.getFriendRequestStatus(lastMessageID)
|
|
||||||
if (lastMessageFRStatus != LokiMessageFriendRequestStatus.REQUEST_PENDING) {
|
|
||||||
lokiMessageDB.setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.broadcastReceiver = broadcastReceiver
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -230,6 +212,14 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
createNewPrivateChat()
|
createNewPrivateChat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
val broadcastReceiver = this.broadcastReceiver
|
||||||
|
if (broadcastReceiver != null) {
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)
|
||||||
|
}
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Updating
|
// region Updating
|
||||||
@ -251,8 +241,105 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongConversationClick(view: ConversationView) {
|
override fun onLongConversationClick(view: ConversationView) {
|
||||||
|
val thread = view.thread ?: return
|
||||||
|
val bottomSheet = ConversationOptionsBottomSheet()
|
||||||
|
bottomSheet.recipient = thread.recipient
|
||||||
|
bottomSheet.onBlockOrUnblockTapped = {
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
if (thread.recipient.isBlocked) {
|
||||||
|
unblockConversation(thread)
|
||||||
|
} else {
|
||||||
|
blockConversation(thread)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bottomSheet.onDeleteTapped = {
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
deleteConversation(thread)
|
||||||
|
}
|
||||||
|
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun blockConversation(thread: ThreadRecord) {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.RecipientPreferenceActivity_block_this_contact_question)
|
||||||
|
.setMessage(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.RecipientPreferenceActivity_block) { dialog, _ ->
|
||||||
|
Thread {
|
||||||
|
DatabaseFactory.getRecipientDatabase(this).setBlocked(thread.recipient, true)
|
||||||
|
ApplicationContext.getInstance(this).jobManager.add(MultiDeviceBlockedUpdateJob())
|
||||||
|
Util.runOnMain {
|
||||||
|
recyclerView.adapter!!.notifyDataSetChanged()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unblockConversation(thread: ThreadRecord) {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.RecipientPreferenceActivity_unblock_this_contact_question)
|
||||||
|
.setMessage(R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.RecipientPreferenceActivity_unblock) { dialog, _ ->
|
||||||
|
Thread {
|
||||||
|
DatabaseFactory.getRecipientDatabase(this).setBlocked(thread.recipient, false)
|
||||||
|
ApplicationContext.getInstance(this).jobManager.add(MultiDeviceBlockedUpdateJob())
|
||||||
|
Util.runOnMain {
|
||||||
|
recyclerView.adapter!!.notifyDataSetChanged()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteConversation(thread: ThreadRecord) {
|
||||||
|
val threadID = thread.threadId
|
||||||
|
val recipient = thread.recipient
|
||||||
|
val threadDB = DatabaseFactory.getThreadDatabase(this)
|
||||||
|
val deleteThread = object : Runnable {
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
AsyncTask.execute {
|
||||||
|
val publicChat = DatabaseFactory.getLokiThreadDatabase(this@HomeActivity).getPublicChat(threadID)
|
||||||
|
if (publicChat != null) {
|
||||||
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(this@HomeActivity)
|
||||||
|
apiDB.removeLastMessageServerID(publicChat.channel, publicChat.server)
|
||||||
|
apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server)
|
||||||
|
ApplicationContext.getInstance(this@HomeActivity).publicChatAPI!!.leave(publicChat.channel, publicChat.server)
|
||||||
|
}
|
||||||
|
threadDB.deleteConversation(threadID)
|
||||||
|
ApplicationContext.getInstance(this@HomeActivity).messageNotifier.updateNotification(this@HomeActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val dialogMessage = if (recipient.isGroupRecipient) R.string.activity_home_leave_group_dialog_message else R.string.activity_home_delete_conversation_dialog_message
|
||||||
|
val dialog = AlertDialog.Builder(this)
|
||||||
|
dialog.setMessage(dialogMessage)
|
||||||
|
dialog.setPositiveButton(R.string.yes) { _, _ ->
|
||||||
|
val isClosedGroup = recipient.address.isClosedGroup
|
||||||
|
// Send a leave group message if this is an active closed group
|
||||||
|
if (isClosedGroup && DatabaseFactory.getGroupDatabase(this).isActive(recipient.address.toGroupString())) {
|
||||||
|
if (!ClosedGroupsProtocol.leaveGroup(this, recipient)) {
|
||||||
|
Toast.makeText(this, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show()
|
||||||
|
return@setPositiveButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Archive the conversation and then delete it after 10 seconds (the case where the
|
||||||
|
// app was closed before the conversation could be deleted is handled in onCreate)
|
||||||
|
threadDB.archiveConversation(threadID)
|
||||||
|
val delay = if (isClosedGroup) 10000L else 1000L
|
||||||
|
val handler = Handler()
|
||||||
|
handler.postDelayed(deleteThread, delay)
|
||||||
|
// Notify the user
|
||||||
|
val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message
|
||||||
|
Toast.makeText(this, toastMessage, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
dialog.setNegativeButton(R.string.no) { _, _ ->
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
dialog.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun openConversation(thread: ThreadRecord) {
|
private fun openConversation(thread: ThreadRecord) {
|
||||||
val intent = Intent(this, ConversationActivity::class.java)
|
val intent = Intent(this, ConversationActivity::class.java)
|
||||||
@ -289,92 +376,5 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
val intent = Intent(this, JoinPublicChatActivity::class.java)
|
val intent = Intent(this, JoinPublicChatActivity::class.java)
|
||||||
show(intent)
|
show(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SwipeCallback(val activity: HomeActivity) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
|
||||||
|
|
||||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
|
||||||
viewHolder as HomeAdapter.ViewHolder
|
|
||||||
val threadID = viewHolder.view.thread!!.threadId
|
|
||||||
val recipient = viewHolder.view.thread!!.recipient
|
|
||||||
val threadDatabase = DatabaseFactory.getThreadDatabase(activity)
|
|
||||||
val deleteThread = object : Runnable {
|
|
||||||
|
|
||||||
override fun run() {
|
|
||||||
AsyncTask.execute {
|
|
||||||
val publicChat = DatabaseFactory.getLokiThreadDatabase(activity).getPublicChat(threadID)
|
|
||||||
if (publicChat != null) {
|
|
||||||
val apiDatabase = DatabaseFactory.getLokiAPIDatabase(activity)
|
|
||||||
apiDatabase.removeLastMessageServerID(publicChat.channel, publicChat.server)
|
|
||||||
apiDatabase.removeLastDeletionServerID(publicChat.channel, publicChat.server)
|
|
||||||
ApplicationContext.getInstance(activity).lokiPublicChatAPI!!.leave(publicChat.channel, publicChat.server)
|
|
||||||
}
|
|
||||||
threadDatabase.deleteConversation(threadID)
|
|
||||||
ApplicationContext.getInstance(activity).messageNotifier.updateNotification(activity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val dialogMessage = if (recipient.isGroupRecipient) R.string.activity_home_leave_group_dialog_message else R.string.activity_home_delete_conversation_dialog_message
|
|
||||||
val dialog = AlertDialog.Builder(activity)
|
|
||||||
dialog.setMessage(dialogMessage)
|
|
||||||
dialog.setPositiveButton(R.string.yes) { _, _ ->
|
|
||||||
val isClosedGroup = recipient.address.isClosedGroup
|
|
||||||
// Send a leave group message if this is an active closed group
|
|
||||||
if (isClosedGroup && DatabaseFactory.getGroupDatabase(activity).isActive(recipient.address.toGroupString())) {
|
|
||||||
if (!ClosedGroupsProtocol.leaveGroup(activity, recipient)) {
|
|
||||||
Toast.makeText(activity, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show()
|
|
||||||
clearView(activity.recyclerView, viewHolder)
|
|
||||||
return@setPositiveButton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Archive the conversation and then delete it after 10 seconds (the case where the
|
|
||||||
// app was closed before the conversation could be deleted is handled in onCreate)
|
|
||||||
threadDatabase.archiveConversation(threadID)
|
|
||||||
val delay = if (isClosedGroup) 10000L else 1000L
|
|
||||||
val handler = Handler()
|
|
||||||
handler.postDelayed(deleteThread, delay)
|
|
||||||
// Notify the user
|
|
||||||
val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message
|
|
||||||
Toast.makeText(activity, toastMessage, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
dialog.setNegativeButton(R.string.no) { _, _ ->
|
|
||||||
clearView(activity.recyclerView, viewHolder)
|
|
||||||
}
|
|
||||||
dialog.create().show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dx: Float, dy: Float, actionState: Int, isCurrentlyActive: Boolean) {
|
|
||||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && dx < 0) {
|
|
||||||
val itemView = viewHolder.itemView
|
|
||||||
animate(viewHolder, dx)
|
|
||||||
val backgroundPaint = Paint()
|
|
||||||
backgroundPaint.color = activity.resources.getColorWithID(R.color.destructive, activity.theme)
|
|
||||||
c.drawRect(itemView.right.toFloat() - abs(dx), itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat(), backgroundPaint)
|
|
||||||
val icon = BitmapFactory.decodeResource(activity.resources, R.drawable.ic_trash_filled_32)
|
|
||||||
val iconPaint = Paint()
|
|
||||||
val left = itemView.right.toFloat() - abs(dx) + activity.resources.getDimension(R.dimen.medium_spacing)
|
|
||||||
val top = itemView.top.toFloat() + (itemView.bottom.toFloat() - itemView.top.toFloat() - icon.height) / 2
|
|
||||||
c.drawBitmap(icon, left, top, iconPaint)
|
|
||||||
} else {
|
|
||||||
super.onChildDraw(c, recyclerView, viewHolder, dx, dy, actionState, isCurrentlyActive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun animate(viewHolder: RecyclerView.ViewHolder, dx: Float) {
|
|
||||||
val alpha = 1.0f - abs(dx) / viewHolder.itemView.width.toFloat()
|
|
||||||
viewHolder.itemView.alpha = alpha
|
|
||||||
viewHolder.itemView.translationX = dx
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
|
||||||
super.clearView(recyclerView, viewHolder)
|
|
||||||
viewHolder.itemView.alpha = 1.0f
|
|
||||||
viewHolder.itemView.translationX = 0.0f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.database.IdentityDatabase
|
|||||||
import org.thoughtcrime.securesms.logging.Log
|
import org.thoughtcrime.securesms.logging.Log
|
||||||
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialog
|
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialog
|
||||||
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialogDelegate
|
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialogDelegate
|
||||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation
|
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||||
import org.thoughtcrime.securesms.loki.utilities.push
|
import org.thoughtcrime.securesms.loki.utilities.push
|
||||||
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
|
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
|
||||||
@ -27,7 +27,6 @@ import org.whispersystems.curve25519.Curve25519
|
|||||||
import org.whispersystems.libsignal.ecc.Curve
|
import org.whispersystems.libsignal.ecc.Curve
|
||||||
import org.whispersystems.libsignal.ecc.ECKeyPair
|
import org.whispersystems.libsignal.ecc.ECKeyPair
|
||||||
import org.whispersystems.libsignal.util.KeyHelper
|
import org.whispersystems.libsignal.util.KeyHelper
|
||||||
import org.whispersystems.signalservice.loki.protocol.friendrequests.FriendRequestProtocol
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
|
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
||||||
@ -110,8 +109,7 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
|
|||||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
|
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
|
||||||
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||||
val sessionResetImpl = LokiSessionResetImplementation(this)
|
val sessionResetImpl = SessionResetImplementation(this)
|
||||||
FriendRequestProtocol.configureIfNeeded(apiDB, userPublicKey)
|
|
||||||
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
|
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
|
||||||
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
|
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
|
||||||
org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.configureIfNeeded(apiDB)
|
org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.configureIfNeeded(apiDB)
|
||||||
@ -124,13 +122,13 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
|
|||||||
linkDeviceDialog.show(supportFragmentManager, "Link Device Dialog")
|
linkDeviceDialog.show(supportFragmentManager, "Link Device Dialog")
|
||||||
AsyncTask.execute {
|
AsyncTask.execute {
|
||||||
retryIfNeeded(8) {
|
retryIfNeeded(8) {
|
||||||
MultiDeviceProtocol.sendDeviceLinkMessage(this@LandingActivity, deviceLink.masterHexEncodedPublicKey, deviceLink)
|
MultiDeviceProtocol.sendDeviceLinkMessage(this@LandingActivity, deviceLink.masterPublicKey, deviceLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
|
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
|
||||||
TextSecurePreferences.setMasterHexEncodedPublicKey(this, deviceLink.masterHexEncodedPublicKey)
|
TextSecurePreferences.setMasterHexEncodedPublicKey(this, deviceLink.masterPublicKey)
|
||||||
val intent = Intent(this, HomeActivity::class.java)
|
val intent = Intent(this, HomeActivity::class.java)
|
||||||
show(intent)
|
show(intent)
|
||||||
finish()
|
finish()
|
||||||
|
@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.loki.utilities.recipient
|
|||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
@ -126,30 +126,30 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager
|
|||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(this)
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(this)
|
||||||
val deviceLinks = apiDB.getDeviceLinks(userPublicKey)
|
val deviceLinks = apiDB.getDeviceLinks(userPublicKey)
|
||||||
val deviceLink = deviceLinks.find { it.masterHexEncodedPublicKey == userPublicKey && it.slaveHexEncodedPublicKey == slaveDevicePublicKey }
|
val deviceLink = deviceLinks.find { it.masterPublicKey == userPublicKey && it.slavePublicKey == slaveDevicePublicKey }
|
||||||
if (deviceLink == null) {
|
if (deviceLink == null) {
|
||||||
return Toast.makeText(this, R.string.activity_linked_devices_unlinking_failed_message, Toast.LENGTH_LONG).show()
|
return Toast.makeText(this, R.string.activity_linked_devices_unlinking_failed_message, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
LokiFileServerAPI.shared.setDeviceLinks(setOf()).successUi {
|
FileServerAPI.shared.setDeviceLinks(setOf()).successUi {
|
||||||
DatabaseFactory.getLokiAPIDatabase(this).clearDeviceLinks(userPublicKey)
|
DatabaseFactory.getLokiAPIDatabase(this).clearDeviceLinks(userPublicKey)
|
||||||
deviceLinks.forEach { deviceLink ->
|
deviceLinks.forEach { deviceLink ->
|
||||||
// We don't use PushEphemeralMessageJob because want these messages to send before the pre key and
|
// We don't use PushEphemeralMessageJob because want these messages to send before the pre key and
|
||||||
// session associated with the slave device have been deleted
|
// session associated with the slave device have been deleted
|
||||||
val unlinkingRequest = SignalServiceDataMessage.newBuilder()
|
val unlinkingRequest = SignalServiceDataMessage.newBuilder()
|
||||||
.withTimestamp(System.currentTimeMillis())
|
.withTimestamp(System.currentTimeMillis())
|
||||||
.asUnlinkingRequest(true)
|
.asDeviceUnlinkingRequest(true)
|
||||||
val messageSender = ApplicationContext.getInstance(this@LinkedDevicesActivity).communicationModule.provideSignalMessageSender()
|
val messageSender = ApplicationContext.getInstance(this@LinkedDevicesActivity).communicationModule.provideSignalMessageSender()
|
||||||
val address = SignalServiceAddress(deviceLink.slaveHexEncodedPublicKey)
|
val address = SignalServiceAddress(deviceLink.slavePublicKey)
|
||||||
try {
|
try {
|
||||||
val udAccess = UnidentifiedAccessUtil.getAccessFor(this@LinkedDevicesActivity, recipient(this@LinkedDevicesActivity, deviceLink.slaveHexEncodedPublicKey))
|
val udAccess = UnidentifiedAccessUtil.getAccessFor(this@LinkedDevicesActivity, recipient(this@LinkedDevicesActivity, deviceLink.slavePublicKey))
|
||||||
messageSender.sendMessage(0, address, udAccess, unlinkingRequest.build()) // The message ID doesn't matter
|
messageSender.sendMessage(0, address, udAccess, unlinkingRequest.build()) // The message ID doesn't matter
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Failed to send unlinking request due to error: $e.")
|
Log.d("Loki", "Failed to send unlinking request due to error: $e.")
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)
|
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slavePublicKey)
|
||||||
val sessionStore = TextSecureSessionStore(this@LinkedDevicesActivity)
|
val sessionStore = TextSecureSessionStore(this@LinkedDevicesActivity)
|
||||||
sessionStore.deleteAllSessions(deviceLink.slaveHexEncodedPublicKey)
|
sessionStore.deleteAllSessions(deviceLink.slavePublicKey)
|
||||||
}
|
}
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, this)
|
LoaderManager.getInstance(this).restartLoader(0, null, this)
|
||||||
Toast.makeText(this, R.string.activity_linked_devices_unlinking_successful_message, Toast.LENGTH_LONG).show()
|
Toast.makeText(this, R.string.activity_linked_devices_unlinking_successful_message, Toast.LENGTH_LONG).show()
|
||||||
|
@ -121,6 +121,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
titleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
|
titleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
|
||||||
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.medium_font_size))
|
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.medium_font_size))
|
||||||
titleTextView.text = title
|
titleTextView.text = title
|
||||||
|
titleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
|
||||||
val titleContainer = LinearLayout(this)
|
val titleContainer = LinearLayout(this)
|
||||||
titleContainer.orientation = LinearLayout.VERTICAL
|
titleContainer.orientation = LinearLayout.VERTICAL
|
||||||
titleContainer.addView(titleTextView)
|
titleContainer.addView(titleTextView)
|
||||||
@ -133,6 +134,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
subtitleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
|
subtitleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
|
||||||
subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
|
subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
|
||||||
subtitleTextView.text = subtitle
|
subtitleTextView.text = subtitle
|
||||||
|
subtitleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
|
||||||
titleContainer.addView(subtitleTextView)
|
titleContainer.addView(subtitleTextView)
|
||||||
}
|
}
|
||||||
return mainContainer
|
return mainContainer
|
||||||
|
@ -42,7 +42,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil
|
|||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher
|
import org.whispersystems.signalservice.api.crypto.ProfileCipher
|
||||||
import org.whispersystems.signalservice.api.util.StreamDetails
|
import org.whispersystems.signalservice.api.util.StreamDetails
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
@ -73,7 +73,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
showQRCodeButton.setOnClickListener { showQRCode() }
|
showQRCodeButton.setOnClickListener { showQRCode() }
|
||||||
glide = GlideApp.with(this)
|
glide = GlideApp.with(this)
|
||||||
profilePictureView.glide = glide
|
profilePictureView.glide = glide
|
||||||
profilePictureView.hexEncodedPublicKey = hexEncodedPublicKey
|
profilePictureView.publicKey = hexEncodedPublicKey
|
||||||
profilePictureView.isLarge = true
|
profilePictureView.isLarge = true
|
||||||
profilePictureView.update()
|
profilePictureView.update()
|
||||||
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
||||||
@ -151,7 +151,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
val promises = mutableListOf<Promise<*, Exception>>()
|
val promises = mutableListOf<Promise<*, Exception>>()
|
||||||
val displayName = displayNameToBeUploaded
|
val displayName = displayNameToBeUploaded
|
||||||
if (displayName != null) {
|
if (displayName != null) {
|
||||||
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
|
val publicChatAPI = ApplicationContext.getInstance(this).publicChatAPI
|
||||||
if (publicChatAPI != null) {
|
if (publicChatAPI != null) {
|
||||||
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
||||||
promises.addAll(servers.map { publicChatAPI.setDisplayName(displayName, it) })
|
promises.addAll(servers.map { publicChatAPI.setDisplayName(displayName, it) })
|
||||||
@ -162,7 +162,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
|
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
|
||||||
val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey)
|
val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey)
|
||||||
if (isUpdatingProfilePicture && profilePicture != null) {
|
if (isUpdatingProfilePicture && profilePicture != null) {
|
||||||
val storageAPI = LokiFileServerAPI.shared
|
val storageAPI = FileServerAPI.shared
|
||||||
val deferred = deferred<Unit, Exception>()
|
val deferred = deferred<Unit, Exception>()
|
||||||
AsyncTask.execute {
|
AsyncTask.execute {
|
||||||
val stream = StreamDetails(ByteArrayInputStream(profilePicture), "image/jpeg", profilePicture.size.toLong())
|
val stream = StreamDetails(ByteArrayInputStream(profilePicture), "image/jpeg", profilePicture.size.toLong())
|
||||||
|
@ -15,7 +15,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val pollInterval = TimeUnit.MINUTES.toMillis(15)
|
private val pollInterval = TimeUnit.MINUTES.toMillis(30)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun schedule(context: Context) {
|
fun schedule(context: Context) {
|
||||||
@ -29,7 +29,7 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
|||||||
|
|
||||||
override fun onAlarm(context: Context, scheduledTime: Long): Long {
|
override fun onAlarm(context: Context, scheduledTime: Long): Long {
|
||||||
if (scheduledTime != 0L) {
|
if (scheduledTime != 0L) {
|
||||||
if (TextSecurePreferences.isUsingFCM(context)) {
|
if (!TextSecurePreferences.isUsingFCM(context)) {
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
try {
|
try {
|
||||||
@ -47,7 +47,7 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
|||||||
}
|
}
|
||||||
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value }
|
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value }
|
||||||
for (openGroup in openGroups) {
|
for (openGroup in openGroups) {
|
||||||
val poller = LokiPublicChatPoller(context, openGroup)
|
val poller = PublicChatPoller(context, openGroup)
|
||||||
poller.stop()
|
poller.stop()
|
||||||
poller.pollForNewMessages()
|
poller.pollForNewMessages()
|
||||||
}
|
}
|
||||||
|
@ -47,11 +47,11 @@ object LokiPushNotificationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun register(token: String, hexEncodedPublicKey: String, context: Context?, force: Boolean) {
|
fun register(token: String, publicKey: String, context: Context?, force: Boolean) {
|
||||||
val oldToken = TextSecurePreferences.getFCMToken(context)
|
val oldToken = TextSecurePreferences.getFCMToken(context)
|
||||||
val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context)
|
val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context)
|
||||||
if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return }
|
if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return }
|
||||||
val parameters = mapOf( "token" to token, "pubKey" to hexEncodedPublicKey )
|
val parameters = mapOf( "token" to token, "pubKey" to publicKey )
|
||||||
val url = "$server/register"
|
val url = "$server/register"
|
||||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
val request = Request.Builder().url(url).post(body).build()
|
val request = Request.Builder().url(url).post(body).build()
|
||||||
|
@ -12,11 +12,11 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
|
|||||||
import org.thoughtcrime.securesms.groups.GroupManager
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||||
|
|
||||||
class LokiPublicChatManager(private val context: Context) {
|
class PublicChatManager(private val context: Context) {
|
||||||
private var chats = mutableMapOf<Long, LokiPublicChat>()
|
private var chats = mutableMapOf<Long, PublicChat>()
|
||||||
private val pollers = mutableMapOf<Long, LokiPublicChatPoller>()
|
private val pollers = mutableMapOf<Long, PublicChatPoller>()
|
||||||
private val observers = mutableMapOf<Long, ContentObserver>()
|
private val observers = mutableMapOf<Long, ContentObserver>()
|
||||||
private var isPolling = false
|
private var isPolling = false
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class LokiPublicChatManager(private val context: Context) {
|
|||||||
var areAllCaughtUp = true
|
var areAllCaughtUp = true
|
||||||
refreshChatsAndPollers()
|
refreshChatsAndPollers()
|
||||||
for ((threadID, chat) in chats) {
|
for ((threadID, chat) in chats) {
|
||||||
val poller = pollers[threadID] ?: LokiPublicChatPoller(context, chat)
|
val poller = pollers[threadID] ?: PublicChatPoller(context, chat)
|
||||||
areAllCaughtUp = areAllCaughtUp && poller.isCaughtUp
|
areAllCaughtUp = areAllCaughtUp && poller.isCaughtUp
|
||||||
}
|
}
|
||||||
return areAllCaughtUp
|
return areAllCaughtUp
|
||||||
@ -33,7 +33,7 @@ class LokiPublicChatManager(private val context: Context) {
|
|||||||
public fun markAllAsNotCaughtUp() {
|
public fun markAllAsNotCaughtUp() {
|
||||||
refreshChatsAndPollers()
|
refreshChatsAndPollers()
|
||||||
for ((threadID, chat) in chats) {
|
for ((threadID, chat) in chats) {
|
||||||
val poller = pollers[threadID] ?: LokiPublicChatPoller(context, chat)
|
val poller = pollers[threadID] ?: PublicChatPoller(context, chat)
|
||||||
poller.isCaughtUp = false
|
poller.isCaughtUp = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ class LokiPublicChatManager(private val context: Context) {
|
|||||||
refreshChatsAndPollers()
|
refreshChatsAndPollers()
|
||||||
|
|
||||||
for ((threadId, chat) in chats) {
|
for ((threadId, chat) in chats) {
|
||||||
val poller = pollers[threadId] ?: LokiPublicChatPoller(context, chat)
|
val poller = pollers[threadId] ?: PublicChatPoller(context, chat)
|
||||||
poller.startIfNeeded()
|
poller.startIfNeeded()
|
||||||
listenToThreadDeletion(threadId)
|
listenToThreadDeletion(threadId)
|
||||||
if (!pollers.containsKey(threadId)) { pollers[threadId] = poller }
|
if (!pollers.containsKey(threadId)) { pollers[threadId] = poller }
|
||||||
@ -55,8 +55,8 @@ class LokiPublicChatManager(private val context: Context) {
|
|||||||
isPolling = false
|
isPolling = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun addChat(server: String, channel: Long): Promise<LokiPublicChat, Exception> {
|
public fun addChat(server: String, channel: Long): Promise<PublicChat, Exception> {
|
||||||
val groupChatAPI = ApplicationContext.getInstance(context).lokiPublicChatAPI ?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!"))
|
val groupChatAPI = ApplicationContext.getInstance(context).publicChatAPI ?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!"))
|
||||||
return groupChatAPI.getAuthToken(server).bind {
|
return groupChatAPI.getAuthToken(server).bind {
|
||||||
groupChatAPI.getChannelInfo(channel, server)
|
groupChatAPI.getChannelInfo(channel, server)
|
||||||
}.map {
|
}.map {
|
||||||
@ -64,8 +64,8 @@ class LokiPublicChatManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun addChat(server: String, channel: Long, name: String): LokiPublicChat {
|
public fun addChat(server: String, channel: Long, name: String): PublicChat {
|
||||||
val chat = LokiPublicChat(channel, server, name, true)
|
val chat = PublicChat(channel, server, name, true)
|
||||||
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
|
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
|
||||||
// Create the group if we don't have one
|
// Create the group if we don't have one
|
||||||
if (threadID < 0) {
|
if (threadID < 0) {
|
||||||
@ -76,7 +76,7 @@ class LokiPublicChatManager(private val context: Context) {
|
|||||||
// Set our name on the server
|
// Set our name on the server
|
||||||
val displayName = TextSecurePreferences.getProfileName(context)
|
val displayName = TextSecurePreferences.getProfileName(context)
|
||||||
if (!TextUtils.isEmpty(displayName)) {
|
if (!TextUtils.isEmpty(displayName)) {
|
||||||
ApplicationContext.getInstance(context).lokiPublicChatAPI?.setDisplayName(displayName, server)
|
ApplicationContext.getInstance(context).publicChatAPI?.setDisplayName(displayName, server)
|
||||||
}
|
}
|
||||||
// Start polling
|
// Start polling
|
||||||
Util.runOnMain{ startPollersIfNeeded() }
|
Util.runOnMain{ startPollersIfNeeded() }
|
@ -22,16 +22,15 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage
|
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatMessage
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatMessage
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class LokiPublicChatPoller(private val context: Context, private val group: LokiPublicChat) {
|
class PublicChatPoller(private val context: Context, private val group: PublicChat) {
|
||||||
private val handler = Handler()
|
private val handler = Handler()
|
||||||
private var hasStarted = false
|
private var hasStarted = false
|
||||||
public var isCaughtUp = false
|
public var isCaughtUp = false
|
||||||
@ -40,12 +39,12 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
private var displayNameUpdatees = setOf<String>()
|
private var displayNameUpdatees = setOf<String>()
|
||||||
|
|
||||||
private val api: LokiPublicChatAPI
|
private val api: PublicChatAPI
|
||||||
get() = {
|
get() = {
|
||||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
|
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
|
||||||
LokiPublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
|
PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
|
||||||
}()
|
}()
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
@ -112,16 +111,16 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Polling
|
// region Polling
|
||||||
private fun getDataMessage(message: LokiPublicChatMessage): SignalServiceDataMessage {
|
private fun getDataMessage(message: PublicChatMessage): SignalServiceDataMessage {
|
||||||
val id = group.id.toByteArray()
|
val id = group.id.toByteArray()
|
||||||
val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.PUBLIC_CHAT, null, null, null, null)
|
val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.PUBLIC_CHAT, null, null, null, null)
|
||||||
val quote = if (message.quote != null) {
|
val quote = if (message.quote != null) {
|
||||||
SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteeHexEncodedPublicKey), message.quote!!.quotedMessageBody, listOf())
|
SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteePublicKey), message.quote!!.quotedMessageBody, listOf())
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val attachments = message.attachments.mapNotNull { attachment ->
|
val attachments = message.attachments.mapNotNull { attachment ->
|
||||||
if (attachment.kind != LokiPublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null }
|
if (attachment.kind != PublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null }
|
||||||
SignalServiceAttachmentPointer(
|
SignalServiceAttachmentPointer(
|
||||||
attachment.serverID,
|
attachment.serverID,
|
||||||
attachment.contentType,
|
attachment.contentType,
|
||||||
@ -135,7 +134,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
Optional.fromNullable(attachment.caption),
|
Optional.fromNullable(attachment.caption),
|
||||||
attachment.url)
|
attachment.url)
|
||||||
}
|
}
|
||||||
val linkPreview = message.attachments.firstOrNull { it.kind == LokiPublicChatMessage.Attachment.Kind.LinkPreview }
|
val linkPreview = message.attachments.firstOrNull { it.kind == PublicChatMessage.Attachment.Kind.LinkPreview }
|
||||||
val signalLinkPreviews = mutableListOf<SignalServiceDataMessage.Preview>()
|
val signalLinkPreviews = mutableListOf<SignalServiceDataMessage.Preview>()
|
||||||
if (linkPreview != null) {
|
if (linkPreview != null) {
|
||||||
val attachment = SignalServiceAttachmentPointer(
|
val attachment = SignalServiceAttachmentPointer(
|
||||||
@ -157,16 +156,16 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun pollForNewMessages() {
|
fun pollForNewMessages() {
|
||||||
fun processIncomingMessage(message: LokiPublicChatMessage) {
|
fun processIncomingMessage(message: PublicChatMessage) {
|
||||||
// If the sender of the current message is not a slave device, set the display name in the database
|
// If the sender of the current message is not a slave device, set the display name in the database
|
||||||
val masterHexEncodedPublicKey = MultiDeviceProtocol.shared.getMasterDevice(message.hexEncodedPublicKey)
|
val masterHexEncodedPublicKey = MultiDeviceProtocol.shared.getMasterDevice(message.publicKey)
|
||||||
if (masterHexEncodedPublicKey == null) {
|
if (masterHexEncodedPublicKey == null) {
|
||||||
val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
|
val senderDisplayName = "${message.displayName} (...${message.publicKey.takeLast(8)})"
|
||||||
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)
|
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.publicKey, senderDisplayName)
|
||||||
}
|
}
|
||||||
val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.hexEncodedPublicKey
|
val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.publicKey
|
||||||
val serviceDataMessage = getDataMessage(message)
|
val serviceDataMessage = getDataMessage(message)
|
||||||
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false, false, false)
|
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false)
|
||||||
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
|
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
|
||||||
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
||||||
} else {
|
} else {
|
||||||
@ -182,16 +181,9 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
database.setProfileKey(senderAsRecipient, profileKey)
|
database.setProfileKey(senderAsRecipient, profileKey)
|
||||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url))
|
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url))
|
||||||
}
|
}
|
||||||
} 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(senderAsRecipient, ""))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
fun processOutgoingMessage(message: PublicChatMessage) {
|
||||||
fun processOutgoingMessage(message: LokiPublicChatMessage) {
|
|
||||||
val messageServerID = message.serverID ?: return
|
val messageServerID = message.serverID ?: return
|
||||||
val isDuplicate = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID) != null
|
val isDuplicate = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID) != null
|
||||||
if (isDuplicate) { return }
|
if (isDuplicate) { return }
|
||||||
@ -206,7 +198,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript)
|
PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript)
|
||||||
}
|
}
|
||||||
// If we got a message from our master device then make sure our mapping stays in sync
|
// If we got a message from our master device then make sure our mapping stays in sync
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(message.hexEncodedPublicKey), false)
|
val recipient = Recipient.from(context, Address.fromSerialized(message.publicKey), false)
|
||||||
if (recipient.isUserMasterDevice && message.profilePicture != null) {
|
if (recipient.isUserMasterDevice && message.profilePicture != null) {
|
||||||
val profileKey = message.profilePicture!!.profileKey
|
val profileKey = message.profilePicture!!.profileKey
|
||||||
val url = message.profilePicture!!.url
|
val url = message.profilePicture!!.url
|
||||||
@ -222,15 +214,15 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
var uniqueDevices = setOf<String>()
|
var uniqueDevices = setOf<String>()
|
||||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
||||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
LokiFileServerAPI.configure(false, userHexEncodedPublicKey, userPrivateKey, apiDB)
|
FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB)
|
||||||
// Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below
|
// Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below
|
||||||
api.getMessages(group.channel, group.server).bind(LokiPublicChatAPI.sharedContext) { messages ->
|
api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages ->
|
||||||
if (messages.isNotEmpty()) {
|
if (messages.isNotEmpty()) {
|
||||||
// We need to fetch the device mapping for any devices we don't have
|
// We need to fetch the device mapping for any devices we don't have
|
||||||
uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet()
|
uniqueDevices = messages.map { it.publicKey }.toSet()
|
||||||
val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && LokiFileServerAPI.shared.hasDeviceLinkCacheExpired(hexEncodedPublicKey = it) }
|
val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && FileServerAPI.shared.hasDeviceLinkCacheExpired(publicKey = it) }
|
||||||
if (devicesToUpdate.isNotEmpty()) {
|
if (devicesToUpdate.isNotEmpty()) {
|
||||||
return@bind LokiFileServerAPI.shared.getDeviceLinks(devicesToUpdate.toSet()).then { messages }
|
return@bind FileServerAPI.shared.getDeviceLinks(devicesToUpdate.toSet()).then { messages }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Promise.of(messages)
|
Promise.of(messages)
|
||||||
@ -244,7 +236,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
}.successBackground { messages ->
|
}.successBackground { messages ->
|
||||||
// Process messages in the background
|
// Process messages in the background
|
||||||
messages.forEach { message ->
|
messages.forEach { message ->
|
||||||
if (userDevices.contains(message.hexEncodedPublicKey)) {
|
if (userDevices.contains(message.publicKey)) {
|
||||||
processOutgoingMessage(message)
|
processOutgoingMessage(message)
|
||||||
} else {
|
} else {
|
||||||
processIncomingMessage(message)
|
processIncomingMessage(message)
|
@ -17,6 +17,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
private val userPublicKey get() = TextSecurePreferences.getLocalNumber(context)
|
private val userPublicKey get() = TextSecurePreferences.getLocalNumber(context)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
// Shared
|
||||||
|
private val publicKey = "public_key"
|
||||||
|
private val timestamp = "timestamp"
|
||||||
// Snode pool cache
|
// Snode pool cache
|
||||||
private val snodePoolCache = "loki_snode_pool_cache"
|
private val snodePoolCache = "loki_snode_pool_cache"
|
||||||
private val dummyKey = "dummy_key"
|
private val dummyKey = "dummy_key"
|
||||||
@ -29,9 +32,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
@JvmStatic val createOnionRequestPathCacheCommand = "CREATE TABLE $onionRequestPathCache ($indexPath TEXT PRIMARY KEY, $snode TEXT);"
|
@JvmStatic val createOnionRequestPathCacheCommand = "CREATE TABLE $onionRequestPathCache ($indexPath TEXT PRIMARY KEY, $snode TEXT);"
|
||||||
// Swarm cache
|
// Swarm cache
|
||||||
private val swarmCache = "loki_api_swarm_cache"
|
private val swarmCache = "loki_api_swarm_cache"
|
||||||
private val hexEncodedPublicKey = "hex_encoded_public_key"
|
private val swarmPublicKey = "hex_encoded_public_key"
|
||||||
private val swarm = "swarm"
|
private val swarm = "swarm"
|
||||||
@JvmStatic val createSwarmCacheCommand = "CREATE TABLE $swarmCache ($hexEncodedPublicKey TEXT PRIMARY KEY, $swarm TEXT);"
|
@JvmStatic val createSwarmCacheCommand = "CREATE TABLE $swarmCache ($swarmPublicKey TEXT PRIMARY KEY, $swarm TEXT);"
|
||||||
// Last message hash value cache
|
// Last message hash value cache
|
||||||
private val lastMessageHashValueCache = "loki_api_last_message_hash_value_cache"
|
private val lastMessageHashValueCache = "loki_api_last_message_hash_value_cache"
|
||||||
private val target = "target"
|
private val target = "target"
|
||||||
@ -59,22 +62,30 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
@JvmStatic val createLastDeletionServerIDCacheCommand = "CREATE TABLE $lastDeletionServerIDCache ($lastDeletionServerIDCacheIndex STRING PRIMARY KEY, $lastDeletionServerID INTEGER DEFAULT 0);"
|
@JvmStatic val createLastDeletionServerIDCacheCommand = "CREATE TABLE $lastDeletionServerIDCache ($lastDeletionServerIDCacheIndex STRING PRIMARY KEY, $lastDeletionServerID INTEGER DEFAULT 0);"
|
||||||
// Device link cache
|
// Device link cache
|
||||||
private val deviceLinkCache = "loki_pairing_authorisation_cache"
|
private val deviceLinkCache = "loki_pairing_authorisation_cache"
|
||||||
private val masterHexEncodedPublicKey = "primary_device"
|
private val masterPublicKey = "primary_device"
|
||||||
private val slaveHexEncodedPublicKey = "secondary_device"
|
private val slavePublicKey = "secondary_device"
|
||||||
private val requestSignature = "request_signature"
|
private val requestSignature = "request_signature"
|
||||||
private val authorizationSignature = "grant_signature"
|
private val authorizationSignature = "grant_signature"
|
||||||
@JvmStatic val createDeviceLinkCacheCommand = "CREATE TABLE $deviceLinkCache ($masterHexEncodedPublicKey TEXT, $slaveHexEncodedPublicKey TEXT, " +
|
@JvmStatic val createDeviceLinkCacheCommand = "CREATE TABLE $deviceLinkCache ($masterPublicKey TEXT, $slavePublicKey TEXT, " +
|
||||||
"$requestSignature TEXT NULLABLE DEFAULT NULL, $authorizationSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($masterHexEncodedPublicKey, $slaveHexEncodedPublicKey));"
|
"$requestSignature TEXT NULLABLE DEFAULT NULL, $authorizationSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($masterPublicKey, $slavePublicKey));"
|
||||||
// User count cache
|
// User count cache
|
||||||
private val userCountCache = "loki_user_count_cache"
|
private val userCountCache = "loki_user_count_cache"
|
||||||
private val publicChatID = "public_chat_id"
|
private val publicChatID = "public_chat_id"
|
||||||
private val userCount = "user_count"
|
private val userCount = "user_count"
|
||||||
@JvmStatic val createUserCountCacheCommand = "CREATE TABLE $userCountCache ($publicChatID STRING PRIMARY KEY, $userCount INTEGER DEFAULT 0);"
|
@JvmStatic val createUserCountCacheCommand = "CREATE TABLE $userCountCache ($publicChatID STRING PRIMARY KEY, $userCount INTEGER DEFAULT 0);"
|
||||||
// Session request timestamp cache
|
// Session request sent timestamp cache
|
||||||
|
private val sessionRequestSentTimestampCache = "session_request_sent_timestamp_cache"
|
||||||
|
@JvmStatic val createSessionRequestSentTimestampCacheCommand = "CREATE TABLE $sessionRequestSentTimestampCache ($publicKey STRING PRIMARY KEY, $timestamp INTEGER DEFAULT 0);"
|
||||||
|
// Session request processed timestamp cache
|
||||||
|
private val sessionRequestProcessedTimestampCache = "session_request_processed_timestamp_cache"
|
||||||
|
@JvmStatic val createSessionRequestProcessedTimestampCacheCommand = "CREATE TABLE $sessionRequestProcessedTimestampCache ($publicKey STRING PRIMARY KEY, $timestamp INTEGER DEFAULT 0);"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// region Deprecated
|
||||||
private val sessionRequestTimestampCache = "session_request_timestamp_cache"
|
private val sessionRequestTimestampCache = "session_request_timestamp_cache"
|
||||||
private val publicKey = "public_key"
|
|
||||||
private val timestamp = "timestamp"
|
|
||||||
@JvmStatic val createSessionRequestTimestampCacheCommand = "CREATE TABLE $sessionRequestTimestampCache ($publicKey STRING PRIMARY KEY, $timestamp INTEGER DEFAULT 0);"
|
@JvmStatic val createSessionRequestTimestampCacheCommand = "CREATE TABLE $sessionRequestTimestampCache ($publicKey STRING PRIMARY KEY, $timestamp INTEGER DEFAULT 0);"
|
||||||
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSnodePool(): Set<Snode> {
|
override fun getSnodePool(): Set<Snode> {
|
||||||
@ -161,9 +172,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
set("1-1", path1[1]); set("1-2", path1[2])
|
set("1-1", path1[1]); set("1-2", path1[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSwarm(hexEncodedPublicKey: String): Set<Snode>? {
|
override fun getSwarm(publicKey: String): Set<Snode>? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(swarmCache, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey)) { cursor ->
|
return database.get(swarmCache, "${Companion.swarmPublicKey} = ?", wrap(publicKey)) { cursor ->
|
||||||
val swarmAsString = cursor.getString(cursor.getColumnIndexOrThrow(swarm))
|
val swarmAsString = cursor.getString(cursor.getColumnIndexOrThrow(swarm))
|
||||||
swarmAsString.split(", ").mapNotNull { targetAsString ->
|
swarmAsString.split(", ").mapNotNull { targetAsString ->
|
||||||
val components = targetAsString.split("-")
|
val components = targetAsString.split("-")
|
||||||
@ -176,7 +187,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
}?.toSet()
|
}?.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setSwarm(hexEncodedPublicKey: String, newValue: Set<Snode>) {
|
override fun setSwarm(publicKey: String, newValue: Set<Snode>) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val swarmAsString = newValue.joinToString(", ") { target ->
|
val swarmAsString = newValue.joinToString(", ") { target ->
|
||||||
var string = "${target.address}-${target.port}"
|
var string = "${target.address}-${target.port}"
|
||||||
@ -186,21 +197,21 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
}
|
}
|
||||||
string
|
string
|
||||||
}
|
}
|
||||||
val row = wrap(mapOf(Companion.hexEncodedPublicKey to hexEncodedPublicKey, swarm to swarmAsString))
|
val row = wrap(mapOf(Companion.swarmPublicKey to publicKey, swarm to swarmAsString))
|
||||||
database.insertOrUpdate(swarmCache, row, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey))
|
database.insertOrUpdate(swarmCache, row, "${Companion.swarmPublicKey} = ?", wrap(publicKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLastMessageHashValue(target: Snode): String? {
|
override fun getLastMessageHashValue(snode: Snode): String? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(lastMessageHashValueCache, "${Companion.target} = ?", wrap(target.address)) { cursor ->
|
return database.get(lastMessageHashValueCache, "${Companion.target} = ?", wrap(snode.address)) { cursor ->
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(lastMessageHashValue))
|
cursor.getString(cursor.getColumnIndexOrThrow(lastMessageHashValue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setLastMessageHashValue(target: Snode, newValue: String) {
|
override fun setLastMessageHashValue(snode: Snode, newValue: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val row = wrap(mapOf(Companion.target to target.address, lastMessageHashValue to newValue))
|
val row = wrap(mapOf(Companion.target to snode.address, lastMessageHashValue to newValue))
|
||||||
database.insertOrUpdate(lastMessageHashValueCache, row, "${Companion.target} = ?", wrap(target.address))
|
database.insertOrUpdate(lastMessageHashValueCache, row, "${Companion.target} = ?", wrap(snode.address))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReceivedMessageHashValues(): Set<String>? {
|
override fun getReceivedMessageHashValues(): Set<String>? {
|
||||||
@ -277,35 +288,35 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.delete(lastDeletionServerIDCache,"$lastDeletionServerIDCacheIndex = ?", wrap(index))
|
database.delete(lastDeletionServerIDCache,"$lastDeletionServerIDCacheIndex = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDeviceLinks(hexEncodedPublicKey: String): Set<DeviceLink> {
|
override fun getDeviceLinks(publicKey: String): Set<DeviceLink> {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.getAll(deviceLinkCache, "$masterHexEncodedPublicKey = ? OR $slaveHexEncodedPublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) { cursor ->
|
return database.getAll(deviceLinkCache, "$masterPublicKey = ? OR $slavePublicKey = ?", arrayOf( publicKey, publicKey )) { cursor ->
|
||||||
val masterHexEncodedPublicKey = cursor.getString(masterHexEncodedPublicKey)
|
val masterHexEncodedPublicKey = cursor.getString(masterPublicKey)
|
||||||
val slaveHexEncodedPublicKey = cursor.getString(slaveHexEncodedPublicKey)
|
val slaveHexEncodedPublicKey = cursor.getString(slavePublicKey)
|
||||||
val requestSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(requestSignature))) null else cursor.getBase64EncodedData(requestSignature)
|
val requestSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(requestSignature))) null else cursor.getBase64EncodedData(requestSignature)
|
||||||
val authorizationSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(authorizationSignature))) null else cursor.getBase64EncodedData(authorizationSignature)
|
val authorizationSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(authorizationSignature))) null else cursor.getBase64EncodedData(authorizationSignature)
|
||||||
DeviceLink(masterHexEncodedPublicKey, slaveHexEncodedPublicKey, requestSignature, authorizationSignature)
|
DeviceLink(masterHexEncodedPublicKey, slaveHexEncodedPublicKey, requestSignature, authorizationSignature)
|
||||||
}.toSet()
|
}.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearDeviceLinks(hexEncodedPublicKey: String) {
|
override fun clearDeviceLinks(publicKey: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
database.delete(deviceLinkCache, "$masterHexEncodedPublicKey = ? OR $slaveHexEncodedPublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey ))
|
database.delete(deviceLinkCache, "$masterPublicKey = ? OR $slavePublicKey = ?", arrayOf( publicKey, publicKey ))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addDeviceLink(deviceLink: DeviceLink) {
|
override fun addDeviceLink(deviceLink: DeviceLink) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val values = ContentValues()
|
val values = ContentValues()
|
||||||
values.put(masterHexEncodedPublicKey, deviceLink.masterHexEncodedPublicKey)
|
values.put(masterPublicKey, deviceLink.masterPublicKey)
|
||||||
values.put(slaveHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey)
|
values.put(slavePublicKey, deviceLink.slavePublicKey)
|
||||||
if (deviceLink.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(deviceLink.requestSignature)) }
|
if (deviceLink.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(deviceLink.requestSignature)) }
|
||||||
if (deviceLink.authorizationSignature != null) { values.put(authorizationSignature, Base64.encodeBytes(deviceLink.authorizationSignature)) }
|
if (deviceLink.authorizationSignature != null) { values.put(authorizationSignature, Base64.encodeBytes(deviceLink.authorizationSignature)) }
|
||||||
database.insertOrUpdate(deviceLinkCache, values, "$masterHexEncodedPublicKey = ? AND $slaveHexEncodedPublicKey = ?", arrayOf( deviceLink.masterHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey ))
|
database.insertOrUpdate(deviceLinkCache, values, "$masterPublicKey = ? AND $slavePublicKey = ?", arrayOf( deviceLink.masterPublicKey, deviceLink.slavePublicKey ))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeDeviceLink(deviceLink: DeviceLink) {
|
override fun removeDeviceLink(deviceLink: DeviceLink) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
database.delete(deviceLinkCache, "$masterHexEncodedPublicKey = ? OR $slaveHexEncodedPublicKey = ?", arrayOf( deviceLink.masterHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey ))
|
database.delete(deviceLinkCache, "$masterPublicKey = ? OR $slavePublicKey = ?", arrayOf( deviceLink.masterPublicKey, deviceLink.slavePublicKey ))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserCount(group: Long, server: String): Int? {
|
fun getUserCount(group: Long, server: String): Int? {
|
||||||
@ -323,17 +334,30 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(userCountCache, row, "$publicChatID = ?", wrap(index))
|
database.insertOrUpdate(userCountCache, row, "$publicChatID = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSessionRequestTimestamp(publicKey: String): Long? {
|
override fun getSessionRequestSentTimestamp(publicKey: String): Long? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(sessionRequestTimestampCache, "$LokiAPIDatabase.publicKey = ?", wrap(publicKey)) { cursor ->
|
return database.get(sessionRequestSentTimestampCache, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) { cursor ->
|
||||||
cursor.getInt(LokiAPIDatabase.timestamp)
|
cursor.getInt(LokiAPIDatabase.timestamp)
|
||||||
}?.toLong()
|
}?.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setSessionRequestTimestamp(publicKey: String, timestamp: Long) {
|
override fun setSessionRequestSentTimestamp(publicKey: String, timestamp: Long) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val row = wrap(mapOf(LokiAPIDatabase.publicKey to publicKey, LokiAPIDatabase.timestamp to timestamp.toString()))
|
val row = wrap(mapOf(LokiAPIDatabase.publicKey to publicKey, LokiAPIDatabase.timestamp to timestamp.toString()))
|
||||||
database.insertOrUpdate(sessionRequestTimestampCache, row, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey))
|
database.insertOrUpdate(sessionRequestSentTimestampCache, row, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSessionRequestProcessedTimestamp(publicKey: String): Long? {
|
||||||
|
val database = databaseHelper.readableDatabase
|
||||||
|
return database.get(sessionRequestProcessedTimestampCache, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) { cursor ->
|
||||||
|
cursor.getInt(LokiAPIDatabase.timestamp)
|
||||||
|
}?.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setSessionRequestProcessedTimestamp(publicKey: String, timestamp: Long) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val row = wrap(mapOf(LokiAPIDatabase.publicKey to publicKey, LokiAPIDatabase.timestamp to timestamp.toString()))
|
||||||
|
database.insertOrUpdate(sessionRequestProcessedTimestampCache, row, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,39 +11,38 @@ import org.thoughtcrime.securesms.loki.utilities.getInt
|
|||||||
import org.thoughtcrime.securesms.loki.utilities.getString
|
import org.thoughtcrime.securesms.loki.utilities.getString
|
||||||
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
|
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
|
||||||
import org.whispersystems.signalservice.loki.database.LokiMessageDatabaseProtocol
|
import org.whispersystems.signalservice.loki.database.LokiMessageDatabaseProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
|
||||||
|
|
||||||
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {
|
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val messageFriendRequestTableName = "loki_message_friend_request_database"
|
private val messageIDTable = "loki_message_friend_request_database"
|
||||||
private val messageThreadMappingTableName = "loki_message_thread_mapping_database"
|
private val messageThreadMappingTable = "loki_message_thread_mapping_database"
|
||||||
private val errorMessageTableName = "loki_error_message_database"
|
private val errorMessageTable = "loki_error_message_database"
|
||||||
private val messageID = "message_id"
|
private val messageID = "message_id"
|
||||||
private val serverID = "server_id"
|
private val serverID = "server_id"
|
||||||
private val friendRequestStatus = "friend_request_status"
|
private val friendRequestStatus = "friend_request_status"
|
||||||
private val threadID = "thread_id"
|
private val threadID = "thread_id"
|
||||||
private val errorMessage = "error_message"
|
private val errorMessage = "error_message"
|
||||||
@JvmStatic val createMessageFriendRequestTableCommand = "CREATE TABLE $messageFriendRequestTableName ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);"
|
@JvmStatic val createMessageIDTableCommand = "CREATE TABLE $messageIDTable ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);"
|
||||||
@JvmStatic val createMessageToThreadMappingTableCommand = "CREATE TABLE IF NOT EXISTS $messageThreadMappingTableName ($messageID INTEGER PRIMARY KEY, $threadID INTEGER);"
|
@JvmStatic val createMessageToThreadMappingTableCommand = "CREATE TABLE IF NOT EXISTS $messageThreadMappingTable ($messageID INTEGER PRIMARY KEY, $threadID INTEGER);"
|
||||||
@JvmStatic val createErrorMessageTableCommand = "CREATE TABLE IF NOT EXISTS $errorMessageTableName ($messageID INTEGER PRIMARY KEY, $errorMessage STRING);"
|
@JvmStatic val createErrorMessageTableCommand = "CREATE TABLE IF NOT EXISTS $errorMessageTable ($messageID INTEGER PRIMARY KEY, $errorMessage STRING);"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getQuoteServerID(quoteID: Long, quoteeHexEncodedPublicKey: String): Long? {
|
override fun getQuoteServerID(quoteID: Long, quoteePublicKey: String): Long? {
|
||||||
val message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quoteID, Address.fromSerialized(quoteeHexEncodedPublicKey))
|
val message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quoteID, Address.fromSerialized(quoteePublicKey))
|
||||||
return if (message != null) getServerID(message.getId()) else null
|
return if (message != null) getServerID(message.getId()) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getServerID(messageID: Long): Long? {
|
fun getServerID(messageID: Long): Long? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(messageFriendRequestTableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
return database.get(messageIDTable, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
||||||
cursor.getInt(serverID)
|
cursor.getInt(serverID)
|
||||||
}?.toLong()
|
}?.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMessageID(serverID: Long): Long? {
|
fun getMessageID(serverID: Long): Long? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(messageFriendRequestTableName, "${Companion.serverID} = ?", arrayOf( serverID.toString() )) { cursor ->
|
return database.get(messageIDTable, "${Companion.serverID} = ?", arrayOf( serverID.toString() )) { cursor ->
|
||||||
cursor.getInt(messageID)
|
cursor.getInt(messageID)
|
||||||
}?.toLong()
|
}?.toLong()
|
||||||
}
|
}
|
||||||
@ -53,12 +52,12 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
|||||||
val contentValues = ContentValues(2)
|
val contentValues = ContentValues(2)
|
||||||
contentValues.put(Companion.messageID, messageID)
|
contentValues.put(Companion.messageID, messageID)
|
||||||
contentValues.put(Companion.serverID, serverID)
|
contentValues.put(Companion.serverID, serverID)
|
||||||
database.insertOrUpdate(messageFriendRequestTableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
database.insertOrUpdate(messageIDTable, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOriginalThreadID(messageID: Long): Long {
|
fun getOriginalThreadID(messageID: Long): Long {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(messageThreadMappingTableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
return database.get(messageThreadMappingTable, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
||||||
cursor.getInt(threadID)
|
cursor.getInt(threadID)
|
||||||
}?.toLong() ?: -1L
|
}?.toLong() ?: -1L
|
||||||
}
|
}
|
||||||
@ -68,38 +67,12 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
|||||||
val contentValues = ContentValues(2)
|
val contentValues = ContentValues(2)
|
||||||
contentValues.put(Companion.messageID, messageID)
|
contentValues.put(Companion.messageID, messageID)
|
||||||
contentValues.put(Companion.threadID, threadID)
|
contentValues.put(Companion.threadID, threadID)
|
||||||
database.insertOrUpdate(messageThreadMappingTableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
database.insertOrUpdate(messageThreadMappingTable, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
||||||
}
|
|
||||||
|
|
||||||
fun getFriendRequestStatus(messageID: Long): LokiMessageFriendRequestStatus {
|
|
||||||
val database = databaseHelper.readableDatabase
|
|
||||||
val result = database.get(messageFriendRequestTableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
|
||||||
cursor.getInt(friendRequestStatus)
|
|
||||||
}
|
|
||||||
return if (result != null) {
|
|
||||||
LokiMessageFriendRequestStatus.values().first { it.rawValue == result }
|
|
||||||
} else {
|
|
||||||
LokiMessageFriendRequestStatus.NONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFriendRequestStatus(messageID: Long, friendRequestStatus: LokiMessageFriendRequestStatus) {
|
|
||||||
val database = databaseHelper.writableDatabase
|
|
||||||
val contentValues = ContentValues(2)
|
|
||||||
contentValues.put(Companion.messageID, messageID)
|
|
||||||
contentValues.put(Companion.friendRequestStatus, friendRequestStatus.rawValue)
|
|
||||||
database.insertOrUpdate(messageFriendRequestTableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
|
||||||
val threadID = DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID)
|
|
||||||
notifyConversationListeners(threadID)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isFriendRequest(messageID: Long): Boolean {
|
|
||||||
return getFriendRequestStatus(messageID) != LokiMessageFriendRequestStatus.NONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getErrorMessage(messageID: Long): String? {
|
fun getErrorMessage(messageID: Long): String? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(errorMessageTableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
return database.get(errorMessageTable, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor ->
|
||||||
cursor.getString(errorMessage)
|
cursor.getString(errorMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,6 +82,6 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
|||||||
val contentValues = ContentValues(2)
|
val contentValues = ContentValues(2)
|
||||||
contentValues.put(Companion.messageID, messageID)
|
contentValues.put(Companion.messageID, messageID)
|
||||||
contentValues.put(Companion.errorMessage, errorMessage)
|
contentValues.put(Companion.errorMessage, errorMessage)
|
||||||
database.insertOrUpdate(errorMessageTableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
database.insertOrUpdate(errorMessageTable, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,8 +26,8 @@ import org.whispersystems.signalservice.loki.database.LokiPreKeyBundleDatabasePr
|
|||||||
class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiPreKeyBundleDatabaseProtocol {
|
class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiPreKeyBundleDatabaseProtocol {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val tableName = "loki_pre_key_bundle_database"
|
private val table = "loki_pre_key_bundle_database"
|
||||||
private val hexEncodedPublicKey = "public_key"
|
private val publicKey = "public_key"
|
||||||
private val preKeyID = "pre_key_id"
|
private val preKeyID = "pre_key_id"
|
||||||
private val preKeyPublic = "pre_key_public"
|
private val preKeyPublic = "pre_key_public"
|
||||||
private val signedPreKeyID = "signed_pre_key_id"
|
private val signedPreKeyID = "signed_pre_key_id"
|
||||||
@ -36,16 +36,16 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
|
|||||||
private val identityKey = "identity_key"
|
private val identityKey = "identity_key"
|
||||||
private val deviceID = "device_id"
|
private val deviceID = "device_id"
|
||||||
private val registrationID = "registration_id"
|
private val registrationID = "registration_id"
|
||||||
@JvmStatic val createTableCommand = "CREATE TABLE $tableName (" + "$hexEncodedPublicKey TEXT PRIMARY KEY," + "$preKeyID INTEGER," +
|
@JvmStatic val createTableCommand = "CREATE TABLE $table (" + "$publicKey TEXT PRIMARY KEY," + "$preKeyID INTEGER," +
|
||||||
"$preKeyPublic TEXT NOT NULL," + "$signedPreKeyID INTEGER," + "$signedPreKeyPublic TEXT NOT NULL," +
|
"$preKeyPublic TEXT NOT NULL," + "$signedPreKeyID INTEGER," + "$signedPreKeyPublic TEXT NOT NULL," +
|
||||||
"$signedPreKeySignature TEXT," + "$identityKey TEXT NOT NULL," + "$deviceID INTEGER," + "$registrationID INTEGER" + ");"
|
"$signedPreKeySignature TEXT," + "$identityKey TEXT NOT NULL," + "$deviceID INTEGER," + "$registrationID INTEGER" + ");"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generatePreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? {
|
fun generatePreKeyBundle(publicKey: String): PreKeyBundle? {
|
||||||
var failureCount = 0
|
var failureCount = 0
|
||||||
while (failureCount < 3) {
|
while (failureCount < 3) {
|
||||||
try {
|
try {
|
||||||
val preKey = generatePreKeyBundle(hexEncodedPublicKey, failureCount > 0) ?: return null
|
val preKey = generatePreKeyBundle(publicKey, failureCount > 0) ?: return null
|
||||||
// Verify the bundle is correct
|
// Verify the bundle is correct
|
||||||
if (!Curve.verifySignature(preKey.identityKey.publicKey, preKey.signedPreKey.serialize(), preKey.signedPreKeySignature)) {
|
if (!Curve.verifySignature(preKey.identityKey.publicKey, preKey.signedPreKey.serialize(), preKey.signedPreKeySignature)) {
|
||||||
throw InvalidKeyException()
|
throw InvalidKeyException()
|
||||||
@ -55,19 +55,19 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
|
|||||||
failureCount += 1
|
failureCount += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.w("Loki", "Failed to generate a valid pre key bundle for: $hexEncodedPublicKey.")
|
Log.w("Loki", "Failed to generate a valid pre key bundle for: $publicKey.")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generatePreKeyBundle(hexEncodedPublicKey: String, forceClean: Boolean): PreKeyBundle? {
|
private fun generatePreKeyBundle(publicKey: String, forceClean: Boolean): PreKeyBundle? {
|
||||||
if (hexEncodedPublicKey.isEmpty()) return null
|
if (publicKey.isEmpty()) return null
|
||||||
var registrationID = TextSecurePreferences.getLocalRegistrationId(context)
|
var registrationID = TextSecurePreferences.getLocalRegistrationId(context)
|
||||||
if (registrationID == 0) {
|
if (registrationID == 0) {
|
||||||
registrationID = KeyHelper.generateRegistrationId(false)
|
registrationID = KeyHelper.generateRegistrationId(false)
|
||||||
TextSecurePreferences.setLocalRegistrationId(context, registrationID)
|
TextSecurePreferences.setLocalRegistrationId(context, registrationID)
|
||||||
}
|
}
|
||||||
val deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID
|
val deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID
|
||||||
val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getOrCreatePreKeyRecord(hexEncodedPublicKey)
|
val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getOrCreatePreKeyRecord(publicKey)
|
||||||
val identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context)
|
val identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context)
|
||||||
if (!forceClean && TextSecurePreferences.isSignedPreKeyRegistered(context)) {
|
if (!forceClean && TextSecurePreferences.isSignedPreKeyRegistered(context)) {
|
||||||
Log.d("Loki", "A signed pre key has already been registered.")
|
Log.d("Loki", "A signed pre key has already been registered.")
|
||||||
@ -80,9 +80,9 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
|
|||||||
return PreKeyBundle(registrationID, deviceID, preKeyRecord.id, preKeyRecord.keyPair.publicKey, activeSignedPreKey.id, activeSignedPreKey.keyPair.publicKey, activeSignedPreKey.signature, identityKeyPair.publicKey)
|
return PreKeyBundle(registrationID, deviceID, preKeyRecord.id, preKeyRecord.keyPair.publicKey, activeSignedPreKey.id, activeSignedPreKey.keyPair.publicKey, activeSignedPreKey.signature, identityKeyPair.publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? {
|
override fun getPreKeyBundle(publicKey: String): PreKeyBundle? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(tableName, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey )) { cursor ->
|
return database.get(table, "${Companion.publicKey} = ?", arrayOf( publicKey )) { cursor ->
|
||||||
val registrationID = cursor.getInt(registrationID)
|
val registrationID = cursor.getInt(registrationID)
|
||||||
val deviceID = cursor.getInt(deviceID)
|
val deviceID = cursor.getInt(deviceID)
|
||||||
val preKeyID = cursor.getInt(preKeyID)
|
val preKeyID = cursor.getInt(preKeyID)
|
||||||
@ -95,7 +95,7 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPreKeyBundle(hexEncodedPublicKey: String, preKeyBundle: PreKeyBundle) {
|
fun setPreKeyBundle(publicKey: String, preKeyBundle: PreKeyBundle) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val values = ContentValues(9)
|
val values = ContentValues(9)
|
||||||
values.put(registrationID, preKeyBundle.registrationId)
|
values.put(registrationID, preKeyBundle.registrationId)
|
||||||
@ -106,20 +106,20 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
|
|||||||
values.put(signedPreKeyPublic, Base64.encodeBytes(preKeyBundle.signedPreKey.serialize()))
|
values.put(signedPreKeyPublic, Base64.encodeBytes(preKeyBundle.signedPreKey.serialize()))
|
||||||
values.put(signedPreKeySignature, Base64.encodeBytes(preKeyBundle.signedPreKeySignature))
|
values.put(signedPreKeySignature, Base64.encodeBytes(preKeyBundle.signedPreKeySignature))
|
||||||
values.put(identityKey, Base64.encodeBytes(preKeyBundle.identityKey.serialize()))
|
values.put(identityKey, Base64.encodeBytes(preKeyBundle.identityKey.serialize()))
|
||||||
values.put(Companion.hexEncodedPublicKey, hexEncodedPublicKey)
|
values.put(Companion.publicKey, publicKey)
|
||||||
database.insertOrUpdate(tableName, values, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ))
|
database.insertOrUpdate(table, values, "${Companion.publicKey} = ?", arrayOf( publicKey ))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removePreKeyBundle(hexEncodedPublicKey: String) {
|
override fun removePreKeyBundle(publicKey: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
database.delete(tableName, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ))
|
database.delete(table, "${Companion.publicKey} = ?", arrayOf( publicKey ))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasPreKeyBundle(hexEncodedPublicKey: String): Boolean {
|
fun hasPreKeyBundle(publicKey: String): Boolean {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
var cursor: Cursor? = null
|
var cursor: Cursor? = null
|
||||||
return try {
|
return try {
|
||||||
cursor = database.query(tableName, null, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ), null, null, null)
|
cursor = database.query(table, null, "${Companion.publicKey} = ?", arrayOf( publicKey ), null, null, null)
|
||||||
cursor != null && cursor.count > 0
|
cursor != null && cursor.count > 0
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
false
|
false
|
||||||
|
@ -14,38 +14,38 @@ import org.whispersystems.signalservice.loki.database.LokiPreKeyRecordDatabasePr
|
|||||||
class LokiPreKeyRecordDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiPreKeyRecordDatabaseProtocol {
|
class LokiPreKeyRecordDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiPreKeyRecordDatabaseProtocol {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val tableName = "loki_pre_key_record_database"
|
private val table = "loki_pre_key_record_database"
|
||||||
private val hexEncodedPublicKey = "public_key"
|
private val publicKey = "public_key"
|
||||||
private val preKeyID = "pre_key_id"
|
private val preKeyID = "pre_key_id"
|
||||||
@JvmStatic val createTableCommand = "CREATE TABLE $tableName ($hexEncodedPublicKey TEXT PRIMARY KEY, $preKeyID INTEGER);"
|
@JvmStatic val createTableCommand = "CREATE TABLE $table ($publicKey TEXT PRIMARY KEY, $preKeyID INTEGER);"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasPreKey(hexEncodedPublicKey: String): Boolean {
|
fun hasPreKey(publicKey: String): Boolean {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(tableName, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey )) { it.count > 0 } ?: false
|
return database.get(table, "${Companion.publicKey} = ?", arrayOf( publicKey )) { it.count > 0 } ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPreKeyRecord(hexEncodedPublicKey: String): PreKeyRecord? {
|
override fun getPreKeyRecord(publicKey: String): PreKeyRecord? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(tableName, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey )) { cursor ->
|
return database.get(table, "${Companion.publicKey} = ?", arrayOf( publicKey )) { cursor ->
|
||||||
val preKeyID = cursor.getInt(preKeyID)
|
val preKeyID = cursor.getInt(preKeyID)
|
||||||
PreKeyUtil.loadPreKey(context, preKeyID)
|
PreKeyUtil.loadPreKey(context, preKeyID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrCreatePreKeyRecord(hexEncodedPublicKey: String): PreKeyRecord {
|
fun getOrCreatePreKeyRecord(publicKey: String): PreKeyRecord {
|
||||||
return getPreKeyRecord(hexEncodedPublicKey) ?: generateAndStorePreKeyRecord(hexEncodedPublicKey)
|
return getPreKeyRecord(publicKey) ?: generateAndStorePreKeyRecord(publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateAndStorePreKeyRecord(hexEncodedPublicKey: String): PreKeyRecord {
|
private fun generateAndStorePreKeyRecord(publicKey: String): PreKeyRecord {
|
||||||
val records = PreKeyUtil.generatePreKeyRecords(context, 1)
|
val records = PreKeyUtil.generatePreKeyRecords(context, 1)
|
||||||
PreKeyUtil.storePreKeyRecords(context, records)
|
PreKeyUtil.storePreKeyRecords(context, records)
|
||||||
val record = records.first()
|
val record = records.first()
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val values = ContentValues(2)
|
val values = ContentValues(2)
|
||||||
values.put(Companion.hexEncodedPublicKey, hexEncodedPublicKey)
|
values.put(Companion.publicKey, publicKey)
|
||||||
values.put(preKeyID, record.id)
|
values.put(preKeyID, record.id)
|
||||||
database.insertOrUpdate(tableName, values, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ))
|
database.insertOrUpdate(table, values, "${Companion.publicKey} = ?", arrayOf( publicKey ))
|
||||||
return record
|
return record
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,27 +11,24 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
|||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
import org.thoughtcrime.securesms.loki.utilities.*
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
import org.whispersystems.libsignal.loki.SessionResetStatus
|
||||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||||
import org.whispersystems.signalservice.loki.database.LokiThreadDatabaseProtocol
|
import org.whispersystems.signalservice.loki.database.LokiThreadDatabaseProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
|
||||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
||||||
|
|
||||||
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
|
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
|
||||||
var delegate: LokiThreadDatabaseDelegate? = null
|
var delegate: LokiThreadDatabaseDelegate? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val friendRequestTableName = "loki_thread_friend_request_database"
|
private val sessionResetTable = "loki_thread_session_reset_database"
|
||||||
private val sessionResetTableName = "loki_thread_session_reset_database"
|
val publicChatTable = "loki_public_chat_database"
|
||||||
val publicChatTableName = "loki_public_chat_database"
|
|
||||||
val threadID = "thread_id"
|
val threadID = "thread_id"
|
||||||
private val friendRequestStatus = "friend_request_status"
|
private val friendRequestStatus = "friend_request_status"
|
||||||
private val sessionResetStatus = "session_reset_status"
|
private val sessionResetStatus = "session_reset_status"
|
||||||
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 $sessionResetTable ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
|
||||||
@JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTableName ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
|
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
|
||||||
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTableName ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getThreadID(hexEncodedPublicKey: String): Long {
|
override fun getThreadID(hexEncodedPublicKey: String): Long {
|
||||||
@ -44,74 +41,40 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
|||||||
return DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID)
|
return DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFriendRequestStatus(threadID: Long): LokiThreadFriendRequestStatus {
|
fun getSessionResetStatus(hexEncodedPublicKey: String): SessionResetStatus {
|
||||||
if (threadID < 0) { return LokiThreadFriendRequestStatus.NONE }
|
|
||||||
val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID)
|
|
||||||
if (recipient != null && recipient.isGroupRecipient) { return LokiThreadFriendRequestStatus.FRIENDS; }
|
|
||||||
val database = databaseHelper.readableDatabase
|
|
||||||
val result = database.get(friendRequestTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
|
|
||||||
cursor.getInt(friendRequestStatus)
|
|
||||||
}
|
|
||||||
return if (result != null) {
|
|
||||||
LokiThreadFriendRequestStatus.values().first { it.rawValue == result }
|
|
||||||
} else {
|
|
||||||
LokiThreadFriendRequestStatus.NONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFriendRequestStatus(threadID: Long, friendRequestStatus: LokiThreadFriendRequestStatus) {
|
|
||||||
if (threadID < 0) { return }
|
|
||||||
Log.d("Loki", "Setting FR status for thread with ID $threadID to $friendRequestStatus.")
|
|
||||||
val database = databaseHelper.writableDatabase
|
|
||||||
val contentValues = ContentValues(2)
|
|
||||||
contentValues.put(Companion.threadID, threadID)
|
|
||||||
contentValues.put(Companion.friendRequestStatus, friendRequestStatus.rawValue)
|
|
||||||
database.insertOrUpdate(friendRequestTableName, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
|
||||||
notifyConversationListListeners()
|
|
||||||
notifyConversationListeners(threadID)
|
|
||||||
delegate?.handleThreadFriendRequestStatusChanged(threadID)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hasPendingFriendRequest(threadID: Long): Boolean {
|
|
||||||
val friendRequestStatus = getFriendRequestStatus(threadID)
|
|
||||||
return friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT
|
|
||||||
|| friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSessionResetStatus(hexEncodedPublicKey: String): LokiSessionResetStatus {
|
|
||||||
val threadID = getThreadID(hexEncodedPublicKey)
|
val threadID = getThreadID(hexEncodedPublicKey)
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
val result = database.get(sessionResetTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
|
val result = database.get(sessionResetTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
|
||||||
cursor.getInt(sessionResetStatus)
|
cursor.getInt(sessionResetStatus)
|
||||||
}
|
}
|
||||||
return if (result != null) {
|
return if (result != null) {
|
||||||
LokiSessionResetStatus.values().first { it.rawValue == result }
|
SessionResetStatus.values().first { it.rawValue == result }
|
||||||
} else {
|
} else {
|
||||||
LokiSessionResetStatus.NONE
|
SessionResetStatus.NONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: LokiSessionResetStatus) {
|
fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: SessionResetStatus) {
|
||||||
val threadID = getThreadID(hexEncodedPublicKey)
|
val threadID = getThreadID(hexEncodedPublicKey)
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val contentValues = ContentValues(2)
|
val contentValues = ContentValues(2)
|
||||||
contentValues.put(Companion.threadID, threadID)
|
contentValues.put(Companion.threadID, threadID)
|
||||||
contentValues.put(Companion.sessionResetStatus, sessionResetStatus.rawValue)
|
contentValues.put(Companion.sessionResetStatus, sessionResetStatus.rawValue)
|
||||||
database.insertOrUpdate(sessionResetTableName, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
database.insertOrUpdate(sessionResetTable, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
||||||
notifyConversationListListeners()
|
notifyConversationListListeners()
|
||||||
notifyConversationListeners(threadID)
|
notifyConversationListeners(threadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllPublicChats(): Map<Long, LokiPublicChat> {
|
fun getAllPublicChats(): Map<Long, PublicChat> {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
var cursor: Cursor? = null
|
var cursor: Cursor? = null
|
||||||
val result = mutableMapOf<Long, LokiPublicChat>()
|
val result = mutableMapOf<Long, PublicChat>()
|
||||||
try {
|
try {
|
||||||
cursor = database.rawQuery("select * from $publicChatTableName", null)
|
cursor = database.rawQuery("select * from $publicChatTable", null)
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
val threadID = cursor.getLong(threadID)
|
val threadID = cursor.getLong(threadID)
|
||||||
val string = cursor.getString(publicChat)
|
val string = cursor.getString(publicChat)
|
||||||
val publicChat = LokiPublicChat.fromJSON(string)
|
val publicChat = PublicChat.fromJSON(string)
|
||||||
if (publicChat != null) { result[threadID] = publicChat }
|
if (publicChat != null) { result[threadID] = publicChat }
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -126,31 +89,31 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
|||||||
return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) }
|
return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPublicChat(threadID: Long): LokiPublicChat? {
|
override fun getPublicChat(threadID: Long): PublicChat? {
|
||||||
if (threadID < 0) { return null }
|
if (threadID < 0) { return null }
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(publicChatTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
|
return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
|
||||||
val publicChatAsJSON = cursor.getString(publicChat)
|
val publicChatAsJSON = cursor.getString(publicChat)
|
||||||
LokiPublicChat.fromJSON(publicChatAsJSON)
|
PublicChat.fromJSON(publicChatAsJSON)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setPublicChat(publicChat: LokiPublicChat, threadID: Long) {
|
override fun setPublicChat(publicChat: PublicChat, threadID: Long) {
|
||||||
if (threadID < 0) { return }
|
if (threadID < 0) { return }
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val contentValues = ContentValues(2)
|
val contentValues = ContentValues(2)
|
||||||
contentValues.put(Companion.threadID, threadID)
|
contentValues.put(Companion.threadID, threadID)
|
||||||
contentValues.put(Companion.publicChat, JsonUtil.toJson(publicChat.toJSON()))
|
contentValues.put(Companion.publicChat, JsonUtil.toJson(publicChat.toJSON()))
|
||||||
database.insertOrUpdate(publicChatTableName, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removePublicChat(threadID: Long) {
|
override fun removePublicChat(threadID: Long) {
|
||||||
databaseHelper.writableDatabase.delete(publicChatTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
databaseHelper.writableDatabase.delete(publicChatTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addSessionRestoreDevice(threadID: Long, hexEncodedPublicKey: String) {
|
fun addSessionRestoreDevice(threadID: Long, publicKey: String) {
|
||||||
val devices = getSessionRestoreDevices(threadID).toMutableSet()
|
val devices = getSessionRestoreDevices(threadID).toMutableSet()
|
||||||
if (devices.add(hexEncodedPublicKey)) {
|
if (devices.add(publicKey)) {
|
||||||
TextSecurePreferences.setStringPreference(context, "session_restore_devices_$threadID", devices.joinToString(","))
|
TextSecurePreferences.setStringPreference(context, "session_restore_devices_$threadID", devices.joinToString(","))
|
||||||
delegate?.handleSessionRestoreDevicesChanged(threadID)
|
delegate?.handleSessionRestoreDevicesChanged(threadID)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,5 @@ package org.thoughtcrime.securesms.loki.database
|
|||||||
|
|
||||||
interface LokiThreadDatabaseDelegate {
|
interface LokiThreadDatabaseDelegate {
|
||||||
|
|
||||||
fun handleThreadFriendRequestStatusChanged(threadID: Long)
|
|
||||||
fun handleSessionRestoreDevicesChanged(threadID: Long)
|
fun handleSessionRestoreDevicesChanged(threadID: Long)
|
||||||
}
|
}
|
@ -20,60 +20,60 @@ class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database
|
|||||||
private val displayName = "display_name"
|
private val displayName = "display_name"
|
||||||
// Display name cache
|
// Display name cache
|
||||||
private val displayNameTable = "loki_user_display_name_database"
|
private val displayNameTable = "loki_user_display_name_database"
|
||||||
private val hexEncodedPublicKey = "hex_encoded_public_key"
|
private val publicKey = "hex_encoded_public_key"
|
||||||
@JvmStatic val createDisplayNameTableCommand = "CREATE TABLE $displayNameTable ($hexEncodedPublicKey TEXT PRIMARY KEY, $displayName TEXT);"
|
@JvmStatic val createDisplayNameTableCommand = "CREATE TABLE $displayNameTable ($publicKey TEXT PRIMARY KEY, $displayName TEXT);"
|
||||||
// Server display name cache
|
// Server display name cache
|
||||||
private val serverDisplayNameTable = "loki_user_server_display_name_database"
|
private val serverDisplayNameTable = "loki_user_server_display_name_database"
|
||||||
private val serverID = "server_id"
|
private val serverID = "server_id"
|
||||||
@JvmStatic val createServerDisplayNameTableCommand = "CREATE TABLE $serverDisplayNameTable ($hexEncodedPublicKey TEXT, $serverID TEXT, $displayName TEXT, PRIMARY KEY ($hexEncodedPublicKey, $serverID));"
|
@JvmStatic val createServerDisplayNameTableCommand = "CREATE TABLE $serverDisplayNameTable ($publicKey TEXT, $serverID TEXT, $displayName TEXT, PRIMARY KEY ($publicKey, $serverID));"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDisplayName(hexEncodedPublicKey: String): String? {
|
override fun getDisplayName(publicKey: String): String? {
|
||||||
if (hexEncodedPublicKey == TextSecurePreferences.getLocalNumber(context)) {
|
if (publicKey == TextSecurePreferences.getLocalNumber(context)) {
|
||||||
return TextSecurePreferences.getProfileName(context)
|
return TextSecurePreferences.getProfileName(context)
|
||||||
} else {
|
} else {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(displayNameTable, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey )) { cursor ->
|
return database.get(displayNameTable, "${Companion.publicKey} = ?", arrayOf( publicKey )) { cursor ->
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(displayName))
|
cursor.getString(cursor.getColumnIndexOrThrow(displayName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDisplayName(hexEncodedPublicKey: String, displayName: String) {
|
fun setDisplayName(publicKey: String, displayName: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val row = ContentValues(2)
|
val row = ContentValues(2)
|
||||||
row.put(Companion.hexEncodedPublicKey, hexEncodedPublicKey)
|
row.put(Companion.publicKey, publicKey)
|
||||||
row.put(Companion.displayName, displayName)
|
row.put(Companion.displayName, displayName)
|
||||||
database.insertOrUpdate(displayNameTable, row, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ))
|
database.insertOrUpdate(displayNameTable, row, "${Companion.publicKey} = ?", arrayOf( publicKey ))
|
||||||
Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).notifyListeners()
|
Recipient.from(context, Address.fromSerialized(publicKey), false).notifyListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getServerDisplayName(serverID: String, hexEncodedPublicKey: String): String? {
|
override fun getServerDisplayName(serverID: String, publicKey: String): String? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(serverDisplayNameTable, "${Companion.hexEncodedPublicKey} = ? AND ${Companion.serverID} = ?", arrayOf( hexEncodedPublicKey, serverID )) { cursor ->
|
return database.get(serverDisplayNameTable, "${Companion.publicKey} = ? AND ${Companion.serverID} = ?", arrayOf( publicKey, serverID )) { cursor ->
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(displayName))
|
cursor.getString(cursor.getColumnIndexOrThrow(displayName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setServerDisplayName(serverID: String, hexEncodedPublicKey: String, displayName: String) {
|
fun setServerDisplayName(serverID: String, publicKey: String, displayName: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val values = ContentValues(3)
|
val values = ContentValues(3)
|
||||||
values.put(Companion.serverID, serverID)
|
values.put(Companion.serverID, serverID)
|
||||||
values.put(Companion.hexEncodedPublicKey, hexEncodedPublicKey)
|
values.put(Companion.publicKey, publicKey)
|
||||||
values.put(Companion.displayName, displayName)
|
values.put(Companion.displayName, displayName)
|
||||||
try {
|
try {
|
||||||
database.insertWithOnConflict(serverDisplayNameTable, null, values, SQLiteDatabase.CONFLICT_REPLACE)
|
database.insertWithOnConflict(serverDisplayNameTable, null, values, SQLiteDatabase.CONFLICT_REPLACE)
|
||||||
Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).notifyListeners()
|
Recipient.from(context, Address.fromSerialized(publicKey), false).notifyListeners()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Couldn't save server display name due to exception: $e.")
|
Log.d("Loki", "Couldn't save server display name due to exception: $e.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getProfilePictureURL(hexEncodedPublicKey: String): String? {
|
override fun getProfilePictureURL(publicKey: String): String? {
|
||||||
return if (hexEncodedPublicKey == TextSecurePreferences.getLocalNumber(context)) {
|
return if (publicKey == TextSecurePreferences.getLocalNumber(context)) {
|
||||||
TextSecurePreferences.getProfilePictureURL(context)
|
TextSecurePreferences.getProfilePictureURL(context)
|
||||||
} else {
|
} else {
|
||||||
Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).resolve().profileAvatar
|
Recipient.from(context, Address.fromSerialized(publicKey), false).resolve().profileAvatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.dialogs
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.design.widget.BottomSheetDialogFragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import kotlinx.android.synthetic.main.fragment_conversation_bottom_sheet.*
|
||||||
|
import kotlinx.android.synthetic.main.fragment_device_list_bottom_sheet.*
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
|
public class ConversationOptionsBottomSheet : BottomSheetDialogFragment() {
|
||||||
|
lateinit var recipient: Recipient
|
||||||
|
var onBlockOrUnblockTapped: (() -> Unit)? = null
|
||||||
|
var onDeleteTapped: (() -> Unit)? = null
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_conversation_bottom_sheet, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
if (!recipient.isGroupRecipient && !recipient.isLocalNumber) {
|
||||||
|
val textID = if (recipient.isBlocked) R.string.RecipientPreferenceActivity_unblock else R.string.RecipientPreferenceActivity_block
|
||||||
|
blockOrUnblockTextView.setText(textID)
|
||||||
|
val iconID = if (recipient.isBlocked) R.drawable.ic_check_white_24dp else R.drawable.ic_block_white_24dp
|
||||||
|
val icon = context!!.resources.getDrawable(iconID, context!!.theme)
|
||||||
|
blockOrUnblockTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null)
|
||||||
|
blockOrUnblockTextView.setOnClickListener { onBlockOrUnblockTapped?.invoke() }
|
||||||
|
} else {
|
||||||
|
blockOrUnblockTextView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
deleteTextView.setOnClickListener { onDeleteTapped?.invoke() }
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.loki.utilities.toPx
|
|||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession
|
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession
|
||||||
@ -52,7 +52,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun requestUserAuthorization(deviceLink: DeviceLink) {
|
override fun requestUserAuthorization(deviceLink: DeviceLink) {
|
||||||
if (deviceLink.type != DeviceLink.Type.REQUEST || deviceLink.masterHexEncodedPublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
|
if (deviceLink.type != DeviceLink.Type.REQUEST || deviceLink.masterPublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
|
||||||
Util.runOnMain {
|
Util.runOnMain {
|
||||||
this.deviceLink = deviceLink
|
this.deviceLink = deviceLink
|
||||||
contentView.qrCodeImageView.visibility = View.GONE
|
contentView.qrCodeImageView.visibility = View.GONE
|
||||||
@ -62,7 +62,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
|||||||
contentView.titleTextView.text = resources.getString(R.string.dialog_link_device_master_mode_title_2)
|
contentView.titleTextView.text = resources.getString(R.string.dialog_link_device_master_mode_title_2)
|
||||||
contentView.explanationTextView.text = resources.getString(R.string.dialog_link_device_master_mode_explanation_2)
|
contentView.explanationTextView.text = resources.getString(R.string.dialog_link_device_master_mode_explanation_2)
|
||||||
contentView.mnemonicTextView.visibility = View.VISIBLE
|
contentView.mnemonicTextView.visibility = View.VISIBLE
|
||||||
contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), deviceLink.slaveHexEncodedPublicKey)
|
contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), deviceLink.slavePublicKey)
|
||||||
contentView.authorizeButton.visibility = View.VISIBLE
|
contentView.authorizeButton.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
|||||||
contentView.cancelButton.visibility = View.GONE
|
contentView.cancelButton.visibility = View.GONE
|
||||||
contentView.authorizeButton.visibility = View.GONE
|
contentView.authorizeButton.visibility = View.GONE
|
||||||
}
|
}
|
||||||
LokiFileServerAPI.shared.addDeviceLink(deviceLink).bind(SnodeAPI.sharedContext) {
|
FileServerAPI.shared.addDeviceLink(deviceLink).bind(SnodeAPI.sharedContext) {
|
||||||
MultiDeviceProtocol.signAndSendDeviceLinkMessage(context!!, deviceLink)
|
MultiDeviceProtocol.signAndSendDeviceLinkMessage(context!!, deviceLink)
|
||||||
}.success {
|
}.success {
|
||||||
TextSecurePreferences.setMultiDevice(context!!, true)
|
TextSecurePreferences.setMultiDevice(context!!, true)
|
||||||
@ -92,8 +92,8 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
|||||||
delegate?.onDeviceLinkRequestAuthorized()
|
delegate?.onDeviceLinkRequestAuthorized()
|
||||||
dismiss()
|
dismiss()
|
||||||
}.fail {
|
}.fail {
|
||||||
LokiFileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
|
FileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
|
||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context!!).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)
|
DatabaseFactory.getLokiPreKeyBundleDatabase(context!!).removePreKeyBundle(deviceLink.slavePublicKey)
|
||||||
}.failUi {
|
}.failUi {
|
||||||
delegate?.onDeviceLinkAuthorizationFailed()
|
delegate?.onDeviceLinkAuthorizationFailed()
|
||||||
dismiss()
|
dismiss()
|
||||||
@ -104,7 +104,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
|||||||
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
||||||
DeviceLinkingSession.shared.removeListener(this)
|
DeviceLinkingSession.shared.removeListener(this)
|
||||||
if (deviceLink != null) {
|
if (deviceLink != null) {
|
||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(deviceLink!!.slaveHexEncodedPublicKey)
|
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(deviceLink!!.slavePublicKey)
|
||||||
}
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
delegate?.onDeviceLinkCanceled()
|
delegate?.onDeviceLinkCanceled()
|
||||||
|
@ -41,7 +41,7 @@ class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
|
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
|
||||||
if (deviceLink.type != DeviceLink.Type.AUTHORIZATION || deviceLink.slaveHexEncodedPublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
|
if (deviceLink.type != DeviceLink.Type.AUTHORIZATION || deviceLink.slavePublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
|
||||||
Util.runOnMain {
|
Util.runOnMain {
|
||||||
this.deviceLink = deviceLink
|
this.deviceLink = deviceLink
|
||||||
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
||||||
|
@ -15,10 +15,10 @@ sealed class ContactSelectionListItem {
|
|||||||
class ContactSelectionListLoader(context: Context, val mode: Int, val filter: String?) : AsyncLoader<List<ContactSelectionListItem>>(context) {
|
class ContactSelectionListLoader(context: Context, val mode: Int, val filter: String?) : AsyncLoader<List<ContactSelectionListItem>>(context) {
|
||||||
|
|
||||||
object DisplayMode {
|
object DisplayMode {
|
||||||
const val FLAG_FRIENDS = 1
|
const val FLAG_CONTACTS = 1
|
||||||
const val FLAG_CLOSED_GROUPS = 1 shl 1
|
const val FLAG_CLOSED_GROUPS = 1 shl 1
|
||||||
const val FLAG_OPEN_GROUPS = 1 shl 2
|
const val FLAG_OPEN_GROUPS = 1 shl 2
|
||||||
const val FLAG_ALL = FLAG_FRIENDS or FLAG_CLOSED_GROUPS or FLAG_OPEN_GROUPS
|
const val FLAG_ALL = FLAG_CONTACTS or FLAG_CLOSED_GROUPS or FLAG_OPEN_GROUPS
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isFlagSet(flag: Int): Boolean {
|
private fun isFlagSet(flag: Int): Boolean {
|
||||||
@ -39,15 +39,15 @@ class ContactSelectionListLoader(context: Context, val mode: Int, val filter: St
|
|||||||
if (isFlagSet(DisplayMode.FLAG_OPEN_GROUPS)) {
|
if (isFlagSet(DisplayMode.FLAG_OPEN_GROUPS)) {
|
||||||
list.addAll(getOpenGroups(contacts))
|
list.addAll(getOpenGroups(contacts))
|
||||||
}
|
}
|
||||||
if (isFlagSet(DisplayMode.FLAG_FRIENDS)) {
|
if (isFlagSet(DisplayMode.FLAG_CONTACTS)) {
|
||||||
list.addAll(getFriends(contacts))
|
list.addAll(getContacts(contacts))
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFriends(contacts: List<Contact>): List<ContactSelectionListItem> {
|
private fun getContacts(contacts: List<Contact>): List<ContactSelectionListItem> {
|
||||||
return getItems(contacts, context.getString(R.string.fragment_contact_selection_contacts_title)) {
|
return getItems(contacts, context.getString(R.string.fragment_contact_selection_contacts_title)) {
|
||||||
!it.recipient.isGroupRecipient && it.isFriend && !it.isOurDevice && !it.isSlave
|
!it.recipient.isGroupRecipient && !it.isOurDevice && !it.isSlave
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ object ClosedGroupsProtocol {
|
|||||||
if (!conversation.address.isClosedGroup || groupID == null) { return false }
|
if (!conversation.address.isClosedGroup || groupID == null) { return false }
|
||||||
// A closed group's members should never include slave devices
|
// A closed group's members should never include slave devices
|
||||||
val senderPublicKey = content.sender
|
val senderPublicKey = content.sender
|
||||||
LokiFileServerAPI.shared.getDeviceLinks(senderPublicKey).timeout(6000).get()
|
FileServerAPI.shared.getDeviceLinks(senderPublicKey).timeout(6000).get()
|
||||||
val senderMasterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(senderPublicKey)
|
val senderMasterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(senderPublicKey)
|
||||||
val publicKeyToCheckFor = senderMasterPublicKey ?: senderPublicKey
|
val publicKeyToCheckFor = senderMasterPublicKey ?: senderPublicKey
|
||||||
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, true)
|
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, true)
|
||||||
@ -57,7 +57,7 @@ object ClosedGroupsProtocol {
|
|||||||
} else {
|
} else {
|
||||||
// A closed group's members should never include slave devices
|
// A closed group's members should never include slave devices
|
||||||
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, false)
|
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, false)
|
||||||
return LokiFileServerAPI.shared.getDeviceLinks(members.map { it.address.serialize() }.toSet()).map {
|
return FileServerAPI.shared.getDeviceLinks(members.map { it.address.serialize() }.toSet()).map {
|
||||||
val result = members.flatMap { member ->
|
val result = members.flatMap { member ->
|
||||||
MultiDeviceProtocol.shared.getAllLinkedDevices(member.address.serialize()).map { Address.fromSerialized(it) }
|
MultiDeviceProtocol.shared.getAllLinkedDevices(member.address.serialize()).map { Address.fromSerialized(it) }
|
||||||
}.toMutableSet()
|
}.toMutableSet()
|
||||||
@ -106,7 +106,7 @@ object ClosedGroupsProtocol {
|
|||||||
allDevices.remove(userPublicKey)
|
allDevices.remove(userPublicKey)
|
||||||
}
|
}
|
||||||
for (device in allDevices) {
|
for (device in allDevices) {
|
||||||
ApplicationContext.getInstance(context).sendSessionRequest(device)
|
ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,34 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
|
||||||
|
|
||||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
|
||||||
|
|
||||||
class EphemeralMessage private constructor(val data: Map<*, *>) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun create(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey ))
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun createUnlinkingRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "unpairingRequest" to true ))
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun createSessionRestorationRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "friendRequest" to true, "sessionRestore" to true ))
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun createSessionRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "friendRequest" to true, "sessionRequest" to true ))
|
|
||||||
|
|
||||||
internal fun parse(serialized: String): EphemeralMessage {
|
|
||||||
val data = JsonUtil.fromJson(serialized, Map::class.java) ?: throw IllegalArgumentException("Couldn't parse string to JSON")
|
|
||||||
return EphemeralMessage(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> get(key: String, defaultValue: T): T {
|
|
||||||
return data[key] as? T ?: defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun serialize(): String {
|
|
||||||
return JsonUtil.toJson(data)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,318 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
|
||||||
import org.thoughtcrime.securesms.database.Address
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.recipient
|
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender
|
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage
|
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
|
||||||
|
|
||||||
object FriendRequestProtocol {
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun acceptFriendRequest(context: Context, recipient: Recipient) {
|
|
||||||
if (recipient.isGroupRecipient) { return; }
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
||||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
|
||||||
// Accept all outstanding friend requests associated with this user and try to establish sessions with the
|
|
||||||
// subset of their devices that haven't sent a friend request.
|
|
||||||
val allContactDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.address.serialize())
|
|
||||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
|
||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
for (device in allContactDevices) {
|
|
||||||
val deviceAsRecipient = recipient(context, device)
|
|
||||||
val deviceThreadID = threadDB.getThreadIdFor(deviceAsRecipient)
|
|
||||||
val deviceFRStatus = lokiThreadDB.getFriendRequestStatus(deviceThreadID)
|
|
||||||
if (deviceFRStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
|
|
||||||
lokiThreadDB.setFriendRequestStatus(deviceThreadID, LokiThreadFriendRequestStatus.FRIENDS)
|
|
||||||
val lastMessageID = getLastMessageID(context, deviceThreadID)
|
|
||||||
if (lastMessageID != null) {
|
|
||||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
|
||||||
}
|
|
||||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient(context, device), true)
|
|
||||||
val ephemeralMessage = EphemeralMessage.create(device)
|
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
|
||||||
// Sync contact if needed
|
|
||||||
if (allUserDevices.contains(device)) { return }
|
|
||||||
val deviceToSync = MultiDeviceProtocol.shared.getMasterDevice(device) ?: device
|
|
||||||
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(deviceToSync))
|
|
||||||
} else if (deviceFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
|
||||||
// Do nothing
|
|
||||||
} else if (!allUserDevices.contains(device)
|
|
||||||
&& (deviceFRStatus == LokiThreadFriendRequestStatus.NONE || deviceFRStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED)) {
|
|
||||||
sendAutoGeneratedFriendRequest(context, device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun rejectFriendRequest(context: Context, recipient: Recipient) {
|
|
||||||
if (recipient.isGroupRecipient) { return; }
|
|
||||||
val linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.address.serialize())
|
|
||||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
|
||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
for (device in linkedDevices) {
|
|
||||||
val deviceAsRecipient = recipient(context, device)
|
|
||||||
val deviceThreadID = threadDB.getThreadIdFor(deviceAsRecipient)
|
|
||||||
val deviceFRStatus = lokiThreadDB.getFriendRequestStatus(deviceThreadID)
|
|
||||||
// We only want to decline incoming requests
|
|
||||||
if (deviceFRStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
|
|
||||||
// Delete the pre key bundle for the given contact. This ensures that if we send a
|
|
||||||
// new message after this, it restarts the friend request process from scratch.
|
|
||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(device)
|
|
||||||
lokiThreadDB.setFriendRequestStatus(deviceThreadID, LokiThreadFriendRequestStatus.NONE)
|
|
||||||
val lastMessageID = getLastMessageID(context, deviceThreadID)
|
|
||||||
if (lastMessageID != null) {
|
|
||||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_REJECTED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun shouldInputPanelBeEnabled(context: Context, recipient: Recipient): Boolean {
|
|
||||||
// Friend requests have nothing to do with groups, so if this is a group thread the input panel should be enabled
|
|
||||||
if (recipient.isGroupRecipient) { return true }
|
|
||||||
// If this is a note to self the input panel should be enabled
|
|
||||||
if (SessionMetaProtocol.shared.isNoteToSelf(recipient.address.serialize())) { return true }
|
|
||||||
// Gather friend request statuses
|
|
||||||
val linkedDeviceFRStatuses = mutableSetOf<LokiThreadFriendRequestStatus>()
|
|
||||||
val linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.address.serialize())
|
|
||||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
|
||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
for (device in linkedDevices) {
|
|
||||||
val deviceAsRecipient = recipient(context, device)
|
|
||||||
val deviceThreadID = threadDB.getThreadIdFor(deviceAsRecipient)
|
|
||||||
val deviceFRStatus = lokiThreadDB.getFriendRequestStatus(deviceThreadID)
|
|
||||||
linkedDeviceFRStatuses.add(deviceFRStatus)
|
|
||||||
}
|
|
||||||
// If the user is friends with any of the other user's devices the input panel should be enabled
|
|
||||||
if (linkedDeviceFRStatuses.contains(LokiThreadFriendRequestStatus.FRIENDS)) { return true }
|
|
||||||
// If no friend request has been sent the input panel should be enabled
|
|
||||||
if (linkedDeviceFRStatuses.all { it == LokiThreadFriendRequestStatus.NONE || it == LokiThreadFriendRequestStatus.REQUEST_EXPIRED }) { return true }
|
|
||||||
// There must be a pending friend request
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun shouldAttachmentButtonBeEnabled(context: Context, recipient: Recipient): Boolean {
|
|
||||||
// Friend requests have nothing to do with groups, so if this is a group thread the attachment button should be enabled
|
|
||||||
if (recipient.isGroupRecipient) { return true }
|
|
||||||
// If this is a note to self the attachment button should be enabled
|
|
||||||
if (SessionMetaProtocol.shared.isNoteToSelf(recipient.address.serialize())) { return true }
|
|
||||||
// Gather friend request statuses
|
|
||||||
val linkedDeviceFRStatuses = mutableSetOf<LokiThreadFriendRequestStatus>()
|
|
||||||
val linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.address.serialize())
|
|
||||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
|
||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
for (device in linkedDevices) {
|
|
||||||
val deviceAsRecipient = recipient(context, device)
|
|
||||||
val deviceThreadID = threadDB.getThreadIdFor(deviceAsRecipient)
|
|
||||||
val deviceFRStatus = lokiThreadDB.getFriendRequestStatus(deviceThreadID)
|
|
||||||
linkedDeviceFRStatuses.add(deviceFRStatus)
|
|
||||||
}
|
|
||||||
// If the user is friends with any of the other user's devices the attachment button should be enabled
|
|
||||||
if (linkedDeviceFRStatuses.contains(LokiThreadFriendRequestStatus.FRIENDS)) { return true }
|
|
||||||
// Otherwise don't allow attachments
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getLastMessageID(context: Context, threadID: Long): Long? {
|
|
||||||
val db = DatabaseFactory.getSmsDatabase(context)
|
|
||||||
val messageCount = db.getMessageCountForThread(threadID)
|
|
||||||
if (messageCount == 0) { return null }
|
|
||||||
return db.getIDForMessageAtIndex(threadID, messageCount - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun handleFriendRequestAcceptanceIfNeeded(context: Context, publicKey: String, content: SignalServiceContent) {
|
|
||||||
// If we get an envelope that isn't a friend request, then we can infer that we had to use
|
|
||||||
// Signal cipher decryption and thus that we have a session with the other person.
|
|
||||||
if (content.isFriendRequest) { return }
|
|
||||||
val recipient = recipient(context, publicKey)
|
|
||||||
// Friend requests don't apply to groups
|
|
||||||
if (recipient.isGroupRecipient) { return }
|
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
|
||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
|
||||||
// Guard against invalid state transitions
|
|
||||||
if (threadFRStatus != LokiThreadFriendRequestStatus.REQUEST_SENDING && threadFRStatus != LokiThreadFriendRequestStatus.REQUEST_SENT
|
|
||||||
&& threadFRStatus != LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { return }
|
|
||||||
Log.d("Loki", "Received a friend request accepted message from $publicKey.")
|
|
||||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS)
|
|
||||||
val lastMessageID = getLastMessageID(context, threadID)
|
|
||||||
if (lastMessageID != null) {
|
|
||||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
|
||||||
}
|
|
||||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true)
|
|
||||||
// Send a contact sync message if needed
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
||||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
|
||||||
if (allUserDevices.contains(publicKey)) { return }
|
|
||||||
val deviceToSync = MultiDeviceProtocol.shared.getMasterDevice(publicKey) ?: publicKey
|
|
||||||
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(deviceToSync))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun canFriendRequestBeAutoAccepted(context: Context, publicKey: String): Boolean {
|
|
||||||
val recipient = recipient(context, publicKey)
|
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
|
||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
|
||||||
if (threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
|
||||||
// This can happen if Alice sent Bob a friend request, Bob declined, but then Bob changed his
|
|
||||||
// mind and sent a friend request to Alice. In this case we want Alice to auto-accept the request
|
|
||||||
// and send a friend request accepted message back to Bob. We don't check that sending the
|
|
||||||
// friend request accepted message succeeds. Even if it doesn't, the thread's current friend
|
|
||||||
// request status will be set to FRIENDS for Alice making it possible for Alice to send messages
|
|
||||||
// to Bob. When Bob receives a message, his thread's friend request status will then be set to
|
|
||||||
// FRIENDS. If we do check for a successful send before updating Alice's thread's friend request
|
|
||||||
// status to FRIENDS, we can end up in a deadlock where both users' threads' friend request statuses
|
|
||||||
// are SENT.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Auto-accept any friend requests from the user's own linked devices
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
||||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
|
||||||
if (allUserDevices.contains(publicKey)) { return true }
|
|
||||||
// Auto-accept if the user is friends with any of the sender's linked devices.
|
|
||||||
val allSenderDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(publicKey)
|
|
||||||
if (allSenderDevices.any { device ->
|
|
||||||
val deviceAsRecipient = recipient(context, publicKey)
|
|
||||||
val deviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(deviceAsRecipient)
|
|
||||||
lokiThreadDB.getFriendRequestStatus(deviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS
|
|
||||||
}) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun handleFriendRequestMessageIfNeeded(context: Context, publicKey: String, content: SignalServiceContent) {
|
|
||||||
if (!content.isFriendRequest) { return }
|
|
||||||
val recipient = recipient(context, publicKey)
|
|
||||||
// Friend requests don't apply to groups
|
|
||||||
if (recipient.isGroupRecipient) { return }
|
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
|
||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
|
||||||
if (canFriendRequestBeAutoAccepted(context, publicKey)) {
|
|
||||||
Log.d("Loki", "Auto-accepting friend request from $publicKey.")
|
|
||||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS)
|
|
||||||
val lastMessageID = getLastMessageID(context, threadID)
|
|
||||||
if (lastMessageID != null) {
|
|
||||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
|
||||||
}
|
|
||||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true)
|
|
||||||
val ephemeralMessage = EphemeralMessage.create(publicKey)
|
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
|
||||||
} else if (threadFRStatus != LokiThreadFriendRequestStatus.FRIENDS) {
|
|
||||||
Log.d("Loki", "Handling friend request from $publicKey.")
|
|
||||||
// Checking that the sender of the message isn't already a friend is necessary because otherwise
|
|
||||||
// the following situation can occur: Alice and Bob are friends. Bob loses his database and his
|
|
||||||
// friend request status is reset to NONE. Bob now sends Alice a friend request. Alice's thread's
|
|
||||||
// friend request status is reset to RECEIVED
|
|
||||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED)
|
|
||||||
val masterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(publicKey) ?: publicKey
|
|
||||||
val masterThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient(context, masterPublicKey))
|
|
||||||
val masterThreadLastMessageID = getLastMessageID(context, masterThreadID) // Messages get routed into the master thread
|
|
||||||
if (masterThreadLastMessageID != null) {
|
|
||||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(masterThreadLastMessageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
|
||||||
} else {
|
|
||||||
// Device link fetching could fail, in which case the message could get routed into the slave thread
|
|
||||||
val slaveThreadLastMessageID = getLastMessageID(context, threadID)
|
|
||||||
if (slaveThreadLastMessageID != null) {
|
|
||||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(slaveThreadLastMessageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun isFriendRequestFromBeforeRestoration(context: Context, content: SignalServiceContent): Boolean {
|
|
||||||
return content.isFriendRequest && content.timestamp < TextSecurePreferences.getRestorationTime(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun shouldUpdateFriendRequestStatusFromOutgoingTextMessage(context: Context, message: OutgoingTextMessage): Boolean {
|
|
||||||
// The order of these checks matters
|
|
||||||
if (message.recipient.isGroupRecipient) { return false }
|
|
||||||
if (message.recipient.address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
|
|
||||||
// TODO: Return true if the message is a device linking request
|
|
||||||
// TODO: Return false if the message is a session request
|
|
||||||
return message.isFriendRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun shouldUpdateFriendRequestStatusFromOutgoingMediaMessage(context: Context, message: OutgoingMediaMessage): Boolean {
|
|
||||||
// The order of these checks matters
|
|
||||||
if (message.recipient.isGroupRecipient) { return false }
|
|
||||||
if (message.recipient.address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
|
|
||||||
// TODO: Return true if the message is a device linking request
|
|
||||||
// TODO: Return false if the message is a session request
|
|
||||||
return message.isFriendRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun setFriendRequestStatusToSendingIfNeeded(context: Context, messageID: Long, threadID: Long) {
|
|
||||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
|
||||||
val messageFRStatus = messageDB.getFriendRequestStatus(messageID)
|
|
||||||
if (messageFRStatus == LokiMessageFriendRequestStatus.NONE || messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_EXPIRED) {
|
|
||||||
messageDB.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_SENDING)
|
|
||||||
}
|
|
||||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
val threadFRStatus = threadDB.getFriendRequestStatus(threadID)
|
|
||||||
if (threadFRStatus == LokiThreadFriendRequestStatus.NONE || threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
|
|
||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_SENDING)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun setFriendRequestStatusToSentIfNeeded(context: Context, messageID: Long, threadID: Long) {
|
|
||||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
|
||||||
val messageFRStatus = messageDB.getFriendRequestStatus(messageID)
|
|
||||||
if (messageFRStatus == LokiMessageFriendRequestStatus.NONE || messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_EXPIRED
|
|
||||||
|| messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_SENDING) {
|
|
||||||
messageDB.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
|
||||||
}
|
|
||||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
val threadFRStatus = threadDB.getFriendRequestStatus(threadID)
|
|
||||||
if (threadFRStatus == LokiThreadFriendRequestStatus.NONE || threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED
|
|
||||||
|| threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING) {
|
|
||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_SENT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun setFriendRequestStatusToFailedIfNeeded(context: Context, messageID: Long, threadID: Long) {
|
|
||||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
|
||||||
val messageFRStatus = messageDB.getFriendRequestStatus(messageID)
|
|
||||||
if (messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_SENDING) {
|
|
||||||
messageDB.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_FAILED)
|
|
||||||
}
|
|
||||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
|
||||||
val threadFRStatus = threadDB.getFriendRequestStatus(threadID)
|
|
||||||
if (threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING) {
|
|
||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.NONE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendAutoGeneratedFriendRequest(context: Context, publicKey: String) {
|
|
||||||
val recipient = recipient(context, publicKey)
|
|
||||||
val message = OutgoingEncryptedMessage(recipient, "Please accept to enable messages to be synced across devices", 0)
|
|
||||||
message.isFriendRequest = true
|
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
|
||||||
MessageSender.send(context, message, threadID, false, null)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
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) {
|
|
||||||
val ephemeralMessage = EphemeralMessage.create(hexEncodedPublicKey)
|
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
|
||||||
}
|
|
||||||
// TODO: Show session reset succeed message
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun validatePreKeySignalMessage(sender: String, message: PreKeySignalMessage) {
|
|
||||||
val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getPreKeyRecord(sender) ?: return
|
|
||||||
// TODO: Checking that the pre key record isn't null is causing issues when it shouldn't
|
|
||||||
check(preKeyRecord.id == (message.preKeyId ?: -1)) { "Received a background message from an unknown source." }
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.logging.Log
|
|||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters)
|
|||||||
|
|
||||||
constructor() : this(Parameters.Builder()
|
constructor() : this(Parameters.Builder()
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
.setQueue("MultiDeviceOpenGroupUpdateJob")
|
.setQueue(KEY)
|
||||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
.setMaxAttempts(Parameters.UNLIMITED)
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
.build())
|
.build())
|
||||||
@ -43,7 +43,7 @@ class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Gather open groups
|
// Gather open groups
|
||||||
val openGroups = mutableListOf<LokiPublicChat>()
|
val openGroups = mutableListOf<PublicChat>()
|
||||||
DatabaseFactory.getGroupDatabase(context).groups.use { reader ->
|
DatabaseFactory.getGroupDatabase(context).groups.use { reader ->
|
||||||
while (true) {
|
while (true) {
|
||||||
val record = reader.next ?: return@use
|
val record = reader.next ?: return@use
|
||||||
|
@ -18,12 +18,11 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession
|
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
|
||||||
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
|
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
|
||||||
|
|
||||||
object MultiDeviceProtocol {
|
object MultiDeviceProtocol {
|
||||||
@ -31,46 +30,16 @@ object MultiDeviceProtocol {
|
|||||||
enum class MessageType { Text, Media }
|
enum class MessageType { Text, Media }
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun sendTextPush(context: Context, recipient: Recipient, messageID: Long, isEndSession: Boolean) {
|
fun sendTextPush(context: Context, recipient: Recipient, messageID: Long) {
|
||||||
sendMessagePush(context, recipient, messageID, MessageType.Text, isEndSession)
|
sendMessagePush(context, recipient, messageID, MessageType.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun sendMediaPush(context: Context, recipient: Recipient, messageID: Long) {
|
fun sendMediaPush(context: Context, recipient: Recipient, messageID: Long) {
|
||||||
sendMessagePush(context, recipient, messageID, MessageType.Media, false)
|
sendMessagePush(context, recipient, messageID, MessageType.Media)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendMessagePushToDevice(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType, isEndSession: Boolean): PushSendJob {
|
private fun sendMessagePush(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType) {
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
|
||||||
val threadFRStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
|
|
||||||
val isNoteToSelf = SessionMetaProtocol.shared.isNoteToSelf(recipient.address.serialize())
|
|
||||||
val isContactFriend = (threadFRStatus == LokiThreadFriendRequestStatus.FRIENDS || isNoteToSelf) // In the note to self case the device linking request was the FR
|
|
||||||
val isFRMessage = !isContactFriend
|
|
||||||
val hasVisibleContent = when (messageType) {
|
|
||||||
MessageType.Text -> DatabaseFactory.getSmsDatabase(context).getMessage(messageID).body.isNotBlank()
|
|
||||||
MessageType.Media -> {
|
|
||||||
val outgoingMediaMessage = DatabaseFactory.getMmsDatabase(context).getOutgoingMessage(messageID)
|
|
||||||
outgoingMediaMessage.body.isNotBlank() || outgoingMediaMessage.attachments.isNotEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val shouldSendAutoGeneratedFR = !isContactFriend && !isFRMessage
|
|
||||||
&& !isNoteToSelf && !recipient.address.isGroup // Group threads work through session requests
|
|
||||||
&& hasVisibleContent && !isEndSession
|
|
||||||
if (!shouldSendAutoGeneratedFR) {
|
|
||||||
when (messageType) {
|
|
||||||
MessageType.Text -> return PushTextSendJob(messageID, messageID, recipient.address, isFRMessage, null)
|
|
||||||
MessageType.Media -> return PushMediaSendJob(messageID, messageID, recipient.address, isFRMessage, null)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val autoGeneratedFRMessage = "Please accept to enable messages to be synced across devices"
|
|
||||||
when (messageType) {
|
|
||||||
MessageType.Text -> return PushTextSendJob(messageID, messageID, recipient.address, true, autoGeneratedFRMessage)
|
|
||||||
MessageType.Media -> return PushMediaSendJob(messageID, messageID, recipient.address, true, autoGeneratedFRMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendMessagePush(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType, isEndSession: Boolean) {
|
|
||||||
val jobManager = ApplicationContext.getInstance(context).jobManager
|
val jobManager = ApplicationContext.getInstance(context).jobManager
|
||||||
val isMultiDeviceRequired = !recipient.address.isOpenGroup
|
val isMultiDeviceRequired = !recipient.address.isOpenGroup
|
||||||
if (!isMultiDeviceRequired) {
|
if (!isMultiDeviceRequired) {
|
||||||
@ -80,9 +49,14 @@ object MultiDeviceProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val publicKey = recipient.address.serialize()
|
val publicKey = recipient.address.serialize()
|
||||||
LokiFileServerAPI.shared.getDeviceLinks(publicKey).success {
|
FileServerAPI.shared.getDeviceLinks(publicKey).success {
|
||||||
val devices = MultiDeviceProtocol.shared.getAllLinkedDevices(publicKey)
|
val devices = MultiDeviceProtocol.shared.getAllLinkedDevices(publicKey)
|
||||||
val jobs = devices.map { sendMessagePushToDevice(context, recipient(context, it), messageID, messageType, isEndSession) }
|
val jobs = devices.map {
|
||||||
|
when (messageType) {
|
||||||
|
MessageType.Text -> PushTextSendJob(messageID, messageID, recipient(context, it).address) as PushSendJob
|
||||||
|
MessageType.Media -> PushMediaSendJob(messageID, messageID, recipient(context, it).address) as PushSendJob
|
||||||
|
}
|
||||||
|
}
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
when (messageType) {
|
when (messageType) {
|
||||||
MessageType.Text -> jobManager.startChain(jobs).enqueue()
|
MessageType.Text -> jobManager.startChain(jobs).enqueue()
|
||||||
@ -105,7 +79,7 @@ object MultiDeviceProtocol {
|
|||||||
// A request should include a pre key bundle. An authorization should be a normal message.
|
// A request should include a pre key bundle. An authorization should be a normal message.
|
||||||
if (deviceLink.type == DeviceLink.Type.REQUEST) {
|
if (deviceLink.type == DeviceLink.Type.REQUEST) {
|
||||||
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
|
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
|
||||||
message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
|
message.withPreKeyBundle(preKeyBundle)
|
||||||
} else {
|
} else {
|
||||||
// Include the user's profile key so that the slave device can get the user's profile picture
|
// Include the user's profile key so that the slave device can get the user's profile picture
|
||||||
message.withProfileKey(ProfileKeyUtil.getProfileKey(context))
|
message.withProfileKey(ProfileKeyUtil.getProfileKey(context))
|
||||||
@ -135,7 +109,7 @@ object MultiDeviceProtocol {
|
|||||||
return Promise.ofFail(Exception("Failed to sign device link."))
|
return Promise.ofFail(Exception("Failed to sign device link."))
|
||||||
}
|
}
|
||||||
return retryIfNeeded(8) {
|
return retryIfNeeded(8) {
|
||||||
sendDeviceLinkMessage(context, deviceLink.slaveHexEncodedPublicKey, signedDeviceLink)
|
sendDeviceLinkMessage(context, deviceLink.slavePublicKey, signedDeviceLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +118,7 @@ object MultiDeviceProtocol {
|
|||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
if (deviceLink.type == DeviceLink.Type.REQUEST) {
|
if (deviceLink.type == DeviceLink.Type.REQUEST) {
|
||||||
handleDeviceLinkRequestMessage(context, deviceLink, content)
|
handleDeviceLinkRequestMessage(context, deviceLink, content)
|
||||||
} else if (deviceLink.slaveHexEncodedPublicKey == userPublicKey) {
|
} else if (deviceLink.slavePublicKey == userPublicKey) {
|
||||||
handleDeviceLinkAuthorizedMessage(context, deviceLink, content)
|
handleDeviceLinkAuthorizedMessage(context, deviceLink, content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,10 +132,10 @@ object MultiDeviceProtocol {
|
|||||||
} else if (isRequest && TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null) {
|
} else if (isRequest && TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null) {
|
||||||
Log.d("Loki", "Ignoring unexpected device link message (the device is a slave device).")
|
Log.d("Loki", "Ignoring unexpected device link message (the device is a slave device).")
|
||||||
return false
|
return false
|
||||||
} else if (isRequest && deviceLink.masterHexEncodedPublicKey != userPublicKey) {
|
} else if (isRequest && deviceLink.masterPublicKey != userPublicKey) {
|
||||||
Log.d("Loki", "Ignoring device linking message addressed to another user.")
|
Log.d("Loki", "Ignoring device linking message addressed to another user.")
|
||||||
return false
|
return false
|
||||||
} else if (isRequest && deviceLink.slaveHexEncodedPublicKey == userPublicKey) {
|
} else if (isRequest && deviceLink.slavePublicKey == userPublicKey) {
|
||||||
Log.d("Loki", "Ignoring device linking request message from self.")
|
Log.d("Loki", "Ignoring device linking request message from self.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -188,16 +162,14 @@ object MultiDeviceProtocol {
|
|||||||
}
|
}
|
||||||
val isValid = isValidDeviceLinkMessage(context, deviceLink)
|
val isValid = isValidDeviceLinkMessage(context, deviceLink)
|
||||||
if (!isValid) { return }
|
if (!isValid) { return }
|
||||||
SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(context, content)
|
|
||||||
linkingSession.processLinkingAuthorization(deviceLink)
|
linkingSession.processLinkingAuthorization(deviceLink)
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userPublicKey)
|
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userPublicKey)
|
||||||
DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink)
|
DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink)
|
||||||
TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.masterHexEncodedPublicKey)
|
TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.masterPublicKey)
|
||||||
TextSecurePreferences.setMultiDevice(context, true)
|
TextSecurePreferences.setMultiDevice(context, true)
|
||||||
LokiFileServerAPI.shared.addDeviceLink(deviceLink)
|
FileServerAPI.shared.addDeviceLink(deviceLink)
|
||||||
org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content)
|
org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.handleProfileKeyUpdate(context, content)
|
||||||
org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.duplicate_handleProfileKey(context, content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -210,19 +182,19 @@ object MultiDeviceProtocol {
|
|||||||
// Ignore the request if we don't know about the device link in question
|
// Ignore the request if we don't know about the device link in question
|
||||||
val masterDeviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getDeviceLinks(masterDevicePublicKey)
|
val masterDeviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getDeviceLinks(masterDevicePublicKey)
|
||||||
if (masterDeviceLinks.none {
|
if (masterDeviceLinks.none {
|
||||||
it.masterHexEncodedPublicKey == masterDevicePublicKey && it.slaveHexEncodedPublicKey == userPublicKey
|
it.masterPublicKey == masterDevicePublicKey && it.slavePublicKey == userPublicKey
|
||||||
}) {
|
}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
LokiFileServerAPI.shared.getDeviceLinks(userPublicKey, true).success { slaveDeviceLinks ->
|
FileServerAPI.shared.getDeviceLinks(userPublicKey, true).success { slaveDeviceLinks ->
|
||||||
// Check that the device link IS present on the file server.
|
// Check that the device link IS present on the file server.
|
||||||
// Note that the device link as seen from the master device's perspective has been deleted at this point, but the
|
// Note that the device link as seen from the master device's perspective has been deleted at this point, but the
|
||||||
// device link as seen from the slave perspective hasn't.
|
// device link as seen from the slave perspective hasn't.
|
||||||
if (slaveDeviceLinks.any {
|
if (slaveDeviceLinks.any {
|
||||||
it.masterHexEncodedPublicKey == masterDevicePublicKey && it.slaveHexEncodedPublicKey == userPublicKey
|
it.masterPublicKey == masterDevicePublicKey && it.slavePublicKey == userPublicKey
|
||||||
}) {
|
}) {
|
||||||
for (slaveDeviceLink in slaveDeviceLinks) { // In theory there should only be one
|
for (slaveDeviceLink in slaveDeviceLinks) { // In theory there should only be one
|
||||||
LokiFileServerAPI.shared.removeDeviceLink(slaveDeviceLink) // Attempt to clean up on the file server
|
FileServerAPI.shared.removeDeviceLink(slaveDeviceLink) // Attempt to clean up on the file server
|
||||||
}
|
}
|
||||||
TextSecurePreferences.setWasUnlinked(context, true)
|
TextSecurePreferences.setWasUnlinked(context, true)
|
||||||
ApplicationContext.getInstance(context).clearData()
|
ApplicationContext.getInstance(context).clearData()
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
|
||||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
|
||||||
import org.thoughtcrime.securesms.database.Address
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.Data
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job
|
|
||||||
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.signalservice.api.messages.SignalServiceDataMessage
|
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class PushEphemeralMessageSendJob private constructor(parameters: Parameters, private val message: EphemeralMessage) : BaseJob(parameters) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val KEY_MESSAGE = "message"
|
|
||||||
const val KEY = "PushBackgroundMessageSendJob"
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(message: EphemeralMessage) : this(Parameters.Builder()
|
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
|
||||||
.setQueue(KEY)
|
|
||||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
|
||||||
.setMaxAttempts(1)
|
|
||||||
.build(),
|
|
||||||
message)
|
|
||||||
|
|
||||||
override fun serialize(): Data {
|
|
||||||
return Data.Builder()
|
|
||||||
.putString(KEY_MESSAGE, message.serialize())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFactoryKey(): String { return KEY }
|
|
||||||
|
|
||||||
public override fun onRun() {
|
|
||||||
val recipient = message.get<String?>("recipient", null) ?: throw IllegalStateException()
|
|
||||||
val dataMessage = SignalServiceDataMessage.newBuilder().withTimestamp(System.currentTimeMillis())
|
|
||||||
// Attach a pre key bundle if needed
|
|
||||||
if (message.get("friendRequest", false)) {
|
|
||||||
val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(recipient)
|
|
||||||
dataMessage.withPreKeyBundle(bundle).asFriendRequest(true)
|
|
||||||
}
|
|
||||||
// Set flags if needed (these are mutually exclusive)
|
|
||||||
when {
|
|
||||||
message.get("unpairingRequest", false) -> dataMessage.asUnlinkingRequest(true)
|
|
||||||
message.get("sessionRestore", false) -> dataMessage.asSessionRestorationRequest(true)
|
|
||||||
message.get("sessionRequest", false) -> dataMessage.asSessionRequest(true)
|
|
||||||
}
|
|
||||||
// Send the message
|
|
||||||
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
|
||||||
val address = SignalServiceAddress(recipient)
|
|
||||||
try {
|
|
||||||
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(recipient), false))
|
|
||||||
messageSender.sendMessage(0, address, udAccess, dataMessage.build()) // The message ID doesn't matter
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d("Loki", "Failed to send background message to: $recipient due to error: $e.")
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun onShouldRetry(e: Exception): Boolean {
|
|
||||||
// Disable since we have our own retrying
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCanceled() { }
|
|
||||||
|
|
||||||
class Factory : Job.Factory<PushEphemeralMessageSendJob> {
|
|
||||||
|
|
||||||
override fun create(parameters: Parameters, data: Data): PushEphemeralMessageSendJob {
|
|
||||||
try {
|
|
||||||
val messageJSON = data.getString(KEY_MESSAGE)
|
|
||||||
return PushEphemeralMessageSendJob(parameters, EphemeralMessage.parse(messageJSON))
|
|
||||||
} catch (e: IOException) {
|
|
||||||
throw AssertionError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -56,7 +56,7 @@ class PushNullMessageSendJob private constructor(parameters: Parameters, private
|
|||||||
try {
|
try {
|
||||||
messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess,
|
messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess,
|
||||||
Date().time, serializedContentMessage, false, ttl, false,
|
Date().time, serializedContentMessage, false, ttl, false,
|
||||||
false, false, false, false)
|
false, false, false)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Failed to send null message to: $publicKey due to error: $e.")
|
Log.d("Loki", "Failed to send null message to: $publicKey due to error: $e.")
|
||||||
throw e
|
throw e
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||||
|
import org.thoughtcrime.securesms.database.Address
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job
|
||||||
|
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.signalservice.api.push.SignalServiceAddress
|
||||||
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||||
|
import org.whispersystems.signalservice.loki.protocol.meta.TTLUtilities
|
||||||
|
import java.io.IOException
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class PushSessionRequestMessageSendJob private constructor(parameters: Parameters, private val publicKey: String, private val timestamp: Long) : BaseJob(parameters) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY = "PushSessionRequestMessageSendJob"
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(publicKey: String, timestamp: Long) : this(Parameters.Builder()
|
||||||
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
|
.setQueue(KEY)
|
||||||
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
|
.setMaxAttempts(1)
|
||||||
|
.build(),
|
||||||
|
publicKey,
|
||||||
|
timestamp)
|
||||||
|
|
||||||
|
override fun serialize(): Data {
|
||||||
|
return Data.Builder().putString("publicKey", publicKey).putLong("timestamp", timestamp).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFactoryKey(): String { return KEY }
|
||||||
|
|
||||||
|
public override fun onRun() {
|
||||||
|
// Prepare
|
||||||
|
val contentMessage = SignalServiceProtos.Content.newBuilder()
|
||||||
|
// Attach the pre key bundle message
|
||||||
|
val preKeyBundleMessage = SignalServiceProtos.PreKeyBundleMessage.newBuilder()
|
||||||
|
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(publicKey) ?: return
|
||||||
|
preKeyBundleMessage.identityKey = ByteString.copyFrom(preKeyBundle.identityKey.serialize())
|
||||||
|
preKeyBundleMessage.deviceId = preKeyBundle.deviceId
|
||||||
|
preKeyBundleMessage.preKeyId = preKeyBundle.preKeyId
|
||||||
|
preKeyBundleMessage.signedKeyId = preKeyBundle.signedPreKeyId
|
||||||
|
preKeyBundleMessage.preKey = ByteString.copyFrom(preKeyBundle.preKey.serialize())
|
||||||
|
preKeyBundleMessage.signedKey = ByteString.copyFrom(preKeyBundle.signedPreKey.serialize())
|
||||||
|
preKeyBundleMessage.signature = ByteString.copyFrom(preKeyBundle.signedPreKeySignature)
|
||||||
|
contentMessage.preKeyBundleMessage = preKeyBundleMessage.build()
|
||||||
|
// Attach the null message
|
||||||
|
val nullMessage = SignalServiceProtos.NullMessage.newBuilder()
|
||||||
|
val sr = SecureRandom()
|
||||||
|
val paddingSize = sr.nextInt(512)
|
||||||
|
val padding = ByteArray(paddingSize)
|
||||||
|
sr.nextBytes(padding)
|
||||||
|
nullMessage.padding = ByteString.copyFrom(padding)
|
||||||
|
contentMessage.nullMessage = nullMessage.build()
|
||||||
|
// Send the result
|
||||||
|
val serializedContentMessage = contentMessage.build().toByteArray()
|
||||||
|
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
||||||
|
val address = SignalServiceAddress(publicKey)
|
||||||
|
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||||
|
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
|
||||||
|
val ttl = TTLUtilities.getTTL(TTLUtilities.MessageType.SessionRequest)
|
||||||
|
try {
|
||||||
|
messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess,
|
||||||
|
Date().time, serializedContentMessage, false, ttl, false,
|
||||||
|
true, false, false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d("Loki", "Failed to send session request to: $publicKey due to error: $e.")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun onShouldRetry(e: Exception): Boolean {
|
||||||
|
// Disable since we have our own retrying
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCanceled() {
|
||||||
|
// Update the DB on fail if this is still the most recently sent session request (should always be true)
|
||||||
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
|
if (apiDB.getSessionRequestSentTimestamp(publicKey) == timestamp) {
|
||||||
|
apiDB.setSessionRequestSentTimestamp(publicKey, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Factory : Job.Factory<PushSessionRequestMessageSendJob> {
|
||||||
|
|
||||||
|
override fun create(parameters: Parameters, data: Data): PushSessionRequestMessageSendJob {
|
||||||
|
try {
|
||||||
|
val publicKey = data.getString("publicKey")
|
||||||
|
val timestamp = data.getLong("timestamp")
|
||||||
|
return PushSessionRequestMessageSendJob(parameters, publicKey, timestamp)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw AssertionError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,11 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
|
|||||||
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob
|
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob
|
||||||
import org.thoughtcrime.securesms.loki.utilities.recipient
|
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.sms.MessageSender
|
||||||
|
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
import org.whispersystems.libsignal.loki.SessionResetStatus
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -27,8 +29,8 @@ object SessionManagementProtocol {
|
|||||||
val smsDB = DatabaseFactory.getSmsDatabase(context)
|
val smsDB = DatabaseFactory.getSmsDatabase(context)
|
||||||
val devices = lokiThreadDB.getSessionRestoreDevices(threadID)
|
val devices = lokiThreadDB.getSessionRestoreDevices(threadID)
|
||||||
for (device in devices) {
|
for (device in devices) {
|
||||||
val sessionRestorationRequest = EphemeralMessage.createSessionRestorationRequest(recipient.address.serialize())
|
val endSessionMessage = OutgoingEndSessionMessage(OutgoingTextMessage(recipient, "TERMINATE", 0, -1))
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRestorationRequest))
|
MessageSender.send(context, endSessionMessage, threadID, false, null)
|
||||||
}
|
}
|
||||||
val infoMessage = OutgoingTextMessage(recipient, "", 0, 0)
|
val infoMessage = OutgoingTextMessage(recipient, "", 0, 0)
|
||||||
val infoMessageID = smsDB.insertMessageOutbox(threadID, infoMessage, false, System.currentTimeMillis(), null)
|
val infoMessageID = smsDB.insertMessageOutbox(threadID, infoMessage, false, System.currentTimeMillis(), null)
|
||||||
@ -52,38 +54,30 @@ object SessionManagementProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun handlePreKeyBundleMessageIfNeeded(context: Context, content: SignalServiceContent) {
|
fun shouldProcessSessionRequest(context: Context, publicKey: String, timestamp: Long): Boolean {
|
||||||
val recipient = recipient(context, content.sender)
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
if (recipient.isGroupRecipient) { return }
|
val sentTimestamp = apiDB.getSessionRequestSentTimestamp(publicKey) ?: 0
|
||||||
val preKeyBundleMessage = content.lokiServiceMessage.orNull()?.preKeyBundleMessage ?: return
|
val processedTimestamp = apiDB.getSessionRequestProcessedTimestamp(publicKey) ?: 0
|
||||||
val registrationID = TextSecurePreferences.getLocalRegistrationId(context) // TODO: It seems wrong to use the local registration ID for this?
|
return timestamp > sentTimestamp && timestamp > processedTimestamp
|
||||||
val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context)
|
|
||||||
Log.d("Loki", "Received a pre key bundle from: " + content.sender.toString() + ".")
|
|
||||||
if (content.dataMessage.isPresent && content.dataMessage.get().isSessionRequest) {
|
|
||||||
val sessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
|
|
||||||
if (sessionRequestTimestamp != null && content.timestamp < sessionRequestTimestamp) {
|
|
||||||
// We sent or processed a session request after this one was sent
|
|
||||||
Log.d("Loki", "Ignoring session request from: ${content.sender}.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
|
|
||||||
lokiPreKeyBundleDatabase.setPreKeyBundle(content.sender, preKeyBundle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun handleSessionRequestIfNeeded(context: Context, content: SignalServiceContent): Boolean {
|
fun handlePreKeyBundleMessageIfNeeded(context: Context, content: SignalServiceContent) {
|
||||||
if (!content.dataMessage.isPresent || !content.dataMessage.get().isSessionRequest) { return false }
|
val preKeyBundleMessage = content.preKeyBundleMessage.orNull() ?: return
|
||||||
val sessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
|
val publicKey = content.sender
|
||||||
if (sessionRequestTimestamp != null && content.timestamp < sessionRequestTimestamp) {
|
if (recipient(context, publicKey).isGroupRecipient) { return } // Should never occur
|
||||||
// We sent or processed a session request after this one was sent
|
Log.d("Loki", "Received a pre key bundle from: $publicKey.")
|
||||||
Log.d("Loki", "Ignoring session request from: ${content.sender}.")
|
if (!shouldProcessSessionRequest(context, publicKey, content.timestamp)) {
|
||||||
return false
|
Log.d("Loki", "Ignoring session request from: $publicKey.")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestTimestamp(content.sender, Date().time)
|
val registrationID = TextSecurePreferences.getLocalRegistrationId(context)
|
||||||
val ephemeralMessage = EphemeralMessage.create(content.sender)
|
val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context)
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
|
||||||
return true
|
lokiPreKeyBundleDatabase.setPreKeyBundle(publicKey, preKeyBundle)
|
||||||
|
DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestProcessedTimestamp(publicKey, Date().time)
|
||||||
|
val job = PushNullMessageSendJob(publicKey)
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -93,10 +87,10 @@ object SessionManagementProtocol {
|
|||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||||
Log.d("Loki", "Received a session reset request from: ${content.sender}; archiving the session.")
|
Log.d("Loki", "Received a session reset request from: ${content.sender}; archiving the session.")
|
||||||
sessionStore.archiveAllSessions(content.sender)
|
sessionStore.archiveAllSessions(content.sender)
|
||||||
lokiThreadDB.setSessionResetStatus(content.sender, LokiSessionResetStatus.REQUEST_RECEIVED)
|
lokiThreadDB.setSessionResetStatus(content.sender, SessionResetStatus.REQUEST_RECEIVED)
|
||||||
Log.d("Loki", "Sending an ephemeral message back to: ${content.sender}.")
|
Log.d("Loki", "Sending an ephemeral message back to: ${content.sender}.")
|
||||||
val ephemeralMessage = EphemeralMessage.create(content.sender)
|
val job = PushNullMessageSendJob(content.sender)
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
ApplicationContext.getInstance(context).jobManager.add(job)
|
||||||
SecurityEvent.broadcastSecurityUpdateEvent(context)
|
SecurityEvent.broadcastSecurityUpdateEvent(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
object SessionMetaProtocol {
|
object SessionMetaProtocol {
|
||||||
@ -48,9 +47,8 @@ object SessionMetaProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Basically a duplicate of PushDecryptJob's handleProfileKey
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun duplicate_handleProfileKey(context: Context, content: SignalServiceContent) {
|
fun handleProfileKeyUpdate(context: Context, content: SignalServiceContent) {
|
||||||
val message = content.dataMessage.get()
|
val message = content.dataMessage.get()
|
||||||
if (!message.profileKey.isPresent) { return }
|
if (!message.profileKey.isPresent) { return }
|
||||||
val database = DatabaseFactory.getRecipientDatabase(context)
|
val database = DatabaseFactory.getRecipientDatabase(context)
|
||||||
@ -60,46 +58,34 @@ object SessionMetaProtocol {
|
|||||||
database.setUnidentifiedAccessMode(recipient, RecipientDatabase.UnidentifiedAccessMode.UNKNOWN)
|
database.setUnidentifiedAccessMode(recipient, RecipientDatabase.UnidentifiedAccessMode.UNKNOWN)
|
||||||
val url = content.senderProfilePictureURL.or("")
|
val url = content.senderProfilePictureURL.or("")
|
||||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(recipient, url))
|
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(recipient, url))
|
||||||
handleProfileKeyUpdateIfNeeded(context, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun handleProfileKeyUpdateIfNeeded(context: Context, content: SignalServiceContent) {
|
|
||||||
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||||
if (userMasterPublicKey != content.sender) { return }
|
if (userMasterPublicKey == content.sender) {
|
||||||
ApplicationContext.getInstance(context).updateOpenGroupProfilePicturesIfNeeded()
|
ApplicationContext.getInstance(context).updateOpenGroupProfilePicturesIfNeeded()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Should be invoked for the recipient's master device.
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
fun canUserReplyToNotification(recipient: Recipient, context: Context): Boolean {
|
|
||||||
val isGroup = recipient.isGroupRecipient
|
|
||||||
if (isGroup) { return !recipient.address.isRSSFeed }
|
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
|
||||||
return DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be invoked for the recipient's master device.
|
* Should be invoked for the recipient's master device.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun shouldSendReadReceipt(address: Address, context: Context): Boolean {
|
fun canUserReplyToNotification(recipient: Recipient): Boolean {
|
||||||
if (address.isGroup) { return false }
|
return !recipient.address.isRSSFeed
|
||||||
val recipient = Recipient.from(context, address,false)
|
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
|
||||||
return DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be invoked for the recipient's master device.
|
* Should be invoked for the recipient's master device.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun shouldSendTypingIndicator(recipient: Recipient?, context: Context): Boolean {
|
fun shouldSendReadReceipt(address: Address): Boolean {
|
||||||
if (recipient == null || recipient.isGroupRecipient) { return false }
|
return !address.isGroup
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
}
|
||||||
return DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
|
||||||
|
/**
|
||||||
|
* Should be invoked for the recipient's master device.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun shouldSendTypingIndicator(address: Address): Boolean {
|
||||||
|
return !address.isGroup
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.whispersystems.libsignal.loki.SessionResetProtocol
|
||||||
|
import org.whispersystems.libsignal.loki.SessionResetStatus
|
||||||
|
import org.whispersystems.libsignal.protocol.PreKeySignalMessage
|
||||||
|
|
||||||
|
class SessionResetImplementation(private val context: Context) : SessionResetProtocol {
|
||||||
|
|
||||||
|
override fun getSessionResetStatus(publicKey: String): SessionResetStatus {
|
||||||
|
return DatabaseFactory.getLokiThreadDatabase(context).getSessionResetStatus(publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setSessionResetStatus(publicKey: String, sessionResetStatus: SessionResetStatus) {
|
||||||
|
return DatabaseFactory.getLokiThreadDatabase(context).setSessionResetStatus(publicKey, sessionResetStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewSessionAdopted(publicKey: String, oldSessionResetStatus: SessionResetStatus) {
|
||||||
|
if (oldSessionResetStatus == SessionResetStatus.IN_PROGRESS) {
|
||||||
|
val job = PushNullMessageSendJob(publicKey)
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(job)
|
||||||
|
}
|
||||||
|
// TODO: Show session reset succeed message
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun validatePreKeySignalMessage(publicKey: String, message: PreKeySignalMessage) {
|
||||||
|
val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getPreKeyRecord(publicKey) ?: return
|
||||||
|
// TODO: Checking that the pre key record isn't null is causing issues when it shouldn't
|
||||||
|
check(preKeyRecord.id == (message.preKeyId ?: -1)) { "Received a background message from an unknown source." }
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData
|
|||||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData
|
import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||||
import org.thoughtcrime.securesms.groups.GroupManager
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
import org.thoughtcrime.securesms.groups.GroupMessageProcessor
|
import org.thoughtcrime.securesms.groups.GroupMessageProcessor
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob
|
||||||
@ -19,13 +20,12 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||||
|
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage
|
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream
|
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream
|
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
|
||||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -52,8 +52,9 @@ object SyncMessagesProtocol {
|
|||||||
val allAddresses = ArrayList(DatabaseFactory.getRecipientDatabase(context).allAddresses)
|
val allAddresses = ArrayList(DatabaseFactory.getRecipientDatabase(context).allAddresses)
|
||||||
val result = mutableSetOf<ContactData>()
|
val result = mutableSetOf<ContactData>()
|
||||||
for (address in allAddresses) {
|
for (address in allAddresses) {
|
||||||
if (!shouldSyncContact(context, address)) { continue }
|
if (!shouldSyncContact(context, address.serialize())) { continue }
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, address, false))
|
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(Recipient.from(context, address, false))
|
||||||
|
if (threadID < 0) { continue }
|
||||||
val displayName = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(address.serialize())
|
val displayName = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(address.serialize())
|
||||||
val contactData = ContactData(threadID, displayName)
|
val contactData = ContactData(threadID, displayName)
|
||||||
contactData.numbers.add(NumberData("TextSecure", address.serialize()))
|
contactData.numbers.add(NumberData("TextSecure", address.serialize()))
|
||||||
@ -63,13 +64,12 @@ object SyncMessagesProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun shouldSyncContact(context: Context, address: Address): Boolean {
|
fun shouldSyncContact(context: Context, publicKey: String): Boolean {
|
||||||
if (!PublicKeyValidation.isValid(address.serialize())) { return false }
|
if (!PublicKeyValidation.isValid(publicKey)) { return false }
|
||||||
if (address.serialize() == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { return false }
|
if (publicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { return false }
|
||||||
if (address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
|
if (publicKey == TextSecurePreferences.getLocalNumber(context)) { return false }
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, address, false))
|
if (MultiDeviceProtocol.shared.getSlaveDevices(publicKey).contains(publicKey)) { return false }
|
||||||
val isFriend = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
|
return true
|
||||||
return isFriend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -95,33 +95,13 @@ object SyncMessagesProtocol {
|
|||||||
if (!allUserDevices.contains(content.sender)) { return }
|
if (!allUserDevices.contains(content.sender)) { return }
|
||||||
Log.d("Loki", "Received a contact sync message.")
|
Log.d("Loki", "Received a contact sync message.")
|
||||||
val contactsInputStream = DeviceContactsInputStream(message.contactsStream.asStream().inputStream)
|
val contactsInputStream = DeviceContactsInputStream(message.contactsStream.asStream().inputStream)
|
||||||
val contactPublicKeys = contactsInputStream.readAll().map { it.number }
|
val contacts = contactsInputStream.readAll()
|
||||||
for (contactPublicKey in contactPublicKeys) {
|
for (contact in contacts) {
|
||||||
|
val contactPublicKey = contact.number
|
||||||
if (contactPublicKey == userPublicKey || !PublicKeyValidation.isValid(contactPublicKey)) { return }
|
if (contactPublicKey == userPublicKey || !PublicKeyValidation.isValid(contactPublicKey)) { return }
|
||||||
val recipient = recipient(context, contactPublicKey)
|
val applicationContext = context.applicationContext as ApplicationContext
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
applicationContext.sendSessionRequestIfNeeded(contactPublicKey)
|
||||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
DatabaseFactory.getRecipientDatabase(context).setBlocked(recipient(context, contactPublicKey), contact.isBlocked)
|
||||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
|
||||||
when (threadFRStatus) {
|
|
||||||
LokiThreadFriendRequestStatus.NONE, LokiThreadFriendRequestStatus.REQUEST_EXPIRED -> {
|
|
||||||
val contactLinkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(contactPublicKey)
|
|
||||||
for (device in contactLinkedDevices) {
|
|
||||||
FriendRequestProtocol.sendAutoGeneratedFriendRequest(context, device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LokiThreadFriendRequestStatus.REQUEST_RECEIVED -> {
|
|
||||||
FriendRequestProtocol.acceptFriendRequest(context, recipient(context, contactPublicKey)) // Takes into account multi device internally
|
|
||||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS)
|
|
||||||
val lastMessageID = FriendRequestProtocol.getLastMessageID(context, threadID)
|
|
||||||
if (lastMessageID != null) {
|
|
||||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
|
||||||
}
|
|
||||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient(context, contactPublicKey), true)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,14 +124,14 @@ object SyncMessagesProtocol {
|
|||||||
closedGroup.avatar.orNull(),
|
closedGroup.avatar.orNull(),
|
||||||
closedGroup.admins
|
closedGroup.admins
|
||||||
)
|
)
|
||||||
val signalServiceDataMessage = SignalServiceDataMessage(content.timestamp, signalServiceGroup, null, null)
|
val dataMessage = SignalServiceDataMessage(content.timestamp, signalServiceGroup, null, null)
|
||||||
// This establishes sessions internally
|
// This establishes sessions internally
|
||||||
GroupMessageProcessor.process(context, content, signalServiceDataMessage, false)
|
GroupMessageProcessor.process(context, content, dataMessage, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun handleOpenGroupSyncMessage(context: Context, content: SignalServiceContent, openGroups: List<LokiPublicChat>) {
|
fun handleOpenGroupSyncMessage(context: Context, content: SignalServiceContent, openGroups: List<PublicChat>) {
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||||
if (!allUserDevices.contains(content.sender)) { return }
|
if (!allUserDevices.contains(content.sender)) { return }
|
||||||
@ -164,4 +144,27 @@ object SyncMessagesProtocol {
|
|||||||
OpenGroupUtilities.addGroup(context, url, channel)
|
OpenGroupUtilities.addGroup(context, url, channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun handleBlockedContactsSyncMessage(context: Context, content: SignalServiceContent, blockedContacts: BlockedListMessage) {
|
||||||
|
val recipientDB = DatabaseFactory.getRecipientDatabase(context)
|
||||||
|
val cursor = recipientDB.blocked
|
||||||
|
val blockedPublicKeys = blockedContacts.numbers.toSet()
|
||||||
|
val publicKeysToUnblock = mutableSetOf<String>()
|
||||||
|
fun addToUnblockListIfNeeded() {
|
||||||
|
val publicKey = cursor.getString(cursor.getColumnIndex(RecipientDatabase.ADDRESS)) ?: return
|
||||||
|
if (blockedPublicKeys.contains(publicKey)) { return }
|
||||||
|
publicKeysToUnblock.add(publicKey)
|
||||||
|
}
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
addToUnblockListIfNeeded()
|
||||||
|
}
|
||||||
|
publicKeysToUnblock.forEach {
|
||||||
|
recipientDB.setBlocked(recipient(context, it), false)
|
||||||
|
}
|
||||||
|
blockedPublicKeys.forEach {
|
||||||
|
recipientDB.setBlocked(recipient(context, it), true)
|
||||||
|
}
|
||||||
|
ApplicationContext.getInstance(context).broadcaster.broadcast("blockedContactsChanged")
|
||||||
|
}
|
||||||
}
|
}
|
@ -66,7 +66,7 @@ class LokiRSSFeedPoller(private val context: Context, private val feed: LokiRSSF
|
|||||||
val id = feed.id.toByteArray()
|
val id = feed.id.toByteArray()
|
||||||
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.RSS_FEED, null, null, null, null)
|
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.RSS_FEED, null, null, null, null)
|
||||||
val x2 = SignalServiceDataMessage(timestamp, x1, null, body)
|
val x2 = SignalServiceDataMessage(timestamp, x1, null, body)
|
||||||
val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false, false, false, false)
|
val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false, false)
|
||||||
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.absent())
|
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.absent())
|
||||||
}
|
}
|
||||||
}.fail { exception ->
|
}.fail { exception ->
|
||||||
|
@ -5,11 +5,9 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
|
||||||
|
|
||||||
data class Contact(
|
data class Contact(
|
||||||
val recipient: Recipient,
|
val recipient: Recipient,
|
||||||
val isFriend: Boolean,
|
|
||||||
val isSlave: Boolean,
|
val isSlave: Boolean,
|
||||||
val isOurDevice: Boolean
|
val isOurDevice: Boolean
|
||||||
) {
|
) {
|
||||||
@ -31,10 +29,9 @@ object ContactUtilities {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getAllContacts(context: Context): Set<Contact> {
|
fun getAllContacts(context: Context): Set<Contact> {
|
||||||
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
|
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
|
||||||
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
||||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userHexEncodedPublicKey)
|
val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||||
val cursor = threadDatabase.conversationList
|
val cursor = threadDatabase.conversationList
|
||||||
val result = mutableSetOf<Contact>()
|
val result = mutableSetOf<Contact>()
|
||||||
threadDatabase.readerFor(cursor).use { reader ->
|
threadDatabase.readerFor(cursor).use { reader ->
|
||||||
@ -43,13 +40,12 @@ object ContactUtilities {
|
|||||||
val recipient = thread.recipient
|
val recipient = thread.recipient
|
||||||
val publicKey = recipient.address.serialize()
|
val publicKey = recipient.address.serialize()
|
||||||
val isUserDevice = userDevices.contains(publicKey)
|
val isUserDevice = userDevices.contains(publicKey)
|
||||||
val isFriend = lokiThreadDatabase.getFriendRequestStatus(thread.threadId) == LokiThreadFriendRequestStatus.FRIENDS
|
|
||||||
var isSlave = false
|
var isSlave = false
|
||||||
if (!recipient.isGroupRecipient) {
|
if (!recipient.isGroupRecipient) {
|
||||||
val deviceLinks = lokiAPIDatabase.getDeviceLinks(publicKey)
|
val deviceLinks = lokiAPIDatabase.getDeviceLinks(publicKey)
|
||||||
isSlave = deviceLinks.find { it.slaveHexEncodedPublicKey == publicKey } != null
|
isSlave = deviceLinks.find { it.slavePublicKey == publicKey } != null
|
||||||
}
|
}
|
||||||
result.add(Contact(recipient, isFriend, isSlave, isUserDevice))
|
result.add(Contact(recipient, isSlave, isUserDevice))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
@ -28,22 +28,22 @@ object MentionUtilities {
|
|||||||
val mentions = mutableListOf<Tuple2<Range<Int>, String>>()
|
val mentions = mutableListOf<Tuple2<Range<Int>, String>>()
|
||||||
var startIndex = 0
|
var startIndex = 0
|
||||||
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
if (matcher.find(startIndex)) {
|
if (matcher.find(startIndex)) {
|
||||||
while (true) {
|
while (true) {
|
||||||
val hexEncodedPublicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
||||||
val userDisplayName: String? = if (hexEncodedPublicKey.toLowerCase() == userHexEncodedPublicKey.toLowerCase()) {
|
val userDisplayName: String? = if (publicKey.toLowerCase() == userPublicKey.toLowerCase()) {
|
||||||
TextSecurePreferences.getProfileName(context)
|
TextSecurePreferences.getProfileName(context)
|
||||||
} else if (publicChat != null) {
|
} else if (publicChat != null) {
|
||||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
|
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey)
|
||||||
} else {
|
} else {
|
||||||
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
|
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey)
|
||||||
}
|
}
|
||||||
if (userDisplayName != null) {
|
if (userDisplayName != null) {
|
||||||
text = text.subSequence(0, matcher.start()).toString() + "@" + userDisplayName + text.subSequence(matcher.end(), text.length)
|
text = text.subSequence(0, matcher.start()).toString() + "@" + userDisplayName + text.subSequence(matcher.end(), text.length)
|
||||||
val endIndex = matcher.start() + 1 + userDisplayName.length
|
val endIndex = matcher.start() + 1 + userDisplayName.length
|
||||||
startIndex = endIndex
|
startIndex = endIndex
|
||||||
mentions.add(Tuple2(Range.create(matcher.start(), endIndex), hexEncodedPublicKey))
|
mentions.add(Tuple2(Range.create(matcher.start(), endIndex), publicKey))
|
||||||
} else {
|
} else {
|
||||||
startIndex = matcher.end()
|
startIndex = matcher.end()
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,11 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||||||
fun getOpenGroupDisplayName(recipient: Recipient, threadRecipient: Recipient, context: Context): String {
|
fun getOpenGroupDisplayName(recipient: Recipient, threadRecipient: Recipient, context: Context): String {
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient)
|
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient)
|
||||||
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||||
val hexEncodedPublicKey = recipient.address.toString()
|
val publicKey = recipient.address.toString()
|
||||||
val displayName = if (publicChat != null) {
|
val displayName = if (publicChat != null) {
|
||||||
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
|
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey)
|
||||||
} else {
|
} else {
|
||||||
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
|
DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey)
|
||||||
}
|
}
|
||||||
return displayName ?: hexEncodedPublicKey
|
return displayName ?: publicKey
|
||||||
}
|
}
|
@ -8,21 +8,21 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
|||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.groups.GroupManager
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
|
||||||
|
|
||||||
object OpenGroupUtilities {
|
object OpenGroupUtilities {
|
||||||
|
|
||||||
@JvmStatic fun addGroup(context: Context, url: String, channel: Long): Promise<LokiPublicChat, Exception> {
|
@JvmStatic fun addGroup(context: Context, url: String, channel: Long): Promise<PublicChat, Exception> {
|
||||||
// Check for an existing group
|
// Check for an existing group
|
||||||
val groupID = LokiPublicChat.getId(channel, url)
|
val groupID = PublicChat.getId(channel, url)
|
||||||
val threadID = GroupManager.getOpenGroupThreadID(groupID, context)
|
val threadID = GroupManager.getOpenGroupThreadID(groupID, context)
|
||||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||||
if (openGroup != null) { return Promise.of(openGroup) }
|
if (openGroup != null) { return Promise.of(openGroup) }
|
||||||
// Add the new group
|
// Add the new group
|
||||||
val application = ApplicationContext.getInstance(context)
|
val application = ApplicationContext.getInstance(context)
|
||||||
val displayName = TextSecurePreferences.getProfileName(context)
|
val displayName = TextSecurePreferences.getProfileName(context)
|
||||||
val lokiPublicChatAPI = application.lokiPublicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.")
|
val lokiPublicChatAPI = application.publicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.")
|
||||||
return application.lokiPublicChatManager.addChat(url, channel).then { group ->
|
return application.publicChatManager.addChat(url, channel).then { group ->
|
||||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url)
|
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url)
|
||||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url)
|
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url)
|
||||||
lokiPublicChatAPI.getMessages(channel, url)
|
lokiPublicChatAPI.getMessages(channel, url)
|
||||||
|
@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities.populat
|
|||||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities.highlightMentions
|
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities.highlightMentions
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
|
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -46,22 +47,34 @@ class ConversationView : LinearLayout {
|
|||||||
// region Updating
|
// region Updating
|
||||||
fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) {
|
fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) {
|
||||||
this.thread = thread
|
this.thread = thread
|
||||||
populateUserPublicKeyCacheIfNeeded(thread.threadId, context) // FIXME: This is a terrible place to do this
|
populateUserPublicKeyCacheIfNeeded(thread.threadId, context) // FIXME: This is a bad place to do this
|
||||||
unreadMessagesIndicatorView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE
|
if (thread.recipient.isBlocked) {
|
||||||
|
accentView.setBackgroundResource(R.color.destructive)
|
||||||
|
accentView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
accentView.setBackgroundResource(R.color.accent)
|
||||||
|
accentView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE
|
||||||
|
}
|
||||||
if (thread.recipient.isGroupRecipient) {
|
if (thread.recipient.isGroupRecipient) {
|
||||||
if ("Session Public Chat" == thread.recipient.name) {
|
if ("Session Public Chat" == thread.recipient.name) {
|
||||||
profilePictureView.hexEncodedPublicKey = ""
|
profilePictureView.publicKey = ""
|
||||||
|
profilePictureView.additionalPublicKey = null
|
||||||
profilePictureView.isRSSFeed = true
|
profilePictureView.isRSSFeed = true
|
||||||
} else {
|
} else {
|
||||||
val users = MentionsManager.shared.userPublicKeyCache[thread.threadId]?.toList() ?: listOf()
|
val users = MentionsManager.shared.userPublicKeyCache[thread.threadId]?.toMutableList() ?: mutableListOf()
|
||||||
|
users.remove(TextSecurePreferences.getLocalNumber(context))
|
||||||
|
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||||
|
if (masterPublicKey != null) {
|
||||||
|
users.remove(masterPublicKey)
|
||||||
|
}
|
||||||
val randomUsers = users.sorted() // Sort to provide a level of stability
|
val randomUsers = users.sorted() // Sort to provide a level of stability
|
||||||
profilePictureView.hexEncodedPublicKey = randomUsers.getOrNull(0) ?: ""
|
profilePictureView.publicKey = randomUsers.getOrNull(0) ?: ""
|
||||||
profilePictureView.additionalHexEncodedPublicKey = randomUsers.getOrNull(1) ?: ""
|
profilePictureView.additionalPublicKey = randomUsers.getOrNull(1) ?: ""
|
||||||
profilePictureView.isRSSFeed = thread.recipient.name == "Loki News" || thread.recipient.name == "Session Updates"
|
profilePictureView.isRSSFeed = thread.recipient.name == "Loki News" || thread.recipient.name == "Session Updates"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
profilePictureView.hexEncodedPublicKey = thread.recipient.address.toString()
|
profilePictureView.publicKey = thread.recipient.address.toString()
|
||||||
profilePictureView.additionalHexEncodedPublicKey = null
|
profilePictureView.additionalPublicKey = null
|
||||||
profilePictureView.isRSSFeed = false
|
profilePictureView.isRSSFeed = false
|
||||||
}
|
}
|
||||||
profilePictureView.glide = glide
|
profilePictureView.glide = glide
|
||||||
|
@ -1,190 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki.views
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.TextView
|
|
||||||
import com.github.ybq.android.spinkit.style.DoubleBounce
|
|
||||||
import network.loki.messenger.R
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
|
||||||
|
|
||||||
class FriendRequestView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
|
||||||
private var isUISetUp = false
|
|
||||||
private var message: MessageRecord? = null
|
|
||||||
var delegate: FriendRequestViewDelegate? = null
|
|
||||||
|
|
||||||
// region Components
|
|
||||||
private val topSpacer by lazy {
|
|
||||||
val result = View(context)
|
|
||||||
result.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, toPx(12, resources))
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
private val label by lazy {
|
|
||||||
val result = TextView(context)
|
|
||||||
result.setTextColor(resources.getColorWithID(R.color.text, context.theme))
|
|
||||||
result.textAlignment = TextView.TEXT_ALIGNMENT_CENTER
|
|
||||||
result.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
private val buttonLinearLayout by lazy {
|
|
||||||
val result = LinearLayout(context)
|
|
||||||
result.orientation = HORIZONTAL
|
|
||||||
result.setPadding(0, resources.getDimension(R.dimen.medium_spacing).toInt(), 0, 0)
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
private val loaderContainer by lazy {
|
|
||||||
val result = LinearLayout(context)
|
|
||||||
val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, toPx(50, resources))
|
|
||||||
result.layoutParams = layoutParams
|
|
||||||
result.gravity = Gravity.CENTER
|
|
||||||
result
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Initialization
|
|
||||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
|
||||||
constructor(context: Context) : this(context, null)
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Updating
|
|
||||||
fun update(message: MessageRecord) {
|
|
||||||
this.message = message
|
|
||||||
setUpUIIfNeeded()
|
|
||||||
updateUI()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setUpUIIfNeeded() {
|
|
||||||
if (isUISetUp) { return }
|
|
||||||
isUISetUp = true
|
|
||||||
orientation = VERTICAL
|
|
||||||
setPadding(toPx(48, resources), 0, toPx(48, resources), 0)
|
|
||||||
addView(topSpacer)
|
|
||||||
addView(label)
|
|
||||||
if (!message!!.isOutgoing) {
|
|
||||||
val loader = ProgressBar(context)
|
|
||||||
loader.isIndeterminate = true
|
|
||||||
loader.indeterminateDrawable = DoubleBounce()
|
|
||||||
val loaderLayoutParams = LayoutParams(LayoutParams.MATCH_PARENT, toPx(24, resources))
|
|
||||||
loader.layoutParams = loaderLayoutParams
|
|
||||||
loaderContainer.addView(loader)
|
|
||||||
addView(loaderContainer)
|
|
||||||
fun button(): Button {
|
|
||||||
val result = Button(context)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
result.elevation = 0f
|
|
||||||
result.stateListAnimator = null
|
|
||||||
}
|
|
||||||
result.setTextColor(resources.getColorWithID(R.color.text, context.theme))
|
|
||||||
result.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
|
|
||||||
result.isAllCaps = false
|
|
||||||
result.setPadding(0, 0, 0, 0)
|
|
||||||
val buttonLayoutParams = LayoutParams(0, resources.getDimension(R.dimen.small_button_height).toInt())
|
|
||||||
buttonLayoutParams.weight = 1f
|
|
||||||
result.layoutParams = buttonLayoutParams
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
val rejectButton = button()
|
|
||||||
rejectButton.text = resources.getString(R.string.view_friend_request_reject_button_title)
|
|
||||||
rejectButton.setBackgroundResource(R.drawable.unimportant_dialog_button_background)
|
|
||||||
rejectButton.setOnClickListener { reject() }
|
|
||||||
buttonLinearLayout.addView(rejectButton)
|
|
||||||
val acceptButton = button()
|
|
||||||
acceptButton.text = resources.getString(R.string.view_friend_request_accept_button_title)
|
|
||||||
acceptButton.setBackgroundResource(R.drawable.prominent_dialog_button_background)
|
|
||||||
val acceptButtonLayoutParams = acceptButton.layoutParams as LayoutParams
|
|
||||||
acceptButtonLayoutParams.setMargins(resources.getDimension(R.dimen.medium_spacing).toInt(), 0, 0, 0)
|
|
||||||
acceptButton.layoutParams = acceptButtonLayoutParams
|
|
||||||
acceptButton.setOnClickListener { accept() }
|
|
||||||
buttonLinearLayout.addView(acceptButton)
|
|
||||||
buttonLinearLayout.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, toPx(50, resources))
|
|
||||||
addView(buttonLinearLayout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateUI() {
|
|
||||||
val message = message
|
|
||||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
|
||||||
val contactID = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(message!!.threadId)!!.address.toString()
|
|
||||||
val contactDisplayName = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(contactID) ?: contactID
|
|
||||||
val friendRequestStatus = lokiMessageDatabase.getFriendRequestStatus(message.id)
|
|
||||||
if (message is MediaMmsMessageRecord) {
|
|
||||||
visibility = View.GONE
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!message.isOutgoing) {
|
|
||||||
visibility = if (friendRequestStatus == LokiMessageFriendRequestStatus.NONE) View.GONE else View.VISIBLE
|
|
||||||
buttonLinearLayout.visibility = if (friendRequestStatus != LokiMessageFriendRequestStatus.REQUEST_PENDING) View.GONE else View.VISIBLE
|
|
||||||
loaderContainer.visibility = if (friendRequestStatus == LokiMessageFriendRequestStatus.REQUEST_SENDING) View.VISIBLE else View.GONE
|
|
||||||
val formatID = when (friendRequestStatus) {
|
|
||||||
LokiMessageFriendRequestStatus.NONE, LokiMessageFriendRequestStatus.REQUEST_SENDING, LokiMessageFriendRequestStatus.REQUEST_FAILED -> return
|
|
||||||
LokiMessageFriendRequestStatus.REQUEST_PENDING -> R.string.view_friend_request_incoming_pending_message
|
|
||||||
LokiMessageFriendRequestStatus.REQUEST_ACCEPTED -> R.string.view_friend_request_incoming_accepted_message
|
|
||||||
LokiMessageFriendRequestStatus.REQUEST_REJECTED -> R.string.view_friend_request_incoming_declined_message
|
|
||||||
LokiMessageFriendRequestStatus.REQUEST_EXPIRED -> R.string.view_friend_request_incoming_expired_message
|
|
||||||
}
|
|
||||||
label.text = resources.getString(formatID, contactDisplayName)
|
|
||||||
} else {
|
|
||||||
visibility = if (friendRequestStatus == LokiMessageFriendRequestStatus.NONE) View.GONE else View.VISIBLE
|
|
||||||
buttonLinearLayout.visibility = View.GONE
|
|
||||||
loaderContainer.visibility = View.GONE
|
|
||||||
val formatID = when (friendRequestStatus) {
|
|
||||||
LokiMessageFriendRequestStatus.NONE -> return
|
|
||||||
LokiMessageFriendRequestStatus.REQUEST_SENDING, LokiMessageFriendRequestStatus.REQUEST_FAILED -> null
|
|
||||||
LokiMessageFriendRequestStatus.REQUEST_PENDING, LokiMessageFriendRequestStatus.REQUEST_REJECTED -> R.string.view_friend_request_outgoing_pending_message
|
|
||||||
LokiMessageFriendRequestStatus.REQUEST_ACCEPTED -> R.string.view_friend_request_outgoing_accepted_message
|
|
||||||
LokiMessageFriendRequestStatus.REQUEST_EXPIRED -> R.string.view_friend_request_outgoing_expired_message
|
|
||||||
}
|
|
||||||
if (formatID != null) {
|
|
||||||
label.text = resources.getString(formatID, contactDisplayName)
|
|
||||||
}
|
|
||||||
label.visibility = if (formatID != null) View.VISIBLE else View.GONE
|
|
||||||
topSpacer.visibility = label.visibility
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Interaction
|
|
||||||
private fun accept() {
|
|
||||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
|
||||||
lokiMessageDatabase.setFriendRequestStatus(message!!.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
|
||||||
updateUI()
|
|
||||||
delegate?.acceptFriendRequest(message!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reject() {
|
|
||||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
|
||||||
lokiMessageDatabase.setFriendRequestStatus(message!!.id, LokiMessageFriendRequestStatus.REQUEST_REJECTED)
|
|
||||||
updateUI()
|
|
||||||
delegate?.rejectFriendRequest(message!!)
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Delegate
|
|
||||||
interface FriendRequestViewDelegate {
|
|
||||||
/**
|
|
||||||
* Implementations of this method should update the thread's friend request status
|
|
||||||
* and send a friend request accepted message.
|
|
||||||
*/
|
|
||||||
fun acceptFriendRequest(friendRequest: MessageRecord)
|
|
||||||
/**
|
|
||||||
* Implementations of this method should update the thread's friend request status
|
|
||||||
* and remove the pre keys associated with the contact.
|
|
||||||
*/
|
|
||||||
fun rejectFriendRequest(friendRequest: MessageRecord)
|
|
||||||
}
|
|
||||||
// endregion
|
|
@ -9,7 +9,7 @@ import android.widget.LinearLayout
|
|||||||
import kotlinx.android.synthetic.main.view_mention_candidate.view.*
|
import kotlinx.android.synthetic.main.view_mention_candidate.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI
|
import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI
|
||||||
import org.whispersystems.signalservice.loki.protocol.mentions.Mention
|
import org.whispersystems.signalservice.loki.protocol.mentions.Mention
|
||||||
|
|
||||||
class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
@ -31,13 +31,13 @@ class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr:
|
|||||||
|
|
||||||
private fun update() {
|
private fun update() {
|
||||||
displayNameTextView.text = mentionCandidate.displayName
|
displayNameTextView.text = mentionCandidate.displayName
|
||||||
profilePictureView.hexEncodedPublicKey = mentionCandidate.hexEncodedPublicKey
|
profilePictureView.publicKey = mentionCandidate.publicKey
|
||||||
profilePictureView.additionalHexEncodedPublicKey = null
|
profilePictureView.additionalPublicKey = null
|
||||||
profilePictureView.isRSSFeed = false
|
profilePictureView.isRSSFeed = false
|
||||||
profilePictureView.glide = glide!!
|
profilePictureView.glide = glide!!
|
||||||
profilePictureView.update()
|
profilePictureView.update()
|
||||||
if (publicChatServer != null && publicChatChannel != null) {
|
if (publicChatServer != null && publicChatChannel != null) {
|
||||||
val isUserModerator = LokiPublicChatAPI.isUserModerator(mentionCandidate.hexEncodedPublicKey, publicChatChannel!!, publicChatServer!!)
|
val isUserModerator = PublicChatAPI.isUserModerator(mentionCandidate.publicKey, publicChatChannel!!, publicChatServer!!)
|
||||||
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
|
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
|
||||||
} else {
|
} else {
|
||||||
moderatorIconImageView.visibility = View.GONE
|
moderatorIconImageView.visibility = View.GONE
|
||||||
|
@ -16,6 +16,7 @@ import android.os.Vibrator
|
|||||||
import android.support.annotation.ColorRes
|
import android.support.annotation.ColorRes
|
||||||
import android.support.annotation.DrawableRes
|
import android.support.annotation.DrawableRes
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
@ -91,6 +92,7 @@ class NewConversationButtonSetView : RelativeLayout {
|
|||||||
addView(imageView)
|
addView(imageView)
|
||||||
imageView.x = collapsedImageViewPosition.x
|
imageView.x = collapsedImageViewPosition.x
|
||||||
imageView.y = collapsedImageViewPosition.y
|
imageView.y = collapsedImageViewPosition.y
|
||||||
|
gravity = Gravity.TOP or Gravity.LEFT // Intentionally not Gravity.START
|
||||||
}
|
}
|
||||||
|
|
||||||
fun expand() {
|
fun expand() {
|
||||||
|
@ -21,8 +21,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|||||||
|
|
||||||
class ProfilePictureView : RelativeLayout {
|
class ProfilePictureView : RelativeLayout {
|
||||||
lateinit var glide: GlideRequests
|
lateinit var glide: GlideRequests
|
||||||
var hexEncodedPublicKey: String? = null
|
var publicKey: String? = null
|
||||||
var additionalHexEncodedPublicKey: String? = null
|
var additionalPublicKey: String? = null
|
||||||
var isRSSFeed = false
|
var isRSSFeed = false
|
||||||
var isLarge = false
|
var isLarge = false
|
||||||
|
|
||||||
@ -52,11 +52,11 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
|
|
||||||
// region Updating
|
// region Updating
|
||||||
fun update() {
|
fun update() {
|
||||||
val hexEncodedPublicKey = hexEncodedPublicKey ?: return
|
val publicKey = publicKey ?: return
|
||||||
val additionalHexEncodedPublicKey = additionalHexEncodedPublicKey
|
val additionalPublicKey = additionalPublicKey
|
||||||
doubleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
|
doubleModeImageViewContainer.visibility = if (additionalPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
|
||||||
singleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey == null && !isRSSFeed && !isLarge) View.VISIBLE else View.INVISIBLE
|
singleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && !isLarge) View.VISIBLE else View.INVISIBLE
|
||||||
largeSingleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey == null && !isRSSFeed && isLarge) View.VISIBLE else View.INVISIBLE
|
largeSingleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && isLarge) View.VISIBLE else View.INVISIBLE
|
||||||
rssImageView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE
|
rssImageView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE
|
||||||
fun setProfilePictureIfNeeded(imageView: ImageView, hexEncodedPublicKey: String, @DimenRes sizeID: Int) {
|
fun setProfilePictureIfNeeded(imageView: ImageView, hexEncodedPublicKey: String, @DimenRes sizeID: Int) {
|
||||||
glide.clear(imageView)
|
glide.clear(imageView)
|
||||||
@ -76,10 +76,10 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
imageView.setImageDrawable(null)
|
imageView.setImageDrawable(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setProfilePictureIfNeeded(doubleModeImageView1, hexEncodedPublicKey, R.dimen.small_profile_picture_size)
|
setProfilePictureIfNeeded(doubleModeImageView1, publicKey, R.dimen.small_profile_picture_size)
|
||||||
setProfilePictureIfNeeded(doubleModeImageView2, additionalHexEncodedPublicKey ?: "", R.dimen.small_profile_picture_size)
|
setProfilePictureIfNeeded(doubleModeImageView2, additionalPublicKey ?: "", R.dimen.small_profile_picture_size)
|
||||||
setProfilePictureIfNeeded(singleModeImageView, hexEncodedPublicKey, R.dimen.medium_profile_picture_size)
|
setProfilePictureIfNeeded(singleModeImageView, publicKey, R.dimen.medium_profile_picture_size)
|
||||||
setProfilePictureIfNeeded(largeSingleModeImageView, hexEncodedPublicKey, R.dimen.large_profile_picture_size)
|
setProfilePictureIfNeeded(largeSingleModeImageView, publicKey, R.dimen.large_profile_picture_size)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
@ -48,20 +48,20 @@ class UserView : LinearLayout {
|
|||||||
val address = user.address.serialize()
|
val address = user.address.serialize()
|
||||||
if (user.isGroupRecipient) {
|
if (user.isGroupRecipient) {
|
||||||
if ("Session Public Chat" == user.name || user.address.isRSSFeed) {
|
if ("Session Public Chat" == user.name || user.address.isRSSFeed) {
|
||||||
profilePictureView.hexEncodedPublicKey = ""
|
profilePictureView.publicKey = ""
|
||||||
profilePictureView.additionalHexEncodedPublicKey = null
|
profilePictureView.additionalPublicKey = null
|
||||||
profilePictureView.isRSSFeed = true
|
profilePictureView.isRSSFeed = true
|
||||||
} else {
|
} else {
|
||||||
val threadID = GroupManager.getThreadIDFromGroupID(address, context)
|
val threadID = GroupManager.getThreadIDFromGroupID(address, context)
|
||||||
val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toList() ?: listOf()
|
val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toList() ?: listOf()
|
||||||
val randomUsers = users.sorted() // Sort to provide a level of stability
|
val randomUsers = users.sorted() // Sort to provide a level of stability
|
||||||
profilePictureView.hexEncodedPublicKey = randomUsers.getOrNull(0) ?: ""
|
profilePictureView.publicKey = randomUsers.getOrNull(0) ?: ""
|
||||||
profilePictureView.additionalHexEncodedPublicKey = randomUsers.getOrNull(1) ?: ""
|
profilePictureView.additionalPublicKey = randomUsers.getOrNull(1) ?: ""
|
||||||
profilePictureView.isRSSFeed = false
|
profilePictureView.isRSSFeed = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
profilePictureView.hexEncodedPublicKey = address
|
profilePictureView.publicKey = address
|
||||||
profilePictureView.additionalHexEncodedPublicKey = null
|
profilePictureView.additionalPublicKey = null
|
||||||
profilePictureView.isRSSFeed = false
|
profilePictureView.isRSSFeed = false
|
||||||
}
|
}
|
||||||
profilePictureView.glide = glide
|
profilePictureView.glide = glide
|
||||||
|
@ -23,7 +23,6 @@ public class OutgoingMediaMessage {
|
|||||||
private final int distributionType;
|
private final int distributionType;
|
||||||
private final int subscriptionId;
|
private final int subscriptionId;
|
||||||
private final long expiresIn;
|
private final long expiresIn;
|
||||||
public boolean isFriendRequest = false;
|
|
||||||
private final QuoteModel outgoingQuote;
|
private final QuoteModel outgoingQuote;
|
||||||
|
|
||||||
private final List<NetworkFailure> networkFailures = new LinkedList<>();
|
private final List<NetworkFailure> networkFailures = new LinkedList<>();
|
||||||
|
@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.mms;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
|
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI;
|
||||||
|
|
||||||
public class PushMediaConstraints extends MediaConstraints {
|
public class PushMediaConstraints extends MediaConstraints {
|
||||||
|
|
||||||
@ -22,26 +22,26 @@ public class PushMediaConstraints extends MediaConstraints {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getImageMaxSize(Context context) {
|
public int getImageMaxSize(Context context) {
|
||||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
return FileServerAPI.Companion.getMaxFileSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getGifMaxSize(Context context) {
|
public int getGifMaxSize(Context context) {
|
||||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
return FileServerAPI.Companion.getMaxFileSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getVideoMaxSize(Context context) {
|
public int getVideoMaxSize(Context context) {
|
||||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
return FileServerAPI.Companion.getMaxFileSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAudioMaxSize(Context context) {
|
public int getAudioMaxSize(Context context) {
|
||||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
return FileServerAPI.Companion.getMaxFileSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDocumentMaxSize(Context context) {
|
public int getDocumentMaxSize(Context context) {
|
||||||
return LokiFileServerAPI.Companion.getMaxFileSize();
|
return FileServerAPI.Companion.getMaxFileSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,7 +325,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
ReplyMethod replyMethod = ReplyMethod.forRecipient(context, recipient);
|
ReplyMethod replyMethod = ReplyMethod.forRecipient(context, recipient);
|
||||||
|
|
||||||
boolean canReply = SessionMetaProtocol.canUserReplyToNotification(recipient, context);
|
boolean canReply = SessionMetaProtocol.canUserReplyToNotification(recipient);
|
||||||
|
|
||||||
PendingIntent quickReplyIntent = canReply ? notificationState.getQuickReplyIntent(context, recipient) : null;
|
PendingIntent quickReplyIntent = canReply ? notificationState.getQuickReplyIntent(context, recipient) : null;
|
||||||
PendingIntent remoteReplyIntent = canReply ? notificationState.getRemoteReplyIntent(context, recipient, replyMethod) : null;
|
PendingIntent remoteReplyIntent = canReply ? notificationState.getRemoteReplyIntent(context, recipient, replyMethod) : null;
|
||||||
|
@ -93,7 +93,7 @@ public class MarkReadReceiver extends BroadcastReceiver {
|
|||||||
for (Address address : addressMap.keySet()) {
|
for (Address address : addressMap.keySet()) {
|
||||||
List<Long> timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList();
|
List<Long> timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList();
|
||||||
// Loki - Check whether we want to send a read receipt to this user
|
// Loki - Check whether we want to send a read receipt to this user
|
||||||
if (!SessionMetaProtocol.shouldSendReadReceipt(address, context)) { continue; }
|
if (!SessionMetaProtocol.shouldSendReadReceipt(address)) { continue; }
|
||||||
// Loki - Take into account multi device
|
// Loki - Take into account multi device
|
||||||
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(address.serialize());
|
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(address.serialize());
|
||||||
for (String device : linkedDevices) {
|
for (String device : linkedDevices) {
|
||||||
|
@ -7,7 +7,7 @@ import androidx.annotation.MainThread;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.loki.api.LokiPublicChatManager;
|
import org.thoughtcrime.securesms.loki.api.PublicChatManager;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.Debouncer;
|
import org.thoughtcrime.securesms.util.Debouncer;
|
||||||
import org.whispersystems.signalservice.loki.api.Poller;
|
import org.whispersystems.signalservice.loki.api.Poller;
|
||||||
@ -40,15 +40,15 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateNotification(@NonNull Context context) {
|
public void updateNotification(@NonNull Context context) {
|
||||||
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
Poller lokiPoller = ApplicationContext.getInstance(context).poller;
|
||||||
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
PublicChatManager publicChatManager = ApplicationContext.getInstance(context).publicChatManager;
|
||||||
boolean isCaughtUp = true;
|
boolean isCaughtUp = true;
|
||||||
if (lokiPoller != null) {
|
if (lokiPoller != null) {
|
||||||
isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
|
isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lokiPublicChatManager != null) {
|
if (publicChatManager != null) {
|
||||||
isCaughtUp = isCaughtUp && lokiPublicChatManager.areAllCaughtUp();
|
isCaughtUp = isCaughtUp && publicChatManager.areAllCaughtUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCaughtUp) {
|
if (isCaughtUp) {
|
||||||
@ -60,15 +60,15 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateNotification(@NonNull Context context, long threadId) {
|
public void updateNotification(@NonNull Context context, long threadId) {
|
||||||
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
Poller lokiPoller = ApplicationContext.getInstance(context).poller;
|
||||||
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
PublicChatManager publicChatManager = ApplicationContext.getInstance(context).publicChatManager;
|
||||||
boolean isCaughtUp = true;
|
boolean isCaughtUp = true;
|
||||||
if (lokiPoller != null) {
|
if (lokiPoller != null) {
|
||||||
isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
|
isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lokiPublicChatManager != null) {
|
if (publicChatManager != null) {
|
||||||
isCaughtUp = isCaughtUp && lokiPublicChatManager.areAllCaughtUp();
|
isCaughtUp = isCaughtUp && publicChatManager.areAllCaughtUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCaughtUp) {
|
if (isCaughtUp) {
|
||||||
@ -80,15 +80,15 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateNotification(@NonNull Context context, long threadId, boolean signal) {
|
public void updateNotification(@NonNull Context context, long threadId, boolean signal) {
|
||||||
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
Poller lokiPoller = ApplicationContext.getInstance(context).poller;
|
||||||
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
PublicChatManager publicChatManager = ApplicationContext.getInstance(context).publicChatManager;
|
||||||
boolean isCaughtUp = true;
|
boolean isCaughtUp = true;
|
||||||
if (lokiPoller != null) {
|
if (lokiPoller != null) {
|
||||||
isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
|
isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lokiPublicChatManager != null) {
|
if (publicChatManager != null) {
|
||||||
isCaughtUp = isCaughtUp && lokiPublicChatManager.areAllCaughtUp();
|
isCaughtUp = isCaughtUp && publicChatManager.areAllCaughtUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCaughtUp) {
|
if (isCaughtUp) {
|
||||||
@ -100,15 +100,15 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateNotification(@android.support.annotation.NonNull Context context, boolean signal, int reminderCount) {
|
public void updateNotification(@android.support.annotation.NonNull Context context, boolean signal, int reminderCount) {
|
||||||
Poller lokiPoller = ApplicationContext.getInstance(context).lokiPoller;
|
Poller lokiPoller = ApplicationContext.getInstance(context).poller;
|
||||||
LokiPublicChatManager lokiPublicChatManager = ApplicationContext.getInstance(context).lokiPublicChatManager;
|
PublicChatManager publicChatManager = ApplicationContext.getInstance(context).publicChatManager;
|
||||||
boolean isCaughtUp = true;
|
boolean isCaughtUp = true;
|
||||||
if (lokiPoller != null) {
|
if (lokiPoller != null) {
|
||||||
isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
|
isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lokiPublicChatManager != null) {
|
if (publicChatManager != null) {
|
||||||
isCaughtUp = isCaughtUp && lokiPublicChatManager.areAllCaughtUp();
|
isCaughtUp = isCaughtUp && publicChatManager.areAllCaughtUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCaughtUp) {
|
if (isCaughtUp) {
|
||||||
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.push;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
|
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
@ -18,19 +17,4 @@ public class MessageSenderEventListener implements SignalServiceMessageSender.Ev
|
|||||||
public void onSecurityEvent(SignalServiceAddress textSecureAddress) {
|
public void onSecurityEvent(SignalServiceAddress textSecureAddress) {
|
||||||
SecurityEvent.broadcastSecurityUpdateEvent(context);
|
SecurityEvent.broadcastSecurityUpdateEvent(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFriendRequestSending(long messageID, long threadID) {
|
|
||||||
FriendRequestProtocol.setFriendRequestStatusToSendingIfNeeded(context, messageID, threadID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFriendRequestSent(long messageID, long threadID) {
|
|
||||||
FriendRequestProtocol.setFriendRequestStatusToSentIfNeeded(context, messageID, threadID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFriendRequestSendingFailed(long messageID, long threadID) {
|
|
||||||
FriendRequestProtocol.setFriendRequestStatusToFailedIfNeeded(context, messageID, threadID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,6 @@ import org.thoughtcrime.securesms.jobs.MmsSendJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
|
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
|
||||||
import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
||||||
import org.thoughtcrime.securesms.mms.MmsException;
|
import org.thoughtcrime.securesms.mms.MmsException;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||||
@ -76,12 +75,7 @@ public class MessageSender {
|
|||||||
|
|
||||||
long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener);
|
long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener);
|
||||||
|
|
||||||
// Loki - Set the message's friend request status as soon as it hits the database
|
sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
|
||||||
if (FriendRequestProtocol.shouldUpdateFriendRequestStatusFromOutgoingTextMessage(context, message)) {
|
|
||||||
FriendRequestProtocol.setFriendRequestStatusToSendingIfNeeded(context, messageId, allocatedThreadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTextMessage(context, recipient, forceSms, keyExchange, messageId, message.isEndSession());
|
|
||||||
|
|
||||||
return allocatedThreadId;
|
return allocatedThreadId;
|
||||||
}
|
}
|
||||||
@ -107,11 +101,6 @@ public class MessageSender {
|
|||||||
Recipient recipient = message.getRecipient();
|
Recipient recipient = message.getRecipient();
|
||||||
long messageId = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
|
long messageId = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
|
||||||
|
|
||||||
// Loki - Set the message's friend request status as soon as it hits the database
|
|
||||||
if (FriendRequestProtocol.shouldUpdateFriendRequestStatusFromOutgoingMediaMessage(context, message)) {
|
|
||||||
FriendRequestProtocol.setFriendRequestStatusToSendingIfNeeded(context, messageId, allocatedThreadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMediaMessage(context, recipient, forceSms, messageId, message.getExpiresIn());
|
sendMediaMessage(context, recipient, forceSms, messageId, message.getExpiresIn());
|
||||||
return allocatedThreadId;
|
return allocatedThreadId;
|
||||||
} catch (MmsException e) {
|
} catch (MmsException e) {
|
||||||
@ -135,7 +124,7 @@ public class MessageSender {
|
|||||||
if (messageRecord.isMms()) {
|
if (messageRecord.isMms()) {
|
||||||
sendMediaMessage(context, recipient, forceSms, messageId, expiresIn);
|
sendMediaMessage(context, recipient, forceSms, messageId, expiresIn);
|
||||||
} else {
|
} else {
|
||||||
sendTextMessage(context, recipient, forceSms, keyExchange, messageId, messageRecord.isEndSession());
|
sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,17 +141,17 @@ public class MessageSender {
|
|||||||
|
|
||||||
private static void sendTextMessage(Context context, Recipient recipient,
|
private static void sendTextMessage(Context context, Recipient recipient,
|
||||||
boolean forceSms, boolean keyExchange,
|
boolean forceSms, boolean keyExchange,
|
||||||
long messageId, boolean isEndSession)
|
long messageId)
|
||||||
{
|
{
|
||||||
if (isLocalSelfSend(context, recipient, forceSms)) {
|
if (isLocalSelfSend(context, recipient, forceSms)) {
|
||||||
sendLocalTextSelf(context, messageId);
|
sendLocalTextSelf(context, messageId);
|
||||||
} else {
|
} else {
|
||||||
sendTextPush(context, recipient, messageId, isEndSession);
|
sendTextPush(context, recipient, messageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendTextPush(Context context, Recipient recipient, long messageId, boolean isEndSession) {
|
private static void sendTextPush(Context context, Recipient recipient, long messageId) {
|
||||||
MultiDeviceProtocol.sendTextPush(context, recipient, messageId, isEndSession);
|
MultiDeviceProtocol.sendTextPush(context, recipient, messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendMediaPush(Context context, Recipient recipient, long messageId) {
|
private static void sendMediaPush(Context context, Recipient recipient, long messageId) {
|
||||||
|
@ -9,7 +9,6 @@ public class OutgoingTextMessage {
|
|||||||
private final String message;
|
private final String message;
|
||||||
private final int subscriptionId;
|
private final int subscriptionId;
|
||||||
private final long expiresIn;
|
private final long expiresIn;
|
||||||
public boolean isFriendRequest = false;
|
|
||||||
|
|
||||||
public OutgoingTextMessage(Recipient recipient, String message, int subscriptionId) {
|
public OutgoingTextMessage(Recipient recipient, String message, int subscriptionId) {
|
||||||
this(recipient, message, 0, subscriptionId);
|
this(recipient, message, 0, subscriptionId);
|
||||||
|
Loading…
Reference in New Issue
Block a user