diff --git a/build.gradle b/build.gradle
index c2ba9c23bd..8ddf482a8e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -197,8 +197,8 @@ dependencies {
implementation "com.opencsv:opencsv:$opencsv_version"
}
-def canonicalVersionCode = 56
-def canonicalVersionName = "1.2.3"
+def canonicalVersionCode = 65
+def canonicalVersionName = "1.4.1"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
diff --git a/res/layout-sw400dp/activity_display_name.xml b/res/layout-sw400dp/activity_display_name.xml
index 2443814ae2..1716f068f2 100644
--- a/res/layout-sw400dp/activity_display_name.xml
+++ b/res/layout-sw400dp/activity_display_name.xml
@@ -25,7 +25,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
- android:layout_marginTop="10dp"
+ android:layout_marginTop="7dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
@@ -36,8 +36,10 @@
android:id="@+id/displayNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingTop="28dp"
+ android:paddingBottom="28dp"
android:layout_marginLeft="@dimen/very_large_spacing"
- android:layout_marginTop="@dimen/medium_spacing"
+ android:layout_marginTop="12dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:inputType="textCapWords"
android:hint="@string/activity_display_name_edit_text_hint" />
diff --git a/res/layout-sw400dp/activity_landing.xml b/res/layout-sw400dp/activity_landing.xml
index 815f4f9732..c87a549920 100644
--- a/res/layout-sw400dp/activity_landing.xml
+++ b/res/layout-sw400dp/activity_landing.xml
@@ -57,6 +57,7 @@
android:layout_height="@dimen/onboarding_button_bottom_offset"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
+ android:visibility="invisible"
android:gravity="center"
android:background="@color/transparent"
android:textAllCaps="false"
diff --git a/res/layout-sw400dp/activity_pn_mode.xml b/res/layout-sw400dp/activity_pn_mode.xml
index e2e5433453..5efbf059bd 100644
--- a/res/layout-sw400dp/activity_pn_mode.xml
+++ b/res/layout-sw400dp/activity_pn_mode.xml
@@ -1,114 +1,116 @@
-
+ android:layout_height="match_parent"
+ android:background="@drawable/default_session_background"
+ android:orientation="vertical">
-
+
+
+ android:layout_marginLeft="@dimen/very_large_spacing"
+ android:layout_marginRight="@dimen/very_large_spacing"
+ android:textSize="@dimen/very_large_font_size"
+ android:textStyle="bold"
+ android:textColor="@color/text"
+ android:text="Message Notifications" />
+
+
+
+
-
-
+ android:textStyle="bold"
+ android:text="Fast Mode" />
-
+ android:layout_marginTop="4dp"
+ android:textSize="@dimen/very_small_font_size"
+ android:textColor="@color/text"
+ android:text="You’ll be notified of new messages reliably and immediately using Google’s notification servers. The contents of your messages, and who you’re messaging, are never exposed to Google." />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_marginTop="4dp"
+ android:textSize="@dimen/small_font_size"
+ android:textColor="@color/accent"
+ android:textStyle="bold"
+ android:text="@string/activity_pn_mode_recommended_option_tag" />
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout-sw400dp/activity_restore.xml b/res/layout-sw400dp/activity_restore.xml
index 6b5f6d95fe..f011b04c4a 100644
--- a/res/layout-sw400dp/activity_restore.xml
+++ b/res/layout-sw400dp/activity_restore.xml
@@ -25,7 +25,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
- android:layout_marginTop="10dp"
+ android:layout_marginTop="7dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
@@ -36,8 +36,10 @@
android:id="@+id/mnemonicEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingTop="28dp"
+ android:paddingBottom="28dp"
android:layout_marginLeft="@dimen/very_large_spacing"
- android:layout_marginTop="@dimen/medium_spacing"
+ android:layout_marginTop="12dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:hint="@string/activity_restore_seed_edit_text_hint" />
diff --git a/res/layout-sw400dp/activity_seed.xml b/res/layout-sw400dp/activity_seed.xml
index f5bc0db14e..3eeb46dbb2 100644
--- a/res/layout-sw400dp/activity_seed.xml
+++ b/res/layout-sw400dp/activity_seed.xml
@@ -22,7 +22,7 @@
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
- android:textSize="@dimen/very_large_font_size"
+ android:textSize="26sp"
android:textStyle="bold"
android:textColor="@color/text"
android:text="@string/activity_seed_title_2" />
@@ -31,9 +31,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
- android:layout_marginTop="10dp"
+ android:layout_marginTop="8dp"
android:layout_marginRight="@dimen/very_large_spacing"
- android:textSize="@dimen/medium_font_size"
+ android:textSize="16sp"
android:textColor="@color/text"
android:text="@string/activity_seed_explanation" />
@@ -43,10 +43,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
- android:layout_marginTop="@dimen/medium_spacing"
+ android:layout_marginTop="12dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:gravity="center"
- android:textSize="@dimen/medium_font_size"
+ android:textSize="16sp"
android:textAlignment="center"
android:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" />
@@ -56,7 +56,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textAlignment="center"
- android:textSize="@dimen/medium_font_size"
+ android:textSize="16sp"
android:textColor="@color/text"
android:alpha="0.6"
android:text="@string/activity_seed_reveal_button_title" />
diff --git a/res/layout-sw400dp/fragment_enter_chat_url.xml b/res/layout-sw400dp/fragment_enter_chat_url.xml
index 0fcac50855..4755debf50 100644
--- a/res/layout-sw400dp/fragment_enter_chat_url.xml
+++ b/res/layout-sw400dp/fragment_enter_chat_url.xml
@@ -28,19 +28,7 @@
android:id="@+id/joinPublicChatButton"
android:layout_width="196dp"
android:layout_height="@dimen/medium_button_height"
- android:text="Next" />
-
-
+ android:text="@string/next" />
\ No newline at end of file
diff --git a/res/layout-sw400dp/fragment_enter_public_key.xml b/res/layout-sw400dp/fragment_enter_public_key.xml
index d2a9e7a290..cf74f40d22 100644
--- a/res/layout-sw400dp/fragment_enter_public_key.xml
+++ b/res/layout-sw400dp/fragment_enter_public_key.xml
@@ -12,10 +12,16 @@
style="@style/SessionEditText"
android:id="@+id/publicKeyEditText"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="80dp"
android:layout_marginLeft="@dimen/large_spacing"
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/large_spacing"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp"
+ android:gravity="center_vertical"
+ android:inputType="textMultiLine"
+ android:maxLines="2"
+ android:imeOptions="actionDone"
android:hint="@string/fragment_enter_public_key_edit_text_hint" />
diff --git a/res/layout-sw400dp/fragment_multi_device_removal_bottom_sheet.xml b/res/layout-sw400dp/fragment_multi_device_removal_bottom_sheet.xml
new file mode 100644
index 0000000000..f861c91246
--- /dev/null
+++ b/res/layout-sw400dp/fragment_multi_device_removal_bottom_sheet.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-sw400dp/fragment_pn_mode_bottom_sheet.xml b/res/layout-sw400dp/fragment_pn_mode_bottom_sheet.xml
deleted file mode 100644
index 0b01b12fdc..0000000000
--- a/res/layout-sw400dp/fragment_pn_mode_bottom_sheet.xml
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/layout-sw400dp/view_seed_reminder.xml b/res/layout-sw400dp/view_seed_reminder.xml
index aa28fcb565..c1b16634fe 100644
--- a/res/layout-sw400dp/view_seed_reminder.xml
+++ b/res/layout-sw400dp/view_seed_reminder.xml
@@ -40,11 +40,12 @@
diff --git a/res/layout/activity_landing.xml b/res/layout/activity_landing.xml
index 195020c0d9..3e3ba009dd 100644
--- a/res/layout/activity_landing.xml
+++ b/res/layout/activity_landing.xml
@@ -57,6 +57,7 @@
android:layout_height="@dimen/onboarding_button_bottom_offset"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
+ android:visibility="invisible"
android:gravity="center"
android:background="@color/transparent"
android:textAllCaps="false"
diff --git a/res/layout/activity_pn_mode.xml b/res/layout/activity_pn_mode.xml
index f8b2b75145..1b4897ae7d 100644
--- a/res/layout/activity_pn_mode.xml
+++ b/res/layout/activity_pn_mode.xml
@@ -1,114 +1,116 @@
-
+ android:layout_height="match_parent"
+ android:background="@drawable/default_session_background"
+ android:orientation="vertical">
-
+
+
+ android:layout_marginLeft="@dimen/very_large_spacing"
+ android:layout_marginRight="@dimen/very_large_spacing"
+ android:textSize="@dimen/large_font_size"
+ android:textStyle="bold"
+ android:textColor="@color/text"
+ android:text="Message Notifications" />
+
+
+
+
+ android:text="Fast Mode" />
+ android:text="You’ll be notified of new messages reliably and immediately using Google’s notification servers. The contents of your messages, and who you’re messaging, are never exposed to Google." />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_marginTop="4dp"
+ android:textSize="@dimen/small_font_size"
+ android:textColor="@color/accent"
+ android:textStyle="bold"
+ android:text="@string/activity_pn_mode_recommended_option_tag" />
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/activity_seed.xml b/res/layout/activity_seed.xml
index 72583ef0a9..625801d3c6 100644
--- a/res/layout/activity_seed.xml
+++ b/res/layout/activity_seed.xml
@@ -46,7 +46,7 @@
android:layout_marginTop="10dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:gravity="center"
- android:textSize="@dimen/medium_font_size"
+ android:textSize="14sp"
android:textAlignment="center"
android:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" />
@@ -59,6 +59,7 @@
android:textSize="14sp"
android:textColor="@color/text"
android:alpha="0.6"
+ android:visibility="gone"
android:text="@string/activity_seed_reveal_button_title" />
diff --git a/res/layout/conversation_input_panel.xml b/res/layout/conversation_input_panel.xml
index b2f64984b2..a6b7943ac3 100644
--- a/res/layout/conversation_input_panel.xml
+++ b/res/layout/conversation_input_panel.xml
@@ -96,6 +96,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
+ android:textAlignment="viewStart"
android:layout_weight="1"
android:textColorHint="#99FFFFFF"
android:textSize="@dimen/small_font_size"
@@ -137,7 +138,7 @@
android:id="@+id/recorder_view"
android:layout_height="match_parent"
android:layout_width="36dp"
- android:layout_gravity="center_vertical"
+ android:layout_gravity="center"
android:clipChildren="false"
android:clipToPadding="false">
diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml
index 999f79089c..5b7d503cdf 100644
--- a/res/layout/conversation_item_received.xml
+++ b/res/layout/conversation_item_received.xml
@@ -208,12 +208,6 @@
-
-
-
+
+
\ No newline at end of file
diff --git a/res/layout/conversation_item_sent.xml b/res/layout/conversation_item_sent.xml
index c89e2b8a5d..60f032ea8a 100644
--- a/res/layout/conversation_item_sent.xml
+++ b/res/layout/conversation_item_sent.xml
@@ -161,12 +161,6 @@
-
-
-
+
\ No newline at end of file
diff --git a/res/layout/dialog_clear_all_data.xml b/res/layout/dialog_clear_all_data.xml
index 4f9077a939..86e9924fa9 100644
--- a/res/layout/dialog_clear_all_data.xml
+++ b/res/layout/dialog_clear_all_data.xml
@@ -49,7 +49,7 @@
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_weight="1"
- android:layout_marginLeft="@dimen/medium_spacing"
+ android:layout_marginStart="@dimen/medium_spacing"
android:text="@string/delete" />
diff --git a/res/layout/dialog_link_device_master_mode.xml b/res/layout/dialog_link_device_master_mode.xml
index 6236bba517..76113b3200 100644
--- a/res/layout/dialog_link_device_master_mode.xml
+++ b/res/layout/dialog_link_device_master_mode.xml
@@ -87,7 +87,7 @@
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
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:visibility="gone" />
diff --git a/res/layout/dialog_seed.xml b/res/layout/dialog_seed.xml
index 11c4076dbc..37b523ad98 100644
--- a/res/layout/dialog_seed.xml
+++ b/res/layout/dialog_seed.xml
@@ -17,6 +17,7 @@
android:text="@string/dialog_seed_title"
android:textColor="@color/text"
android:textStyle="bold"
+ android:textAlignment="center"
android:textSize="@dimen/medium_font_size" />
diff --git a/res/layout/fragment_conversation_bottom_sheet.xml b/res/layout/fragment_conversation_bottom_sheet.xml
new file mode 100644
index 0000000000..2727ec8a3b
--- /dev/null
+++ b/res/layout/fragment_conversation_bottom_sheet.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/res/layout/fragment_enter_chat_url.xml b/res/layout/fragment_enter_chat_url.xml
index 26f705c5ea..8620c51fea 100644
--- a/res/layout/fragment_enter_chat_url.xml
+++ b/res/layout/fragment_enter_chat_url.xml
@@ -28,19 +28,7 @@
android:id="@+id/joinPublicChatButton"
android:layout_width="196dp"
android:layout_height="@dimen/medium_button_height"
+ android:layout_marginBottom="@dimen/medium_spacing"
android:text="@string/next" />
-
-
\ No newline at end of file
diff --git a/res/layout/fragment_enter_public_key.xml b/res/layout/fragment_enter_public_key.xml
index 27be721c1f..4abafd17d9 100644
--- a/res/layout/fragment_enter_public_key.xml
+++ b/res/layout/fragment_enter_public_key.xml
@@ -12,10 +12,16 @@
style="@style/SmallSessionEditText"
android:id="@+id/publicKeyEditText"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="54dp"
android:layout_marginLeft="@dimen/large_spacing"
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/large_spacing"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp"
+ android:gravity="center_vertical"
+ android:inputType="textMultiLine"
+ android:maxLines="2"
+ android:imeOptions="actionDone"
android:hint="@string/fragment_enter_public_key_edit_text_hint" />
diff --git a/res/layout/fragment_multi_device_removal_bottom_sheet.xml b/res/layout/fragment_multi_device_removal_bottom_sheet.xml
new file mode 100644
index 0000000000..2d0c9aef29
--- /dev/null
+++ b/res/layout/fragment_multi_device_removal_bottom_sheet.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/fragment_pn_mode_bottom_sheet.xml b/res/layout/fragment_pn_mode_bottom_sheet.xml
deleted file mode 100644
index 48fc5f3a31..0000000000
--- a/res/layout/fragment_pn_mode_bottom_sheet.xml
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/layout/microphone_recorder_view.xml b/res/layout/microphone_recorder_view.xml
index 6fed405950..e02cbc4c89 100644
--- a/res/layout/microphone_recorder_view.xml
+++ b/res/layout/microphone_recorder_view.xml
@@ -8,11 +8,11 @@
android:id="@+id/quick_audio_toggle"
android:layout_width="24dp"
android:layout_height="24dp"
- android:layout_gravity="center_vertical"
- android:layout_marginEnd="2dp"
+ android:layout_gravity="center"
android:background="@null"
android:contentDescription="@string/conversation_activity__quick_attachment_drawer_record_and_send_audio_description"
android:scaleType="centerInside"
+ android:layout_marginEnd="4dp"
android:tint="@color/text"
app:srcCompat="@drawable/ic_microphone" />
diff --git a/res/layout/view_conversation.xml b/res/layout/view_conversation.xml
index fffeb392e0..624d1b262b 100644
--- a/res/layout/view_conversation.xml
+++ b/res/layout/view_conversation.xml
@@ -8,7 +8,7 @@
android:gravity="center_vertical">
@@ -18,14 +18,14 @@
android:layout_width="@dimen/medium_profile_picture_size"
android:layout_height="@dimen/medium_profile_picture_size"
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_marginEnd="4dp" />
+ android:layout_marginStart="@dimen/medium_spacing" />
diff --git a/res/menu/conversation_block.xml b/res/menu/conversation_block.xml
new file mode 100644
index 0000000000..14dfea3546
--- /dev/null
+++ b/res/menu/conversation_block.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/res/menu/conversation_unblock.xml b/res/menu/conversation_unblock.xml
new file mode 100644
index 0000000000..354401a817
--- /dev/null
+++ b/res/menu/conversation_unblock.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/res/menu/menu_pn_mode.xml b/res/menu/menu_pn_mode.xml
new file mode 100644
index 0000000000..9fd06956f5
--- /dev/null
+++ b/res/menu/menu_pn_mode.xml
@@ -0,0 +1,11 @@
+
+
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index e25e90335c..61ac8b96ec 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -1288,5 +1288,185 @@ Schlüsselaustausch-Nachricht für eine ungültige Protokollversion empfangenZUM ENTSPERREN ANTIPPEN
Erinnerung:
Über
-
+
+
+
+
+
+
+ Fortsetzen
+ Kopieren
+ Ungültige URL
+ In die Zwischenablage kopiert.
+ Das Gerät konnte nicht verbunden werden.
+ Weiter
+ Teilen
+ Ungültige Session ID
+ Abbrechen
+ Ihre Session ID
+
+ Ihre Session beginnt hier...
+ Session ID erstellen
+ Ihre Session fortsetzen
+ Mit einem bestehenden Konto verlinken
+ Ihr Gerät wurde erfolgreich getrennt.
+
+ Was ist Session?
+ Es ist eine dezentrale, verschlüsselte Messaging-App.
+ Es werden also weder meine persönlichen Daten noch die Metadaten meiner Konversation erfasst? Wie funktioniert das?
+ Mit einer Kombination fortschrittlicher anonyme Routing- und End-to-End-Verschlüsselungstechnologien.
+ Freunde lassen Freunde keine kompromittierten Messenger verwenden. Herzlich Willkommen.
+
+ Das ist Ihre Session ID.
+ Ihre Session ID ist die eindeutige Adresse, unter der Personen Sie über Session kontaktieren können. Ihre Session ID ist nicht mit Ihrer realen Identität verbunden, völlig anonym und von Natur aus privat.
+ In die Zwischenablage kopiert.
+
+ Ihr Konto wiederherstellen
+ Geben Sie den Wiederherstellungssatz ein, den Sie bei der Anmeldung zur Wiederherstellung Ihres Kontos erhalten haben.
+ Ihr Wiederherstellungssatz
+
+ Gerät verbinden
+ Session ID eingeben
+ QR-Code scannen
+ Navigieren Sie zu "Einstellungen" > "Geräte" > "Ihr Gerät verknüpfen" und scannen Sie den angezeigten QR-Code, um den Verknüpfungsprozess zu starten.
+
+ Ihr Gerät verknüpfen
+ Navigieren Sie zu "Einstellungen" > "Geräte" > "Ihr Gerät verknüpfen" und geben Sie Ihre Session ID hier ein, um den Verknüpfungsprozess zu starten.
+ Geben Sie Ihre Session ID ein.
+
+ Wählen Sie Ihren Anzeigenamen
+ Dies ist Ihr Name, wenn Sie Session verwenden. Es kann Ihr richtiger Name, ein Alias oder etwas andere sein.
+ Geben Sie einen Anzeigenamen ein
+ Bitte wählen Sie einen Anzeigenamen
+ Bitte wählen Sie einen Anzeigenamen, der nur aus den Zeichen a - z, A - Z, 0 - 9 und _ besteht.
+ Bitte wählen Sie einen kürzeren Anzeigenamen
+
+ Empfohlen
+ Bitte wählen Sie eine Option aus.
+
+ Sie haben noch keine Kontakte.
+ Session starten
+ Sind Sie sich sicher, dass Sie diese Gruppe verlassen möchten?
+ Gruppe konnte nicht verlassen werden.
+ Möchten Sie diese Unterhaltung wirklich löschen?
+ Die Unterhaltung wurde gelöscht.
+
+ Ihr Wiederherstellungssatz
+ Das ist Ihr Wiederherstellungssatz.
+ Ihr Wiederherstellungssatz ist der Hauptschlüssel für Ihre Session ID. Mit diesem Satz können Sie Ihre Session ID wiederherstellen, wenn Sie den Zugriff auf Ihr Gerät verlieren. Bewahren Sie Ihren Wiederherstellungssatz an einem sicheren Ort auf und geben Sie ihn an niemandem weiter.
+ Zur Anzeige gedrückt halten
+
+ Sichern Sie Ihr Konto, indem Sie Ihren Wiederherstellungssatz speichern
+ Tippen und halten Sie die verborgenen Wörter, um Ihren Wiederherstellungssatz anzuzeigen, und speichern Sie ihn dann sicher, um Ihre Session ID zu sichern.
+ Bewahren Sie Ihren Wiederherstellungssatz an einem sicheren Ort auf.
+
+ Pfad
+ Session verbirgt Ihre IP-Adresse, indem Ihre Nachrichten über mehrere Dienstknoten im dezentralen Session-Netzwerk weitergeleitet werden. Dies sind die Länder, durch die Ihre Verbindung derzeit weitergeleitet wird:
+ Sie
+ Eingangsknoten
+ Dienstknoten
+ Ziel
+ Mehr erfahren
+
+ Neue Session
+ Session ID eingeben
+ QR-Code scannen
+ Scannen Sie den QR-Code eines Benutzers, um eine Session zu starten. QR-Codes finden Sie, indem Sie in den Einstellungen auf das QR-Code-Symbol tippen.
+
+ Geben Sie eine Session ID ein.
+ Benutzer können ihre Session ID freigeben, indem sie in ihren Einstellungen auf "Session ID freigeben" tippen oder ihren QR-Code freigeben.
+
+ Session benötigt Kamerazugriff, um die QR-Codes scannen zu können.
+ Kamerazugriff gewähren
+
+ Neue geschlossene Gruppe
+ Geben Sie einen Gruppennamen ein.
+ Geschlossene Gruppen unterstützen bis zu 10 Mitglieder und bieten den gleichen Schutz der Privatsphäre wie Einzelgespräche.
+ Sie haben noch keine Kontakte.
+ Session starten
+ Bitte geben Sie einen Gruppennamen ein.
+ Bitte geben Sie einen kürzeren Gruppennamen ein.
+ Bitte wählen Sie mindestens zwei Gruppenmitglieder aus.
+ Eine geschlossene Gruppe kann maximal zehn Mitglieder haben.
+ Ein Mitglied Ihrer Gruppe hat eine ungültige Session ID.
+
+ Offener Gruppe beitreten
+ Konnte der Gruppe nicht beitreten.
+ Gruppen-URL öffnen
+ QR-Code scannen
+ Scannen Sie den QR-Code der offenen Gruppe, der Sie beitreten möchten.
+
+ Geben Sie eine offene Gruppen-URL ein.
+
+ Einstellungen
+ Geben Sie einen Anzeigenamen ein.
+ Bitte wählen Sie einen Anzeigenamen.
+ Bitte wählen Sie einen Anzeigenamen, der nur aus den Zeichen a - z, A - Z, 0 - 9 und _ besteht.
+ Bitte wählen Sie einen kürzeren Anzeigenamen.
+ Datenschutz
+ Benachrichtigungen
+ Chats
+ Geräte
+ Wiederherstellungssatz
+ Daten löschen
+
+ Benachrichtigungen
+ Stil der Benachrichtigungen
+ Inhalt der Benachrichtigungen
+
+ Datenschutz
+
+ Chats
+
+ Geräte
+ Gerätelimit erreicht
+ Es ist derzeit nicht erlaubt, mehr als ein Gerät zu verbinden.
+ Gerät konnte nicht getrennt werden.
+ Ihr Gerät wurde erfolgreich getrennt.
+ Das Gerät konnte nicht verbunden werden.
+ Sie haben noch keine Geräte verlinkt.
+ Gerät (Beta) verknüpfen
+
+ Benachrichtigungsstrategie
+
+ Warten auf Autorisierung
+ Geräteverbindung autorisiert
+ Bitte überprüfen Sie, ob die folgenden Wörter mit denen auf Ihrem anderen Gerät übereinstimmen.
+ Ihr Gerät wurde erfolgreich verknüpft.
+
+ Warten auf Gerät
+ Verknüpfungsanfrage empfangen
+ Gerätelink wird autorisiert
+ Laden Sie Session auf Ihr anderes Gerät herunter und tippen Sie unten auf dem Startbildschirm auf "Mit einem bestehenden Konto verlinken". Wenn Sie bereits ein Konto auf Ihrem anderen Gerät haben, müssen Sie dieses Konto zuerst löschen.
+ Bitte überprüfen Sie, ob die folgenden Wörter mit denen auf Ihrem anderen Gerät übereinstimmen.
+ Bitte warten Sie, während der Gerätelink erstellt wird. Dies kann bis zu einer Minute dauern.
+ Autorisieren
+
+ Namen ändern
+ Gerät trennen
+
+ Geben Sie einen Namen ein.
+
+ Ihr Wiederherstellungssatz
+ Das ist Ihr Wiederherstellungssatz. Damit können Sie Ihre Session ID wiederherstellen oder auf ein neues Gerät migrieren.
+
+ Alle Daten löschen
+ Dadurch werden Ihre Nachrichten, Sessions und Kontakte dauerhaft gelöscht.
+
+ QR-Code
+ Meinen QR-Code anzeigen
+ QR-Code scannen
+ Scannen Sie den QR-Code einer Person, um ein Gespräch mit ihr zu beginnen.
+
+ Das ist Ihr QR-Code. Andere Benutzer können ihn scannen, um eine Session mit Ihnen zu starten.
+ QR-Code freigeben
+
+ Möchten Sie Ihre Session mit %s wiederherstellen?
+ Verwerfen
+ Wiederherstellen
+
+ Kontakte
+ Geschlossene Gruppen
+ Gruppen öffnen
+
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index dbb3634b91..6f1f23d7f3 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -1353,12 +1353,6 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del
Por favor, elige un nombre para mostrar que contenga solo caracteres a-z, A-Z, 0-9 y _
Por favor, elige un nombre para mostrar más corto
- Notificaciones Push
- Session tiene dos tipos de notificaciones push. Asegúrate de leer cuidadosamente las descripciones antes de elegir.
- Firebase Cloud Messaging
- Session usará el servicio Firebase Cloud Messaging para recibir notificaciones push. Recibirás notificaciones de nuevos mensajes de manera segura e inmediata. Usar FCM significa que tu dirección IP y device token serán compartidos con Google. Este sería ya el caso si recibes notificaciones push con otras aplicaciones. Tu dirección IP y device token serán compartidos con Loki, pero tus mensajes seguirán teniendo enrutamiento cebolla y encriptación de extremo a extremo, por lo que el contenido de tus mensajes seguirá siendo completamente privado.
- Sondeo en segundo plano
- Session revisará si hay nuevos mensajes en segundo plano y de manera ocasional. Esto garantiza una protección total de la privacidad, pero las notificaciones de mensajes pueden retrasarse significativamente.
Recomendado
Por favor, elige una opción
@@ -1369,17 +1363,6 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del
¿Seguro que quieres eliminar esta conversación?
Conversación eliminada
- Notificaciones Push
- Session ahora tiene dos formas de manejar las notificaciones push. Asegúrate de leer las descripciones cuidadosamente antes de elegir.
- Firebase Cloud Messaging
- Session usará el servicio Firebase Cloud Messaging para recibir las notificaciones push. Recibirás notificaciones de nuevos mensajes de manera confiable e inmediata. Usar FCM significa que este dispositivo se comunicará directamente con los servidores de Google para recuperar las notificaciones push, lo que expondrá tu dirección IP a Google. A tus mensajes se les seguirá realizando enrutamiento cebolla y cifrado de extremo a extremo, por lo que el contenido de tus mensajes permanecerá completamente privado.
- Sondeo en segundo plano
- Session revisará si hay nuevos mensajes en segundo plano y de manera ocasional. Esto garantiza una protección total de los metadatos, pero las notificaciónes de nuevos mensajes pueden retrasarse significativamente.
- Recomendado
- Por favor, elige una opción
- Confirmar
- Omitir
-
Tu frase de recuperación
Guarda tu frase de recuperación
Tu frase de recuperación es la llave maestra de tu ID de Session, puedes usarla para recuperar tu ID de Session en caso de pérdida de acceso a tu dispositivo. Guarda tu frase de recuperación en un lugar seguro y no se la digas a nadie.
@@ -1426,7 +1409,6 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del
Escanea el código QR del grupo abierto al que quieras unirte
Ingresa una URL de grupo abierto
- Cualquiera puede unirse a los grupos abiertos. Esto no brinda una protección completa de privacidad
Ajustes
Ingresa un nombre para mostrar
@@ -1458,8 +1440,6 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del
Enlazar un dispositivo
Estrategia de notificación
- Utilizar FCM
- El uso de Firebase Cloud Messaging permite notificaciones push más seguras, pero expone tu IP a Google.
Esperando la autorización
Vinculación de dispositivo autorizada
@@ -1493,16 +1473,6 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del
Este es tu código QR. Otros usuarios pueden escanearlo para empezar una Session contigo.
Compartir código QR
- Aceptar
- Rechazar
- %1$s te envió una solicitud de Session
- Has aceptado la solicitud de Session de %1$s
- Has rechazado la solicitud de Session de %1$s
- La solicitud de Session de %1$s ha expirado
- Le has enviado una solicitud de Session a %1$s
- %1$s aceptó tu solicitud de Session
- Tu solicitud de Session para %1$s ha expirado
-
¿Quieres restaurar tu Session con %s?
Descartar
Restaurar
@@ -1510,4 +1480,5 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del
Contactos
Grupos cerrados
Grupos abiertos
+
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index def3986d3a..05b162e14f 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -1214,5 +1214,217 @@
Session قفل شده است
یادآور:
درباره ی ما
-
+
+
+
+
+
+
+
+
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index c0b3977a79..b63a1c12c7 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -1294,5 +1294,186 @@ Vous avez reçu un message d’échange de clés pour une version de protocole i
TOUCHEZ POUR DÉVERROUILLER
Rappel :
À propos de Session
-
+
+
+
+
+
+
+ Continuer
+ Copier
+ URL non valide
+ Copié dans le presse-papier
+ Impossible de relier l\'appareil.
+ Suivant
+ Partager
+ Session ID non valide
+ Annuler
+ Votre Session ID
+
+ Votre Session débute ici...
+ Créer un Session ID
+ Continuez votre Session
+ Relier à un compte existant
+ Votre appareil a été déconnecté avec succès
+
+ Qu\'est-ce que Session ?
+ C\'est une application de messagerie décentralisée et cryptée
+ Elle ne recueille donc pas mes informations personnelles ou mes métadonnées de conversations ? Comment ça marche ?
+ En utilisant une combinaison de technologies avancées de routage anonyme et de chiffrement de bout en bout.
+ Les vrais amis ne laissent pas leurs amis utiliser des outils de messagerie compromis. De rien.
+
+ Dites bonjour à votre Session ID
+ Votre Session ID est l\'identifiant unique que les gens utilisent pour vous contacter dans Session. Sans lien avec votre identité réelle, votre Session ID est complètement anonyme et privé.
+ Copié dans le presse-papier
+
+ Restaurez votre compte
+ Pour restaurer votre compte, veuillez entrer la phrase de récupération qui vous a été fournie lors de la création de votre compte.
+
+ Saisissez votre phrase de récupération
+
+ Relier un appareil
+ Saisir un Session ID
+ Scanner un code QR
+ Rendez-vous dans Paramètres > Appareils reliés > Relier un appareil sur votre autre appareil, puis scannez le code QR affiché pour démarrer le processus de liaison.
+
+ Reliez votre appareil
+ Rendez-vous dans Paramètres > Appareils reliés > Relier un appareil sur votre autre appareil, puis saisissez votre Session ID pour démarrer le processus de liaison.
+ Saisissez votre Session ID
+
+ Choisissez votre nom d\'utilisateur
+ Ce sera votre nom lorsque vous utiliserez Session. Il peut s\'agir de votre vrai nom, d\'un pseudo ou de ce que vous voulez.
+ Saisissez un nom d\'utilisateur
+ Veuillez choisir un nom d\'utilisateur
+ Veuillez choisir un nom d\'utilisateur composé uniquement de caractères a-z, A-Z, 0-9 et _
+ Veuillez choisir un nom d\'utilisateur plus court
+
+ Recommandé
+ Veuillez choisir une option
+
+ Vous n\'avez pas encore de contacts
+ Démarrez une Session
+ Voulez-vous vraiment quitter ce groupe ?
+ Impossible de quitter le groupe
+ Voulez-vous vraiment supprimer cette conversation ?
+ Conversation supprimée
+
+ Votre phrase de récupération
+ Voici votre phrase de récupération
+ Votre phrase de récupération est la clé principale de votre Session ID - vous pouvez l\'utiliser pour restaurer votre Session ID si vous perdez l\'accès à votre appareil. Conservez la dans un endroit sûr et ne la donnez à personne.
+ Appuyer pour révéler
+
+ Sécurisez votre compte en sauvegardant votre phrase de récupération
+ Appuyez et maintenez les mots masqués pour révéler votre phrase de récupération, puis stockez-la en toute sécurité pour sécuriser votre Session ID.
+ Assurez-vous de conserver votre phrase de récupération dans un endroit sûr
+
+ Chemin
+ Session occulte votre adresse IP en envoyant vos messages via plusieurs nœuds de service dans le réseau décentralisé de Session. Voici les pays par le biais desquels votre connexion est actuellement envoyée :
+ Vous
+ Noeud d’entrée
+ Noeud de service
+ Destination
+ En savoir plus
+
+ Nouvelle Session
+ Saisir un Session ID
+ Scanner un Code QR
+ Scannez le code QR d\'un utilisateur pour démarrer une session. Les codes QR peuvent se trouver en appuyant sur l\'icône du code QR dans les paramètres du compte.
+
+ Saisissez le Session ID du destinataire
+ Les utilisateurs peuvent partager leur Session ID depuis les paramètres du compte ou en utilisant le code QR.
+
+ Session a besoin d\'accéder à l\'appareil photo pour scanner les codes QR
+ Autoriser l\'accès
+
+ Nouveau groupe privé
+ Saisissez un nom de groupe
+ Les groupes privés prennent en charge jusqu\'à 10 membres et offrent le même niveau de confidentialité que les sessions individuelles.
+ Vous n\'avez pas encore de contacts
+ Démarrer une session
+ Veuillez saisir un nom de groupe
+ Veuillez saisir un nom de groupe plus court
+ Veuillez sélectionner au moins 2 membres
+ Un groupe privé ne peut pas avoir plus de 10 membres
+ Un des membres de votre groupe a un Session ID non valide
+
+ Joindre un groupe public
+ Impossible de rejoindre le groupe
+ URL du groupe public
+ Scannez le code QR
+ Scannez le code QR du groupe public que vous souhaitez rejoindre
+
+ Saisissez une URL de groupe public
+
+ Paramètres
+ Saisissez un nom d\'utilisateur
+ Veuillez choisir un nom d\'utilisateur
+ Veuillez choisir un nom d\'utilisateur composé uniquement de caractères a-z, A-Z, 0-9 et _
+ Veuillez choisir un nom d\'utilisateur plus court
+ Confidientalité
+ Notifications
+ Chats
+ Appareils reliés
+ Phrase de récupération
+ Effacer les données
+
+ Notifications
+ Style de notification
+ Contenu de notification
+
+ Confidientalité
+
+ Chats
+
+ Appareils reliés
+ Limite d\'appareils atteinte
+ Il n\'est actuellement pas possible de relier plus d\'un appareil.
+ Impossible de déconnecter l\'appareil.
+ Votre appareil a été déconnecté avec succès
+ Impossible de relier l\'appareil.
+ Vous n\'avez encore relié aucun appareil
+ Relier un appareil
+
+ Stratégie de notification
+
+ En attente d\'autorisation
+ Liaison de l\'appareil autorisée
+ Veuillez vérifier que les mots ci-dessous correspondent à ceux affichés sur votre autre appareil.
+ Votre appareil a été connecté avec succès
+
+ En attente d\'une demande de liaison
+ Demande de liaison reçue
+ Autorisation de la liaison de l\'appareil
+ Téléchargez Session sur votre autre appareil, puis cliquez sur \"Relier à un compte existant\" en bas de l\'écran de d’accueil. Si vous possédez déjà un compte sur votre autre appareil, vous devrez d\'abord le supprimer.
+ Veuillez vérifier que les mots ci-dessous correspondent à ceux affichés sur votre autre appareil.
+ Veuillez patienter pendant la création de la liaison. Cela peut prendre jusqu\'à une minute.
+ Autoriser
+
+ Modifier le nom
+ Déconnecter l\'appareil
+
+ Saisissez un nom
+
+ Votre phrase de récupération
+ Ceci est votre phrase de récupération. Elle vous permet de restaurer ou migrer votre Session ID vers un nouvel appareil.
+
+ Effacer toutes les données
+ Cela supprimera définitivement vos messages, vos sessions et vos contacts.
+
+ Code QR
+ Afficher mon code QR
+ Scanner le code QR
+ Scannez le code QR d\'un autre utilisateur pour démarrer une session
+
+ Ceci est votre code QR. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous.
+ Partager le code QR
+
+ Voulez-vous restaurer votre session avec %s ?
+ Fermer
+ Restaurer
+
+ Contacts
+ Groupes privés
+ Groupes publics
+
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index a28b1505a5..14a9234fe3 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -1296,5 +1296,185 @@ Ricevuto un messaggio di scambio chiavi per una versione di protocollo non valid
TAPPA PER SBLOCCARE
Promemoria:
A riguardo
-
+
+
+
+
+
+
+ Continua
+ Copia
+ URL non valido
+ Copiato negli appunti
+ Impossibile collegare il dispositivo.
+ Successivo
+ Condividi
+ Sessione ID non valido
+ Annulla
+ La tua Sessione ID
+
+ La tua Sessione inizia qui...
+ Crea Sessione ID
+ Continua la Sessione
+ Collegamento a un account esistente
+ Il dispositivo è stato scollegato correttamente
+
+ Che cos\'è una Sessione?
+ È un\'app di messaggistica decentralizzato e crittografato
+ Quindi non raccoglie informazioni personali o metadati di conversazione? Come funziona?
+ Utilizza una combinazione di routing anonimo avanzato e tecnologie di crittografia end-to-end.
+ Gli amici non lasciano i suoi amici di utilizzare messaggistica compromessa. Prego.
+
+ Ecco la tua Sessione ID
+ La Sessione ID è l\'indirizzo univoco che le persone possono utilizzare per contattarti su una Sessione. Senza alcuna connessione con la tua vera identità, la Sessione ID è totalmente anonimo e privato fin dal incezione.
+ Copiato negli appunti
+
+ Ripristina il tuo account
+ Inserisci la frase di recupero che ti è stata data quando ti sei registrato per ripristinare il tuo account.
+ Inserisci la frase di recupero
+
+ Collega dispositivo
+ Inserisci la Sessione ID
+ Scansiona il codice QR
+ Vai su Impostazioni> Dispositivi> Collega un dispositivo su un altro dispositivo, quindi effettua la scansione del codice QR visualizzato per avviare il processo di collegamento.
+
+ Collega il dispositivo
+ Vai su Impostazioni> Dispositivi> Collega un dispositivo su un altro dispositivo e inserisci la Sessione ID per avviare il processo di collegamento.
+ Inserisci la Sessione ID
+
+ Scegli il nome da visualizzare
+ Questo sarà il tuo nome quando usi una Sessione. Può essere il tuo vero nome, un soprannome o qualsiasi altra cosa.
+ Inserisci il nome da visualizzare
+ Scegli il nome da visualizzare
+ Il nome visualizzare può contenere solo i caratteri a-z, AZ, 0-9 e _
+ Scegli un nome più breve
+
+ Consigliato
+ Scegli un\'opzione
+
+ Non hai ancora nessun contatto
+ Inizia una sessione
+ Sei sicuro di voler lasciare questo gruppo?
+ Impossibile lasciare il gruppo
+ Sei sicuro di voler eliminare questa conversazione?
+ Conversazione eliminata
+
+ Frase di recupero
+ La frase di recupero
+ La frase di recupero è la chiave principale per la Sessione ID: puoi usarla per ripristinare la Sessione ID se perdi l\'accesso al dispositivo. Conserva la frase di recupero in un luogo sicuro e non rivelarla a nessuno.
+ Tieni premuto per rivelare
+
+ Proteggi il tuo account salvando la frase di recupero
+ Tocca e tieni premute le parole redatte per rivelare la frase di recupero, salva in modo sicuro per proteggere la tua Sessione ID.
+ Assicurati di salvare la frase di recupero in un luogo sicuro
+
+ Percorso
+ La Sessione nasconde il tuo IP facendo rimbalzare i messaggi attraverso diversi nodi di servizio nella sua rete decentralizzata. Questi sono i paesi in cui la connessione viene rimbalzata attualmente:
+ Tu
+ Nodo di entrata
+ Nodo di servizio
+ Destinazione
+ Per saperne di più
+
+ Nuova sessione
+ Inserisci la Sessione ID
+ Scansiona il codice QR
+ Scansiona il codice QR di un utente per avviare una sessione. Puoi trovare i codici QR toccando l\'icona Codice QR nelle impostazioni dell\'account.
+
+ Inserisci la Sessione ID del destinatario
+ Gli utenti possono condividere la propria Sessione ID accedendo alle impostazioni del proprio account e toccando Condividi la Sessione ID o condividendo il proprio codice QR.
+
+ La Sessione richiede l\'accesso alla fotocamera per scansionare i codici QR
+ Concedi l\'accesso alla fotocamera
+
+ Nuovo gruppo chiuso
+ Inserisci un nome per il gruppo
+ I gruppi chiusi supportano fino a 10 membri e forniscono le stesse protezioni per la privacy delle sessioni one-to-one.
+ Non hai ancora nessun contatto
+ Inizia una sessione
+ Inserisci un nome per il gruppo
+ Inserisci un nome gruppo più breve
+ Scegli almeno 2 membri del gruppo
+ Un gruppo chiuso non può avere più di 10 membri
+ Uno dei membri del tuo gruppo ha una Sessione ID non valido
+
+ Unisciti a un gruppo aperto
+ Impossibile unirsi al gruppo
+ Apri l\'URL del gruppo
+ Scansiona il codice QR
+ Scansiona il codice QR del gruppo aperto a cui desideri partecipare
+
+ Inserisci l\'URL di un gruppo aperto
+
+ Impostazioni
+ Inserisci il nome da visualizzare
+ Scegli il nome da visualizzare
+ Il nome visualizzare può contenere solo i caratteri a-z, AZ, 0-9 e _
+ Scegli un nome più breve
+ Privacy
+ Notifiche
+ Chat
+ Dispositivi
+ Frase di recupero
+ Elimina dati
+
+ Notifiche
+ Stile della notifica
+ Contenuto della notifica
+
+ Privacy
+
+ Chat
+
+ Dispositivi
+ Limite del dispositivo raggiunto
+ Al momento non è consentito collegare più di un dispositivo.
+ Impossibile scollegare il dispositivo.
+ Il dispositivo è stato scollegato correttamente
+ Impossibile collegare il dispositivo.
+ Non hai ancora collegato nessun dispositivo
+ Collega un dispositivo
+
+ Strategia di notifica
+
+ In attesa di autorizzazione
+ Collegamento al dispositivo autorizzato
+ Verifica che le seguenti parole corrispondano a quelle visualizzate sull\'altro dispositivo.
+ Il dispositivo è stato collegato correttamente
+
+ In attesa del dispositivo
+ Richiesta di collegamento ricevuta
+ Autorizzazione al collegamento del dispositivo
+ Scarica la Sessione sull\'altro dispositivo e tocca Collega a un account esistente nella parte inferiore della schermata di destinazione. Se disponi già di un account sull\'altro dispositivo, dovrai prima eliminarlo.
+ Verifica che le seguenti parole corrispondano a quelle visualizzate sull\'altro dispositivo.
+ Attendi mentre viene creato il collegamento del dispositivo. Ciò può richiedere fino a un minuto.
+ Autorizza
+
+ Cambia nome
+ Scollega dispositivo
+
+ Inserisci un nome
+
+ Frase di recupero
+ Questa è la tua frase di recupero. Usala per ripristinare o migrare la Sessione ID a un nuovo dispositivo.
+
+ Elimina tutti i dati
+ Ciò eliminerà permanentemente i tuoi messaggi, sessioni e contatti.
+
+ Codice QR
+ Visualizza il mio codice QR
+ Scansiona il codice QR
+ Scansiona il codice QR di un utente per iniziare una conversazione con questa persona
+
+ Questo è il tuo codice QR. Altri utenti possono scansionarlo per iniziare una sessione con te.
+ Condividi codice QR
+
+ Desideri ripristinare la sessione con %s?
+ Rimuovi
+ Ripristina
+
+ Contatti
+ Gruppi chiusi
+ Gruppi aperti
+
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index bf0d5f818a..228c5cf935 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -1351,5 +1351,217 @@ Otrzymano wiadomość wymiany klucz dla niepoprawnej wersji protokołu.
DOTKNIJ, ABY ODBLOKOWAĆ
Przypomnienie:
O mnie
-
+
+
+
+
+
+
+
+
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
index ef8ec19443..f99d092a4d 100644
--- a/res/values-pt-rBR/strings.xml
+++ b/res/values-pt-rBR/strings.xml
@@ -1299,5 +1299,185 @@
TOQUE PARA DESBLOQUEAR
Lembrete:
Sobre
-
+
+
+
+
+
+
+ Continuar
+ Copiar
+ URL inválido
+ Copiado para a área de transferência
+ Não foi possível sincronizar o dispositivo.
+ Próximo
+ Compartilhar
+ ID Session inválido
+ Cancelar
+ Seu ID Session
+
+ O Session começa aqui...
+ Criar ID Session
+ Continuar com seu Session
+ Link para uma conta existente
+ O seu dispositivo foi dessincronizado com sucesso
+
+ O que é o Session?
+ É um aplicativo de mensagens criptografado e descentralizado
+ Então ele não coleta minhas informações pessoais ou meus metadados de conversa? Como funciona?
+ Usando uma combinação de tecnologias avançadas de roteamento anônimo e criptografia de ponta a ponta.
+ Amigos não deixam amigos usarem aplicativos de mensagem comprometidos. De nada.
+
+ Diga olá ao seu ID Session
+ Seu ID Session é o endereço exclusivo que as pessoas podem usar para entrar em contato com você no Session. Sem conexão com sua identidade real, seu ID Session é totalmente anônimo e privado por definição.
+ Copiado para a área de transferência
+
+ Restaurar sua conta
+ Digite a frase de recuperação que lhe foi fornecida quando você se inscreveu para restaurar sua conta.
+ Digite sua frase de recuperação
+
+ Sincronize dispositivo
+ Digite o ID Session
+ Escanear código QR
+ Vá para Configurações > Dispositivos > Sincronize um dispositivo no seu outro dispositivo e, em seguida, escaneie o código QR que aparece para iniciar o processo de sincronização.
+
+ Sincronize seu dispositivo
+ Vá para Configurações > Dispositivos > Sincronizar um dispositivo no seu outro dispositivo e insira seu ID Session aqui para iniciar o processo de sincronização.
+ Digite seu ID Session
+
+ Escolha seu nome de exibição
+ Este será o seu nome quando usar o Session. Pode ser seu nome verdadeiro, um apelido ou qualquer outra coisa que você quiser.
+ Digite um nome de exibição
+ Escolha um nome de exibição
+ Escolha um nome de exibição que contenha apenas caracteres az, AZ, 0-9 e _
+ Escolha um nome de exibição mais curto
+
+ Recomendado
+ Escolha uma opção
+
+ Você ainda não possui contatos
+ Iniciar uma sessão
+ Tem certeza de que deseja sair deste grupo?
+ Não foi possível sair do grupo
+ Tem certeza de que deseja excluir esta conversa?
+ Conversa excluída
+
+ Sua frase de recuperação
+ Revele sua frase de recuperação
+ Sua frase de recuperação é a chave mestra do seu ID Session - você pode usá-la para restaurar seu ID Session se perder o acesso ao seu dispositivo. Armazene sua frase de recuperação em um local seguro e não a entregue a ninguém.
+ Segure para revelar
+
+ Proteja sua conta salvando sua frase de recuperação
+ Toque e segure as palavras editadas para revelar sua frase de recuperação e armazene-a com segurança para proteger seu ID Session.
+ Guarde sua frase de recuperação em um local seguro
+
+ Caminho
+ O Session oculta seu IP ao enviar suas mensagens através de vários Nós de Serviço na rede descentralizada do Session. Estes são os países pelos quais sua conexão está sendo ricocheteada no momento:
+ Você
+ Nó de Entrada
+ Nó de Serviço
+ Destino
+ Saber mais
+
+ Nova Sessão
+ Digite o ID Session
+ Escanear código QR
+ Escaneie o código QR de um usuário para iniciar uma sessão. Os códigos QR podem ser encontrados tocando no ícone de código QR nas configurações da conta.
+
+ Digite o ID Session do destinatário
+ Os usuários podem compartilhar seus IDs Session acessando as configurações da conta e tocando em Compartilhar ID Session, ou compartilhando o código QR.
+
+ O Session precisa de acesso à câmera para escanear códigos QR
+ Conceder acesso à câmera
+
+ Novo grupo fechado
+ Digite o nome do grupo
+ Grupos fechados suportam até 10 membros e fornecem as mesmas proteções de privacidade que as sessões individuais.
+ Você ainda não possui contatos
+ Iniciar uma sessão
+ Digite um nome de grupo
+ Digite um nome de grupo mais curto
+ Escolha pelo menos 2 membros do grupo
+ Um grupo fechado não pode ter mais de 10 membros
+ Um dos membros do seu grupo tem um ID Session inválido
+
+ Participar em grupo aberto
+ Não foi possível entrar no grupo
+ URL do grupo aberto
+ Escanear código QR
+ Escaneie o código QR do grupo aberto no qual você deseja entrar
+
+ Digite a URL do grupo aberto
+
+ Configurações
+ Digite um nome de exibição
+ Escolha um nome de exibição
+ Escolha um nome de exibição que contenha apenas caracteres az, AZ, 0-9 e _
+ Escolha um nome de exibição mais curto
+ Privacidade
+ Notificações
+ Bate-papos
+ Dispositivos
+ Frase de recuperação
+ Apagar os dados
+
+ Notificações
+ Estilo de notificação
+ Conteúdo da notificação
+
+ Privacidade
+
+ Bate-papos
+
+ Dispositivos
+ Limite de dispositivos atingido
+ No momento, não é permitido sincronizar mais de um dispositivo.
+ Não foi possível dessincronizar o dispositivo.
+ O seu dispositivo foi dessincronizado com sucesso
+ Não foi possível sincronizar o dispositivo.
+ Você ainda não sincronizou nenhum dispositivo
+ Sincronizar um dispositivo
+
+ Estratégia de notificação
+
+ Esperando autorização
+ sincronização de dispositivo autorizada
+ Verifique se as palavras abaixo correspondem às mostradas em seu outro dispositivo.
+ Seu dispositivo foi sincronizado com sucesso
+
+ Aguardando dispositivo
+ Solicitação de sincronização recebida
+ Autorizando a sincronização de dispositivo
+ Baixe o Session em seu outro dispositivo e toque em Sincronizar a uma conta existente na parte inferior da tela de início. Se você já possui uma conta em seu outro dispositivo, precisará excluí-la primeiro.
+ Verifique se as palavras abaixo correspondem às mostradas em seu outro dispositivo.
+ Aguarde enquanto a sincronização do dispositivo é criada. Isso pode levar até um minuto.
+ Autorizar
+
+ Mudar o nome
+ Dessincronizar dispositivo
+
+ Insira o nome
+
+ Sua frase de recuperação
+ Esta é sua frase de recuperação. Com ela, você pode restaurar ou migrar seu ID Session para um novo dispositivo.
+
+ Limpar todos os dados
+ Isso excluirá permanentemente suas mensagens, sessões e contatos.
+
+ Código QR
+ Ver meu código QR
+ Escanear código QR
+ Escaneie o código QR de alguém para iniciar uma conversa com essa pessoa
+
+ Este é o seu código QR. Outros usuários podem escaneá-lo para iniciar uma sessão com você.
+ Compartilhar código QR
+
+ Deseja restaurar sua sessão com %s?
+ Dispensar
+ Restaurar
+
+ Contatos
+ Grupos fechados
+ Grupos abertos
+
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 6518e28c52..54e0ec72e5 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -1353,5 +1353,185 @@
КОСНИТЕСЬ, ЧТОБЫ РАЗБЛОКИРОВАТЬ
Напоминание:
О Session
-
+
+
+
+
+
+
+ Продолжить
+ Копировать
+ Неверная ссылка
+ Скопировано в буфер обмена
+ Не удалось привязать устройство.
+ Далее
+ Поделиться
+ Неверный Session ID
+ Отмена
+ Ваш Session ID
+
+ Здесь начинается Session...
+ Создать Session ID
+ Восстановить Session ID
+ Привязать к существующему аккаунту
+ Ваше устройство успешно отвязано
+
+ Что такое Session?
+ Это децентрализованное, зашифрованное приложение для обмена сообщениями
+ Значит ли это, что оно не собирает мою личную информацию или метаданные моего разговора? Как оно работает?
+ С использованием комбинации передовых технологий анонимной маршрутизации и сквозного шифрования.
+ Друзья не позволят друзьям использовать ненадежные мессенджеры. Пользуйтесь на здоровье.
+
+ Познакомьтесь со своим Session ID
+ Ваш Session ID - это уникальный адрес, который могут использовать другие люди для связи с вами при помощи Session. Поскольку ваш Session ID никак не связан с вашей настоящей личностью, он по определению является полностью анонимным и конфиденциальным.
+ Скопировано в буфер обмена
+
+ Восстановите свой аккаунт
+ Для восстановления учетной записи введите секретную фразу, которая была предоставлена вам при регистрации.
+ Введите секретную фразу
+
+ Привязать устройство
+ Введите Session ID
+ Сканировать QR-код
+ Перейдите в «Настройки» > «Устройства»> «Привязать устройство» на другом устройстве, а затем отсканируйте появившийся QR-код, чтобы начать процесс привязки.
+
+ Привяжите свое устройство
+ Перейдите в «Настройки» > «Устройства» > «Привязать устройство» на другом устройстве и введите сюда свой Session ID, чтобы начать процесс привязки.
+ Введите свой Session ID
+
+ Выберите ваше отображаемое имя
+ Это имя будет отображаться, когда вы используете Session. Это может быть ваше настоящее имя, псевдоним или что угодно по вашему выбору.
+ Введите отображаемое имя
+ Пожалуйста, выберите отображаемое имя
+ Пожалуйста, выберите отображаемое имя состоящее только из символов a-z, A-Z, 0-9 и _
+ Пожалуйста, выберите более короткое отображаемое имя
+
+ Рекомендуется
+ Пожалуйста, выберите метод
+
+ У вас еще нет контактов
+ Начать Сессию
+ Вы уверены, что хотите покинуть эту группу?
+ Не удалось покинуть группу
+ Вы уверены, что хотите удалить этот разговор?
+ Разговор удален
+
+ Ваша секретная фраза для восстановления
+ А вот и ваша секретная фраза для восстановления
+ Ваша секретная фраза является главным ключом к вашему Session ID. Вы можете использовать ее для восстановления Session ID, если потеряете доступ к своему устройству. Сохраните свою секретную фразу в безопасном месте, и никому её не передавайте.
+ Удерживайте, чтобы показать
+
+ Защитите свой аккаунт, сохранив секретную фразу
+ Нажмите и удерживайте сокращенные слова, чтобы открыть секретную фразу, а затем сохраните ее в надежном месте, чтобы защитить свой Session ID.
+ Обязательно сохраните секретную фразу в надежном месте.
+
+ Маршрут
+ Session скрывает ваш IP, перенаправляя ваши сообщения через несколько сервисных узлов своей децентрализованной сети. Вот страны, через которые в данный момент проходит ваш сеанс связи:
+ Вы
+ Узел входа
+ Служебный узел
+ Место назначения
+ Узнать больше
+
+ Новый Диалог
+ Введите Session ID
+ Сканировать QR-код
+ Сканируйте QR-код пользователя, чтобы начать сессию. QR-коды можно найти, нажав значок QR-кода в настройках учетной записи.
+
+ Введите Session ID получателя
+ Пользователи могут поделиться своим Session ID, зайдя в настройки своей учетной записи и нажав «Отправить Session ID», или поделившись своим QR-кодом.
+
+ Session нужен доступ к камере для сканирования QR-кодов
+ Предоставить доступ к камере
+
+ Новая закрытая группа
+ Введите название группы
+ Закрытые группы поддерживают до 10 участников и обеспечивают те же меры защиты конфиденциальности, что и сессии один-на-один.
+ У вас еще нет контактов
+ Начать Сессию
+ Пожалуйста, введите название группы
+ Пожалуйста, введите более короткое имя группы
+ Пожалуйста, выберите как минимум 2 участников группы
+ В закрытой группе не может быть больше 10 участников
+ Один из участников вашей группы имеет недопустимый Session ID
+
+ Присоединиться к открытой группе
+ Не удалось присоединиться к группе
+ URL открытой группы
+ Сканировать QR-код
+ Отсканируйте QR-код открытой группы, в которую вы хотите вступить
+
+ Введите URL открытой группы
+
+ Настройки
+ Введите отображаемое имя
+ Пожалуйста, выберите отображаемое имя
+ Пожалуйста, выберите отображаемое имя состоящее только из символов a-z, A-Z, 0-9 и _
+ Пожалуйста, выберите более короткое отображаемое имя
+ Конфиденциальность
+ Уведомления
+ Чаты
+ Устройства
+ Секретная фраза
+ Очистить данные
+
+ Уведомления
+ Стиль уведомлений
+ Содержание уведомления
+
+ Конфиденциальность
+
+ Чаты
+
+ Устройства
+ Достигнуто предельное кол-во устройств
+ В настоящее время запрещено привязывать более одного устройства.
+ Не удалось отвязать устройство.
+ Ваше устройство успешно отвязано
+ Не удалось привязать устройство.
+ Вы еще не привязали ни одного устройства
+ Привязать устройство
+
+ Метод уведомлений
+
+ Ожидание авторизации
+ Привязка устройства авторизована
+ Пожалуйста, убедитесь, что слова ниже соответствуют тем, которые показаны на вашем другом устройстве.
+ Ваше устройство успешно привязано
+
+ Ожидание устройства
+ Запрос на привязывание получен
+ Ссылка на авторизирующее устройство
+ Загрузите Session на другое устройство и нажмите «Привязать к существующей учетной записи» в нижней части целевого экрана. Если у вас уже есть учетная запись на другом устройстве, вам придется сначала удалить ту учетную запись.
+ Пожалуйста, убедитесь, что слова ниже соответствуют тем, которые показаны на вашем другом устройстве.
+ Пожалуйста, подождите, пока будет создана ссылка на устройство. Это может занять до минуты.
+ Авторизация
+
+ Сменить имя
+ Отключить устройство
+
+ Введите имя
+
+ Ваша секретная фраза
+ Это ваша секретная фраза. С ее помощью вы можете восстановить или перенести свой Session ID на новое устройство.
+
+ Очистить все данные
+ Это навсегда удалит ваши сообщения, сессии и контакты.
+
+ QR-код
+ Посмотреть мой QR-код
+ Сканировать QR-код
+ Отсканируйте QR-код другого человека, чтобы начать с ним разговор
+
+ Это ваш QR-код. Другие пользователи могут сканировать его, чтобы начать диалог с вами.
+ Поделиться QR-кодом
+
+ Хотите восстановить сессию с %s?
+ Отклонить
+ Восстановить
+
+ Контакты
+ Закрытые группы
+ Открытые группы
+
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 5cc487291c..9ae1019ebd 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -1266,5 +1266,185 @@
轻触解锁
提醒:
关于
-
+
+
+
+
+
+
+ 继续
+ 复制
+ 无效的网址
+ 复制到剪贴板
+ 无法链接设备。
+ 下一步
+ 分享
+ 无效的Session ID
+ 取消
+ 您的Session ID
+
+ 您的Session从这里开始...
+ 注册Session ID
+ 继续使用您的Session ID
+ 链接到现有帐号
+ 您的设备已成功断开链接
+
+ 什么是Session?
+ Session是一个去中心化的加密消息应用。
+ 所以Session不会收集我的个人信息或对话原始数据?怎么做到的?。
+ 通过结合高效的匿名路由和端到端的加密技术。
+ 好朋友就要与朋友使用能够保证信息安全的聊天工具,不用谢啦
+
+ 向您的新Session ID打个招呼吧
+ Session ID是其他用户需要与您聊天时使用的独一无二的地址。与您的真实身份无关,Session ID的设计是完全是匿名和私有的。
+ 复制到剪贴板
+
+ 恢复您的帐号
+ 在您重新登陆并需要恢复账户时,请输入您注册帐号时的恢复口令。
+ 输入您的恢复口令
+
+ 链接设备
+ 输入Session ID
+ 扫描二维码
+ 在您的设备上导航到“设置”>“设备”>“链接设备”,然后扫描出现的二维码以开始链接过程。
+
+ 链接您的设备
+ 在您的另一个设备上导航到“设置”>“设备” >“链接设备”,然后在此处输入Session ID以开始链接过程。
+ 输入Session ID
+
+ 选择您的显示名称
+ 使用Session时,这就是您的名字。它可以是您的真实姓名,别名或您喜欢的其他任何名称。
+ 输入显示名称
+ 请选择一个显示名称
+ 请选择一个仅包含 az,AZ,0-9 和_字符的显示名称
+ 请选择一个较短的显示名称
+
+ 推荐的选项
+ 请选择一个选项
+
+ 您还没有任何联系人
+ 开始对话
+ 您确定要离开这个群组吗?
+ 无法离开群组
+ 您确定要删除此对话吗?
+ 对话已删除
+
+ 您的恢复口令
+ 这里是您的恢复口令
+ 您的恢复口令是Session ID的主密钥 - 如果您无法访问您的现有设备,则可以使用它在其他设备上恢复Session ID。将您的恢复口令存储在安全的地方,不要将其提供给任何人。
+ 长按显示内容
+
+ 保存恢复短语以保护您的帐号安全
+ 点击并按住遮盖住的单词以显示您的恢复短语,然后安全地存储它以保护Session ID。
+ 确保将恢复短语存储在安全的地方
+
+ 路径
+ Session会通过Session的分散网络中的多个服务节点跳转消息以隐藏IP。以下是国家您目前的消息连接跳转服务节点所在地:
+ 您
+ 入口节点
+ 服务节点
+ 目的地
+ 了解更多
+
+ 新建私人聊天
+ 输入Session ID
+ 扫描二维码
+ 扫描另一用户的二维码以开始使用Session。您可以在帐号设置中点击二维码图标找到二维码。
+
+ 输入对方的Session ID
+ 用户可以通过进入帐号设置并点击共享Session ID来分享自己的Session ID,或通过共享其二维码来分享其Session ID。
+
+ Session需要摄像头访问权限才能扫描二维码
+ 授予摄像头访问权限
+
+ 创建私密群组
+ 输入群组名称
+ 私密群组最多支持 10 位成员,并提供与一对一对话相同的隐私保护。
+ 您还没有任何联系人
+ 开始对话
+ 请输入群组名称
+ 请输入较短的群组名称
+ 请选择至少 2 位小组成员
+ 私密群组成员不得超过 10 个
+ 您群组中的一位成员的Session ID无效
+
+ 加入公开群组
+ 无法加入群组
+ 公开群组网址
+ 扫描二维码
+ 扫描您想加入的公开群组的二维码
+
+ 输入一个公开群组网址
+
+ 设置
+ 输入显示的名称
+ 请选择一个显示名称
+ 请选择一个仅包含 az,AZ,0-9 和 _ 字符的显示名称
+ 请选择一个较短的显示名称
+ 隐私
+ 通知
+ 聊天
+ 设备
+ 恢复口令
+ 清除数据
+
+ 通知
+ 通知风格类型
+ 通知内容
+
+ 隐私
+
+ 聊天
+
+ 设备
+ 达到设备限制
+ 当前不允许链接多个设备。
+ 无法断开链接设备。
+ 您的设备已成功断开链接
+ 无法链接设备。
+ 您尚未链接任何设备
+ 链接设备(测试版)
+
+ 通知选项
+
+ 等待授权
+ 设备链接授权
+ 请检查以下单词是否与您其他设备上显示的单词匹配。
+ 您的设备已成功链接
+
+ 等待设备
+ 收到链接请求
+ 授权设备链接
+ 在其他设备上下载Session,然后点击登陆页面屏幕底部的“链接到现有帐号”。如果您的其他设备上已有一个帐号,则必须先删除已有帐号。
+ 请检查以下单词是否与您其他设备上显示的单词匹配。
+ 创建设备关联时,请耐心等待。这可能需要一分钟的时间。
+ 授权
+
+ 更换名字
+ 断开设备链接
+
+ 输入名字
+
+ 您的恢复口令
+ 这是您的恢复口令。有了它,您可以将Session ID还原或迁移到新设备上。
+
+ 清除所有数据
+ 这将永久删除您的消息、对话和联系人。
+
+ 二维码
+ 查看我的二维码
+ 扫描二维码
+ 扫描对方的二维码,与他们开始对话
+
+ 这是您的二维码。其他用户可以对其进行扫描以开始对话。
+ 分享二维码
+
+ 您要恢复与%s的对话吗?
+ 解散
+ 恢复
+
+ 联系人
+ 私密群组
+ 公开群组
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e698ce54ee..d2ca522451 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1655,7 +1655,6 @@
-
Continue
Copy
Invalid URL
@@ -1703,12 +1702,6 @@
Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters
Please pick a shorter display name
- Push Notifications
- There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose.
- Firebase Cloud Messaging
- Session will use the Firebase Cloud Messaging service to receive push notifications. You\'ll be notified of new messages reliably and immediately. Using FCM means that your IP address and device token will be exposed to Google. If you use push notifications for other apps, this will already be the case. Your IP address and device token will also be exposed to Loki, but your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.
- Background Polling
- Session will occasionally check for new messages in the background. This guarantees full metadata protection, but message notifications may be significantly delayed.
Recommended
Please Pick an Option
@@ -1719,17 +1712,6 @@
Are you sure you want to delete this conversation?
Conversation deleted
- Push Notifications
- Session now features two ways to handle push notifications. Make sure to read the descriptions carefully before you choose.
- Firebase Cloud Messaging
- Session will use the Firebase Cloud Messaging service to receive push notifications. You\'ll be notified of new messages reliably and immediately. Using FCM means that your IP address and device token will be exposed to Google. If you use push notifications for other apps, this will already be the case. Your IP address and device token will also be exposed to Loki, but your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.
- Background Polling
- Session will occasionally check for new messages in the background. This guarantees full metadata protection, but message notifications may be significantly delayed.
- Recommended
- Please Pick an Option
- Confirm
- Skip
-
Your Recovery Phrase
Meet your recovery phrase
Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\’t give it to anyone.
@@ -1795,7 +1777,6 @@
Scan the QR code of the open group you\'d like to join
Enter an open group URL
- Open groups can be joined by anyone and do not provide full privacy protection
Settings
Enter a display name
@@ -1827,8 +1808,6 @@
Link a Device (Beta)
Notification Strategy
- Use FCM
- Using Firebase Cloud Messaging allows for more reliable push notifications, but exposes your IP and device token to Google and Loki.
Waiting for Authorization
Device Link Authorized
@@ -1862,16 +1841,6 @@
This is your QR code. Other users can scan it to start a session with you.
Share QR Code
- Accept
- Decline
- %1$s sent you a session request
- You\'ve accepted %1$s\'s session request
- You\'ve declined %1$s\'s session request
- %1$s\'s session request has expired
- You\'ve sent %1$s a session request
- %1$s accepted your session request
- Your session request to %1$s has expired
-
Would you like to restore your session with %s?
Dismiss
Restore
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 35f4706d93..3355423f77 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -139,6 +139,7 @@
- @dimen/large_font_size
- @color/text
- @font/space_mono_regular
+ - viewStart
@@ -162,6 +164,7 @@
- @dimen/small_font_size
- @color/text
- @drawable/session_edit_text_cursor
+ - viewStart
- 1
diff --git a/res/xml/preferences_notifications.xml b/res/xml/preferences_notifications.xml
index 9a9c918179..a1b33553ac 100644
--- a/res/xml/preferences_notifications.xml
+++ b/res/xml/preferences_notifications.xml
@@ -26,8 +26,8 @@
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
index 3121f02dd5..ae30fce7b2 100644
--- a/src/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
+import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
@@ -60,17 +61,18 @@ import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
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.PublicChatManager;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
-import org.thoughtcrime.securesms.loki.protocol.EphemeralMessage;
-import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
-import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob;
+import org.thoughtcrime.securesms.loki.protocol.PushSessionRequestMessageSendJob;
+import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
+import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
+import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
@@ -89,27 +91,29 @@ import org.webrtc.PeerConnectionFactory;
import org.webrtc.PeerConnectionFactory.InitializationOptions;
import org.webrtc.voiceengine.WebRtcAudioManager;
import org.webrtc.voiceengine.WebRtcAudioUtils;
+import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
-import org.whispersystems.signalservice.loki.api.LokiAPI;
-import org.whispersystems.signalservice.loki.api.LokiPoller;
-import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement;
-import org.whispersystems.signalservice.loki.api.LokiSwarmAPI;
-import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
-import org.whispersystems.signalservice.loki.api.p2p.LokiP2PAPI;
-import org.whispersystems.signalservice.loki.api.p2p.LokiP2PAPIDelegate;
+import org.whispersystems.signalservice.loki.api.Poller;
+import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement;
+import org.whispersystems.signalservice.loki.api.SnodeAPI;
+import org.whispersystems.signalservice.loki.api.SwarmAPI;
+import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI;
+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.LokiP2PAPIDelegate;
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.meta.SessionMetaProtocol;
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink;
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
+import org.whispersystems.signalservice.loki.protocol.meta.TTLUtilities;
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink;
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol;
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocolDelegate;
-import org.whispersystems.signalservice.loki.protocol.syncmessages.SyncMessagesProtocol;
+import org.whispersystems.signalservice.loki.protocol.shelved.syncmessages.SyncMessagesProtocol;
import java.io.File;
import java.io.FileInputStream;
@@ -148,9 +152,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private PersistentLogger persistentLogger;
// Loki
- public LokiPoller lokiPoller = null;
- public LokiPublicChatManager lokiPublicChatManager = null;
- private LokiPublicChatAPI lokiPublicChatAPI = null;
+ public MessageNotifier messageNotifier = null;
+ public Poller poller = null;
+ public PublicChatManager publicChatManager = null;
+ private PublicChatAPI publicChatAPI = null;
public Broadcaster broadcaster = null;
public SignalCommunicationModule communicationModule;
@@ -173,16 +178,16 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
// Loki
// ========
+ messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
broadcaster = new Broadcaster(this);
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
- LokiSessionResetImplementation sessionResetImpl = new LokiSessionResetImplementation(this);
+ SessionResetImplementation sessionResetImpl = new SessionResetImplementation(this);
if (userPublicKey != null) {
- LokiSwarmAPI.Companion.configureIfNeeded(apiDB);
- LokiAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
- FriendRequestProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
+ SwarmAPI.Companion.configureIfNeeded(apiDB);
+ SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
SessionMetaProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
SyncMessagesProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
@@ -190,15 +195,15 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
MultiDeviceProtocol.Companion.configureIfNeeded(apiDB);
SessionManagementProtocol.Companion.configureIfNeeded(sessionResetImpl, threadDB, this);
setUpP2PAPIIfNeeded();
- LokiPushNotificationAcknowledgement.Companion.configureIfNeeded(BuildConfig.DEBUG);
+ PushNotificationAcknowledgement.Companion.configureIfNeeded(BuildConfig.DEBUG);
if (setUpStorageAPIIfNeeded()) {
if (userPublicKey != null) {
Set deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey);
- LokiFileServerAPI.shared.setDeviceLinks(deviceLinks);
+ FileServerAPI.shared.setDeviceLinks(deviceLinks);
}
}
resubmitProfilePictureIfNeeded();
- lokiPublicChatManager = new LokiPublicChatManager(this);
+ publicChatManager = new PublicChatManager(this);
updateOpenGroupProfilePicturesIfNeeded();
registerForFCMIfNeeded(false);
// ========
@@ -222,8 +227,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
executePendingContactSync();
KeyCachingService.onAppForegrounded(this);
// Loki
+ if (poller != null) { poller.setCaughtUp(false); }
startPollingIfNeeded();
- lokiPublicChatManager.startPollersIfNeeded();
+ publicChatManager.markAllAsNotCaughtUp();
+ publicChatManager.startPollersIfNeeded();
}
@Override
@@ -231,10 +238,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
isAppVisible = false;
Log.i(TAG, "App is no longer visible.");
KeyCachingService.onAppBackgrounded(this);
- MessageNotifier.setVisibleThread(-1);
+ messageNotifier.setVisibleThread(-1);
// Loki
- if (lokiPoller != null) { lokiPoller.stopIfNeeded(); }
- if (lokiPublicChatManager != null) { lokiPublicChatManager.stopPollers(); }
+ if (poller != null) { poller.stopIfNeeded(); }
+ if (publicChatManager != null) { publicChatManager.stopPollers(); }
}
@Override
@@ -275,15 +282,15 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
// Loki
- public @Nullable LokiPublicChatAPI getLokiPublicChatAPI() {
- if (lokiPublicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) { return lokiPublicChatAPI; }
+ public @Nullable PublicChatAPI getPublicChatAPI() {
+ if (publicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) { return publicChatAPI; }
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
- if (userPublicKey== null) { return lokiPublicChatAPI; }
+ if (userPublicKey== null) { return publicChatAPI; }
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
- lokiPublicChatAPI = new LokiPublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB);
- return lokiPublicChatAPI;
+ publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB);
+ return publicChatAPI;
}
private void initializeSecurityProvider() {
@@ -437,8 +444,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base)));
}
- private static class ProviderInitializationException extends RuntimeException {
- }
+ private static class ProviderInitializationException extends RuntimeException { }
// region Loki
public boolean setUpStorageAPIIfNeeded() {
@@ -447,7 +453,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
boolean isDebugMode = BuildConfig.DEBUG;
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this);
- LokiFileServerAPI.Companion.configure(isDebugMode, userPublicKey, userPrivateKey, apiDB);
+ FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB);
return true;
}
@@ -484,14 +490,18 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
private void setUpPollingIfNeeded() {
- if (lokiPoller != null) return;
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return;
+ if (poller != null) {
+ SnodeAPI.shared.setUserPublicKey(userPublicKey);
+ poller.setUserPublicKey(userPublicKey);
+ return;
+ }
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
Context context = this;
- LokiSwarmAPI.Companion.configureIfNeeded(apiDB);
- LokiAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
- lokiPoller = new LokiPoller(userPublicKey, apiDB, protos -> {
+ SwarmAPI.Companion.configureIfNeeded(apiDB);
+ SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
+ poller = new Poller(userPublicKey, apiDB, protos -> {
for (SignalServiceProtos.Envelope proto : protos) {
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto), false);
}
@@ -501,7 +511,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
public void startPollingIfNeeded() {
setUpPollingIfNeeded();
- if (lokiPoller != null) { lokiPoller.startIfNeeded(); }
+ if (poller != null) { poller.startIfNeeded(); }
+ }
+
+ public void stopPolling() {
+ if (poller == null) { return; }
+ poller.stopIfNeeded();
}
private void resubmitProfilePictureIfNeeded() {
@@ -516,7 +531,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
try {
File profilePicture = AvatarHelper.getAvatarFile(this, Address.fromSerialized(userPublicKey));
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.setProfileAvatarId(this, new SecureRandom().nextInt());
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey);
@@ -530,9 +545,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
public void updateOpenGroupProfilePicturesIfNeeded() {
AsyncTask.execute(() -> {
- LokiPublicChatAPI publicChatAPI = null;
+ PublicChatAPI publicChatAPI = null;
try {
- publicChatAPI = getLokiPublicChatAPI();
+ publicChatAPI = getPublicChatAPI();
} catch (Exception e) {
// Do nothing
}
@@ -555,6 +570,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
public void clearData() {
+ String token = TextSecurePreferences.getFCMToken(this);
+ if (token != null && !token.isEmpty()) {
+ LokiPushNotificationManager.unregister(token, this);
+ }
boolean wasUnlinked = TextSecurePreferences.getWasUnlinked(this);
TextSecurePreferences.clearAll(this);
TextSecurePreferences.setWasUnlinked(this, wasUnlinked);
@@ -571,11 +590,39 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
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
- public void sendSessionRequest(@NotNull String publicKey) {
- DatabaseFactory.getLokiAPIDatabase(this).setSessionRequestTimestamp(publicKey, new Date().getTime());
- EphemeralMessage sessionRequest = EphemeralMessage.createSessionRequest(publicKey);
- jobManager.add(new PushEphemeralMessageSendJob(sessionRequest));
+ public void sendSessionRequestIfNeeded(@NotNull String publicKey) {
+ // It's never necessary to establish a session with self
+ String userPublicKey = TextSecurePreferences.getLocalNumber(this);
+ if (publicKey.equals(userPublicKey)) { return; }
+ // Check that we don't already have a session
+ SignalProtocolAddress address = new SignalProtocolAddress(publicKey, SignalServiceAddress.DEFAULT_DEVICE_ID);
+ boolean hasSession = new TextSecureSessionStore(this).containsSession(address);
+ if (hasSession) { return; }
+ // Check that we didn't already send a session request
+ LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
+ boolean hasSentSessionRequest = (apiDB.getSessionRequestSentTimestamp(publicKey) != null);
+ boolean hasSentSessionRequestExpired = hasSentSessionRequestExpired(publicKey);
+ if (hasSentSessionRequestExpired) {
+ apiDB.setSessionRequestSentTimestamp(publicKey, 0);
+ }
+ if (hasSentSessionRequest && !hasSentSessionRequestExpired) { return; }
+ // Send the session request
+ long timestamp = new Date().getTime();
+ apiDB.setSessionRequestSentTimestamp(publicKey, timestamp);
+ PushSessionRequestMessageSendJob job = new PushSessionRequestMessageSendJob(publicKey, timestamp);
+ jobManager.add(job);
}
// endregion
}
diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java
index 49fc125778..2d9828705d 100644
--- a/src/org/thoughtcrime/securesms/ConversationListActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java
@@ -298,7 +298,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
Context context = ConversationListActivity.this;
List messageIds = DatabaseFactory.getThreadDatabase(context).setAllThreadsRead();
- MessageNotifier.updateNotification(context);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
MarkReadReceiver.process(context, messageIds);
return null;
diff --git a/src/org/thoughtcrime/securesms/ConversationListFragment.java b/src/org/thoughtcrime/securesms/ConversationListFragment.java
index d96d9860b7..27fb781e5a 100644
--- a/src/org/thoughtcrime/securesms/ConversationListFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationListFragment.java
@@ -324,8 +324,9 @@ public class ConversationListFragment extends Fragment
@Override
protected Void doInBackground(Void... params) {
- DatabaseFactory.getThreadDatabase(getActivity()).deleteConversations(selectedConversations);
- MessageNotifier.updateNotification(getActivity());
+ Context context = getActivity();
+ DatabaseFactory.getThreadDatabase(context).deleteConversations(selectedConversations);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
return null;
}
@@ -542,9 +543,10 @@ public class ConversationListFragment extends Fragment
DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
if (unreadCount > 0) {
- List messageIds = DatabaseFactory.getThreadDatabase(getActivity()).setRead(threadId, false);
- MessageNotifier.updateNotification(getActivity());
- MarkReadReceiver.process(getActivity(), messageIds);
+ Context context = getActivity();
+ List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, false);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
+ MarkReadReceiver.process(context, messageIds);
}
}
@@ -553,8 +555,9 @@ public class ConversationListFragment extends Fragment
DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
if (unreadCount > 0) {
- DatabaseFactory.getThreadDatabase(getActivity()).incrementUnread(threadId, unreadCount);
- MessageNotifier.updateNotification(getActivity());
+ Context context = getActivity();
+ DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, unreadCount);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId);
diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java
index 62eb077481..e41694bae8 100644
--- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java
+++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java
@@ -58,8 +58,8 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
-import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
+import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI;
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -386,7 +386,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
Context context = CreateProfileActivity.this;
TextSecurePreferences.setProfileName(context, name);
- LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(context).getLokiPublicChatAPI();
+ PublicChatAPI publicChatAPI = ApplicationContext.getInstance(context).getPublicChatAPI();
if (publicChatAPI != null) {
Set servers = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChatServers();
for (String server : servers) {
@@ -409,7 +409,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
// Loki - Upload the profile photo here
if (avatar != null) {
Log.d("Loki", "Start uploading profile photo");
- LokiFileServerAPI storageAPI = LokiFileServerAPI.shared;
+ FileServerAPI storageAPI = FileServerAPI.shared;
LokiDotNetAPI.UploadResult result = storageAPI.uploadProfilePicture(storageAPI.getServer(), profileKey, avatar, () -> {
TextSecurePreferences.setLastProfilePictureUpload(CreateProfileActivity.this, new Date().getTime());
return Unit.INSTANCE;
diff --git a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
index ad9bfcb85e..1e6f34a855 100644
--- a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
+++ b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
@@ -176,7 +176,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
- MessageNotifier.updateNotification(context);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
index 17bba3300c..7760a07cf1 100644
--- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java
+++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
@@ -322,7 +322,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override
public void onClick(View v) {
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);
}
}
diff --git a/src/org/thoughtcrime/securesms/InviteActivity.java b/src/org/thoughtcrime/securesms/InviteActivity.java
index 4fbb2bd052..cf4bd2fdd8 100644
--- a/src/org/thoughtcrime/securesms/InviteActivity.java
+++ b/src/org/thoughtcrime/securesms/InviteActivity.java
@@ -53,7 +53,7 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity {
@Override
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.REFRESHABLE, false);
diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
index 147299bbe0..818a3d6669 100644
--- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
+++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
@@ -133,13 +133,13 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
assert getSupportActionBar() != null;
getSupportActionBar().setTitle("Message Details");
- MessageNotifier.setVisibleThread(threadId);
+ ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId);
}
@Override
protected void onPause() {
super.onPause();
- MessageNotifier.setVisibleThread(-1L);
+ ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1L);
}
@Override
diff --git a/src/org/thoughtcrime/securesms/components/QuoteView.java b/src/org/thoughtcrime/securesms/components/QuoteView.java
index 93e06f6dd7..43c9e8bf7c 100644
--- a/src/org/thoughtcrime/securesms/components/QuoteView.java
+++ b/src/org/thoughtcrime/securesms/components/QuoteView.java
@@ -31,7 +31,7 @@ import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil;
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;
@@ -197,7 +197,7 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener
long threadID = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(conversationRecipient);
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()))) {
quoteeDisplayName = TextSecurePreferences.getProfileName(getContext());
} else if (publicChat != null) {
diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java
index dc510073fc..0804d598a5 100644
--- a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java
+++ b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Util;
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import java.util.HashMap;
import java.util.Map;
@@ -84,8 +84,9 @@ public class TypingStatusSender {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Recipient recipient = threadDatabase.getRecipientForThreadId(threadId);
// 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
+ if (recipient == null) { return; }
Set linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.getAddress().serialize());
for (String device : linkedDevices) {
Recipient deviceAsRecipient = Recipient.from(context, Address.fromSerialized(device), false);
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index bc98f2264f..f11fe4383c 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -68,7 +68,6 @@ import android.view.View.OnFocusChangeListener;
import android.view.View.OnKeyListener;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -81,7 +80,6 @@ import com.annimon.stream.Stream;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
-import org.jetbrains.annotations.NotNull;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.ExpirationDialog;
import org.thoughtcrime.securesms.GroupCreateActivity;
@@ -160,10 +158,8 @@ import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
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.utilities.MentionManagerUtilities;
-import org.thoughtcrime.securesms.loki.views.FriendRequestViewDelegate;
import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView;
import org.thoughtcrime.securesms.loki.views.SessionRestoreBannerView;
import org.thoughtcrime.securesms.mediasend.Media;
@@ -188,7 +184,6 @@ import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.mms.TextSlide;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
-import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
@@ -228,12 +223,11 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.thoughtcrime.securesms.util.views.Stub;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
-import org.whispersystems.signalservice.loki.api.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.MentionsManager;
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
-import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus;
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation;
import java.io.IOException;
@@ -274,8 +268,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
ComposeText.CursorPositionChangedListener,
ConversationSearchBottomBar.EventListener,
StickerKeyboardProvider.StickerEventListener,
- LokiThreadDatabaseDelegate,
- FriendRequestViewDelegate
+ LokiThreadDatabaseDelegate
{
private static final String TAG = ConversationActivity.class.getSimpleName();
@@ -357,14 +350,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
// Message status bar
private ArrayList broadcastReceivers = new ArrayList<>();
- private String messageStatus = null;
+ private String messageStatus = null;
// Mentions
- private View mentionCandidateSelectionViewContainer;
+ private View mentionCandidateSelectionViewContainer;
private MentionCandidateSelectionView mentionCandidateSelectionView;
- private int currentMentionStartIndex = -1;
- private ArrayList mentions = new ArrayList<>();
- private String oldText = "";
+ private int currentMentionStartIndex = -1;
+ private ArrayList mentions = new ArrayList<>();
+ private String oldText = "";
// Restoration
protected SessionRestoreBannerView sessionRestoreBannerView;
@@ -388,7 +381,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
getWindow().getDecorView().setBackgroundColor(color);
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale());
- fragment.friendRequestViewDelegate = this;
registerMessageStatusObserver("calculatingPoW");
registerMessageStatusObserver("contactingNetwork");
@@ -439,7 +431,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
mentionCandidateSelectionView.setOnMentionCandidateSelected( mentionCandidate -> {
mentions.add(mentionCandidate);
String oldText = composeText.getText().toString();
- String newText = oldText.substring(0, currentMentionStartIndex) + "@" + mentionCandidate.getDisplayName();
+ String newText = oldText.substring(0, currentMentionStartIndex) + "@" + mentionCandidate.getDisplayName() + " ";
composeText.setText(newText);
composeText.setSelection(newText.length());
currentMentionStartIndex = -1;
@@ -466,9 +458,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this);
- LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
+ PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
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();
return Unit.INSTANCE;
});
@@ -551,12 +543,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
setGroupShareProfileReminder(recipient);
calculateCharactersRemaining();
- MessageNotifier.setVisibleThread(threadId);
+ ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId);
markThreadAsRead();
DatabaseFactory.getLokiThreadDatabase(this).setDelegate(this);
- updateInputPanel();
+ inputPanel.setHint("Message");
updateSessionRestoreBanner();
@@ -566,7 +558,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onPause() {
super.onPause();
- MessageNotifier.setVisibleThread(-1L);
+ ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1L);
if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right);
inputPanel.onPause();
@@ -762,10 +754,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
if (isSingleConversation()) {
- /*
- if (isSecureText) inflater.inflate(R.menu.conversation_callable_secure, menu);
- else inflater.inflate(R.menu.conversation_callable_insecure, menu);
- */
+ if (recipient.isBlocked()) {
+ inflater.inflate(R.menu.conversation_unblock, menu);
+ } else {
+ inflater.inflate(R.menu.conversation_block, menu);
+ }
} else if (isGroupConversation() && !isOpenGroupOrRSSFeed) {
inflater.inflate(R.menu.conversation_group_options, menu);
@@ -869,8 +862,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
- 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_secure: handleDial(getRecipient(), true); 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_add_shortcut: handleAddShortcut(); return true;
case R.id.menu_search: handleSearch(); return true;
@@ -995,12 +990,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
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;
- 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)
.setTitle(titleRes)
.setMessage(bodyRes)
@@ -1079,6 +1068,33 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
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() {
+ @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() {
Intent intent = new Intent(this, MediaOverviewActivity.class);
intent.putExtra(MediaOverviewActivity.ADDRESS_EXTRA, recipient.getAddress());
@@ -1323,6 +1339,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
this.isDefaultSms = isDefaultSms;
this.isSecurityInitialized = true;
+ if (recipient == null || attachmentManager == null) { return; }
+
boolean isMediaMessage = recipient.isMmsGroupRecipient() || attachmentManager.isAttachmentPresent();
sendButton.resetAvailableTransports(isMediaMessage);
@@ -1711,7 +1729,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void initializeResources() {
if (recipient != null) recipient.removeListener(this);
- recipient = Recipient.from(this, getIntent().getParcelableExtra(ADDRESS_EXTRA), true);
+ Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA);
+ if (address == null) { finish(); return; }
+ recipient = Recipient.from(this, address, true);
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
archived = getIntent().getBooleanExtra(IS_ARCHIVED_EXTRA, false);
distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
@@ -2171,7 +2191,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
try {
int startIndex = result.indexOf("@" + mention.getDisplayName());
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) {
Log.d("Loki", "Couldn't process mention due to error: " + exception.toString() + ".");
}
@@ -2243,7 +2263,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (refreshFragment) {
fragment.reload(recipient, threadId);
- MessageNotifier.setVisibleThread(threadId);
+ ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId);
}
fragment.scrollToBottom();
@@ -2252,26 +2272,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
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 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
public void handleSessionRestoreDevicesChanged(long threadID) {
if (threadID == this.threadId) {
@@ -2279,21 +2279,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() {
if (inputPanel.isRecordingInLockedMode()) {
inputPanel.releaseRecordingLock();
@@ -2395,11 +2380,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
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) {
inputPanel.clearQuote();
attachmentManager.clear(glideRequests, false);
@@ -2408,6 +2388,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
final long id = fragment.stageOutgoingMessage(outgoingMessage);
+ if (!recipient.isGroupRecipient()) {
+ ApplicationContext.getInstance(this).sendSessionRequestIfNeeded(recipient.getAddress().serialize());
+ }
+
new AsyncTask() {
@Override
protected Long doInBackground(Void... param) {
@@ -2448,14 +2432,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
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("");
final long id = fragment.stageOutgoingMessage(message);
+ if (!recipient.isGroupRecipient()) {
+ ApplicationContext.getInstance(this).sendSessionRequestIfNeeded(recipient.getAddress().serialize());
+ }
+
new AsyncTask() {
@Override
protected Long doInBackground(OutgoingTextMessage... messages) {
@@ -2482,13 +2465,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void updateToggleButtonState() {
- if (!FriendRequestProtocol.shouldAttachmentButtonBeEnabled(this, recipient)) {
- buttonToggle.display(sendButton);
- quickAttachmentToggle.hide();
- inlineAttachmentToggle.hide();
- return;
- }
-
if (inputPanel.isRecordingInLockedMode()) {
buttonToggle.display(sendButton);
quickAttachmentToggle.show();
@@ -3106,7 +3082,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
muteIndicatorImageView.setVisibility(View.VISIBLE);
subtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()));
} else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) {
- LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
+ PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null) {
Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer());
if (userCount == null) { userCount = 0; }
@@ -3192,19 +3168,5 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateSubtitleTextView();
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
}
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java
index 9940097011..a26197a8b0 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java
@@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.logging.Log;
-import org.thoughtcrime.securesms.loki.views.FriendRequestViewDelegate;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -108,8 +107,6 @@ public class ConversationAdapter
private MessageRecord recordToPulseHighlight;
private String searchQuery;
- public FriendRequestViewDelegate friendRequestViewDelegate; // Loki
-
protected static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(final @NonNull V itemView) {
super(itemView);
@@ -202,11 +199,7 @@ public class ConversationAdapter
MessageRecord previousRecord = adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1) ? getRecordForPositionOrThrow(adapterPosition + 1) : null;
MessageRecord nextRecord = adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1) ? getRecordForPositionOrThrow(adapterPosition - 1) : null;
- BindableConversationItem conversationItem = viewHolder.getView();
- if (conversationItem instanceof ConversationItem) {
- ((ConversationItem)conversationItem).friendRequestViewDelegate = this.friendRequestViewDelegate;
- }
- conversationItem.bind(messageRecord,
+ viewHolder.getView().bind(messageRecord,
Optional.fromNullable(previousRecord),
Optional.fromNullable(nextRecord),
glideRequests,
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java
index 9d406b88af..abc28fd82c 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java
@@ -79,7 +79,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.logging.Log;
-import org.thoughtcrime.securesms.loki.views.FriendRequestViewDelegate;
import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
import org.thoughtcrime.securesms.mediasend.Media;
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.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.util.guava.Optional;
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat;
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI;
import java.io.IOException;
import java.io.InputStream;
@@ -151,7 +150,6 @@ public class ConversationFragment extends Fragment
private View composeDivider;
private View scrollToBottomButton;
private TextView scrollDateHeader;
- public FriendRequestViewDelegate friendRequestViewDelegate; // Loki
@Override
public void onCreate(Bundle icicle) {
@@ -375,9 +373,7 @@ public class ConversationFragment extends Fragment
}
if (messageRecords.size() > 1) {
-// menu.findItem(R.id.menu_context_forward).setVisible(false);
menu.findItem(R.id.menu_context_reply).setVisible(false);
-// menu.findItem(R.id.menu_context_details).setVisible(false);
menu.findItem(R.id.menu_context_save_attachment).setVisible(false);
menu.findItem(R.id.menu_context_resend).setVisible(false);
} else {
@@ -390,32 +386,29 @@ public class ConversationFragment extends Fragment
((MediaMmsMessageRecord)messageRecord).containsMediaSlide() &&
((MediaMmsMessageRecord)messageRecord).getSlideDeck().getStickerSlide() == null);
- /*
- menu.findItem(R.id.menu_context_forward).setVisible(!actionMessage && !sharedContact);
- menu.findItem(R.id.menu_context_details).setVisible(!actionMessage);
- */
menu.findItem(R.id.menu_context_reply).setVisible(!actionMessage &&
!messageRecord.isPending() &&
!messageRecord.isFailed() &&
messageRecord.isSecure());
}
+
menu.findItem(R.id.menu_context_copy).setVisible(!actionMessage && hasText);
boolean isGroupChat = recipient.isGroupRecipient();
if (isGroupChat) {
- LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
- boolean isPublicChat = publicChat != null;
+ PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
+ boolean isPublicChat = (publicChat != null);
int selectedMessageCount = messageRecords.size();
boolean areAllSentByUser = true;
for (MessageRecord message : messageRecords) {
if (!message.isOutgoing()) { areAllSentByUser = false; }
}
menu.findItem(R.id.menu_context_copy_public_key).setVisible(isPublicChat && selectedMessageCount == 1 && !areAllSentByUser);
- menu.findItem(R.id.menu_context_reply).setVisible(isPublicChat && selectedMessageCount == 1);
+ menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1);
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
- boolean userCanModerate = isPublicChat && LokiPublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
- boolean isDeleteOptionVisible = isPublicChat && (areAllSentByUser || userCanModerate);
+ boolean userCanModerate = isPublicChat && PublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
+ boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate);
menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible);
} else {
menu.findItem(R.id.menu_context_copy_public_key).setVisible(false);
@@ -433,9 +426,7 @@ public class ConversationFragment extends Fragment
private MessageRecord getSelectedMessageRecord() {
Set messageRecords = getListAdapter().getSelectedItems();
-
- if (messageRecords.size() == 1) return messageRecords.iterator().next();
- else throw new AssertionError();
+ return messageRecords.iterator().next();
}
public void reload(Recipient recipient, long threadId) {
@@ -509,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.setCancelable(true);
- LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
+ PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
@Override
@@ -525,7 +516,7 @@ public class ConversationFragment extends Fragment
ArrayList ignoredMessages = new ArrayList<>();
ArrayList failedMessages = new ArrayList<>();
boolean isSentByUser = true;
- LokiPublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getLokiPublicChatAPI();
+ PublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getPublicChatAPI();
for (MessageRecord messageRecord : messageRecords) {
isSentByUser = isSentByUser && messageRecord.isOutgoing();
Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id);
@@ -713,7 +704,6 @@ public class ConversationFragment extends Fragment
if (adapter == null) {
return;
}
- adapter.friendRequestViewDelegate = this.friendRequestViewDelegate;
if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) {
adapter.setFooterView(topLoadMoreView);
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java
index 5d46c0fb6c..fb56fbca52 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java
@@ -86,10 +86,7 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
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.views.FriendRequestView;
-import org.thoughtcrime.securesms.loki.views.FriendRequestViewDelegate;
import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
import org.thoughtcrime.securesms.mms.GlideRequests;
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.views.Stub;
import org.whispersystems.libsignal.util.guava.Optional;
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat;
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI;
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChat;
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI;
import java.util.Collections;
import java.util.HashSet;
@@ -158,7 +155,6 @@ public class ConversationItem extends LinearLayout
private ViewGroup contactPhotoHolder;
private AlertView alertView;
private ViewGroup container;
- private FriendRequestView friendRequestView;
private @NonNull Set batchSelected = new HashSet<>();
private Recipient conversationRecipient;
@@ -182,8 +178,6 @@ public class ConversationItem extends LinearLayout
private final Context context;
- public FriendRequestViewDelegate friendRequestViewDelegate; // Loki
-
public ConversationItem(Context context) {
this(context, null);
}
@@ -223,7 +217,6 @@ public class ConversationItem extends LinearLayout
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
this.quoteView = findViewById(R.id.quote_view);
this.container = findViewById(R.id.container);
- this.friendRequestView = findViewById(R.id.friend_request_view);
setOnClickListener(new ClickListener(null));
@@ -269,7 +262,6 @@ public class ConversationItem extends LinearLayout
setQuote(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
setMessageSpacing(context, messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
setFooter(messageRecord, nextMessageRecord, locale, groupThread);
- setFriendRequestView(messageRecord);
adjustMarginsIfNeeded(messageRecord);
}
@@ -801,13 +793,14 @@ public class ConversationItem extends LinearLayout
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)bodyBubble.getLayoutParams();
int groupThreadMargin = (int)((12 * getResources().getDisplayMetrics().density) + getResources().getDimension(R.dimen.small_profile_picture_size));
int defaultMargin = 0;
- String threadName = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(messageRecord.getThreadId()).getName();
+ Recipient r = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(messageRecord.getThreadId());
+ String threadName = r != null ? r.getName() : "";
boolean isRSSFeed = threadName != null && (threadName.equals("Loki News") || threadName.equals("Session Updates"));
layoutParams.setMarginStart((groupThread && !isRSSFeed) ? groupThreadMargin : defaultMargin);
bodyBubble.setLayoutParams(layoutParams);
if (profilePictureView == null) return;
- profilePictureView.setHexEncodedPublicKey(recipient.getAddress().toString());
- profilePictureView.setAdditionalHexEncodedPublicKey(null);
+ profilePictureView.setPublicKey(recipient.getAddress().toString());
+ profilePictureView.setAdditionalPublicKey(null);
profilePictureView.setRSSFeed(false);
profilePictureView.setGlide(glideRequests);
profilePictureView.update();
@@ -919,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) {
if (hasSticker(messageRecord)) {
return stickerFooter;
@@ -1000,9 +988,9 @@ public class ConversationItem extends LinearLayout
profilePictureView.setVisibility(VISIBLE);
int visibility = View.GONE;
- LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId());
+ PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId());
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;
}
@@ -1065,14 +1053,6 @@ public class ConversationItem extends LinearLayout
int spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_collapse);
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)) {
spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_default);
}
diff --git a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java
index 28c9611d6a..8553b2b73c 100644
--- a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java
@@ -27,15 +27,14 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Consumer;
public class RecipientDatabase extends Database {
private static final String TAG = RecipientDatabase.class.getSimpleName();
- static final String TABLE_NAME = "recipient_preferences";
+ static final String TABLE_NAME = "recipient_preferences";
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 NOTIFICATION = "notification";
private static final String VIBRATE = "vibrate";
diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
index 0c2200688c..5b99737b18 100644
--- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -885,7 +885,7 @@ public class SmsDatabase extends MessagingDatabase {
0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
threadId, 0, new LinkedList(),
message.getSubscriptionId(), message.getExpiresIn(),
- System.currentTimeMillis(), 0, false, message.isFriendRequest);
+ System.currentTimeMillis(), 0, false);
}
}
@@ -934,15 +934,12 @@ public class SmsDatabase extends MessagingDatabase {
List mismatches = getMismatches(mismatchDocument);
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,
recipient,
addressDeviceId,
dateSent, dateReceived, deliveryReceiptCount, type,
threadId, status, mismatches, subscriptionId,
- expiresIn, expireStarted, readReceiptCount, unidentified, isFriendRequest);
+ expiresIn, expireStarted, readReceiptCount, unidentified);
}
private List getMismatches(String document) {
diff --git a/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java
index 8eb29d3509..7c9fe9974e 100644
--- a/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java
+++ b/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java
@@ -12,6 +12,7 @@ import android.text.TextUtils;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
@@ -423,7 +424,7 @@ public class ClassicOpenHelper extends SQLiteOpenHelper {
db.endTransaction();
// DecryptingQueue.schedulePendingDecrypts(context, masterSecret);
- MessageNotifier.updateNotification(context);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
}
@Override
diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
index e55644c8ff..2dc877906d 100644
--- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
+++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
@@ -45,7 +45,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.GroupUtil;
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;
@@ -83,8 +83,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV7 = 28;
private static final int lokiV8 = 29;
private static final int lokiV9 = 30;
+ private static final int lokiV10 = 31;
+ private static final int lokiV11 = 32;
- 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 = lokiV11; // 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 final Context context;
@@ -143,12 +145,14 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.getCreateDeviceLinkCacheCommand());
db.execSQL(LokiAPIDatabase.getCreateUserCountCacheCommand());
db.execSQL(LokiAPIDatabase.getCreateSessionRequestTimestampCacheCommand());
+ db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampCacheCommand());
+ db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampCacheCommand());
+ db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyDBCommand());
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
- db.execSQL(LokiMessageDatabase.getCreateMessageFriendRequestTableCommand());
+ db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
- db.execSQL(LokiThreadDatabase.getCreateFriendRequestTableCommand());
db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand());
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
@@ -545,7 +549,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
try (Cursor lokiPublicChatCursor = db.rawQuery("SELECT public_chat FROM loki_public_chat_database", null)) {
while (lokiPublicChatCursor != null && lokiPublicChatCursor.moveToNext()) {
String chatString = lokiPublicChatCursor.getString(0);
- LokiPublicChat publicChat = LokiPublicChat.fromJSON(chatString);
+ PublicChat publicChat = PublicChat.fromJSON(chatString);
if (publicChat != null) {
byte[] groupId = publicChat.getId().getBytes();
String oldId = GroupUtil.getEncodedId(groupId, false);
@@ -590,6 +594,15 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathCacheCommand());
}
+ if (oldVersion < lokiV10) {
+ db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampCacheCommand());
+ db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampCacheCommand());
+ }
+
+ if (oldVersion < lokiV11) {
+ db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyDBCommand());
+ }
+
db.setTransactionSuccessful();
} finally {
db.endTransaction();
diff --git a/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java
index 596d4f84f0..24bf81d97a 100644
--- a/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java
+++ b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities;
import org.thoughtcrime.securesms.util.AsyncLoader;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import java.io.File;
import java.util.Collections;
diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
index 52c9700ebe..056dbacb45 100644
--- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
@@ -39,23 +39,6 @@ import network.loki.messenger.R;
*/
public class SmsMessageRecord extends MessageRecord {
- // Loki
- private final boolean isFriendRequest;
-
- 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 mismatches,
- int subscriptionId, long expiresIn, long expireStarted,
- 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,
@@ -65,22 +48,18 @@ public class SmsMessageRecord extends MessageRecord {
long type, long threadId,
int status, List mismatches,
int subscriptionId, long expiresIn, long expireStarted,
- int readReceiptCount, boolean unidentified, boolean isFriendRequest)
+ int readReceiptCount, boolean unidentified)
{
super(id, body, recipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, status, deliveryReceiptCount, type,
mismatches, new LinkedList<>(), subscriptionId,
expiresIn, expireStarted, readReceiptCount, unidentified);
- this.isFriendRequest = isFriendRequest;
}
public long getType() {
return type;
}
- // Loki
- public boolean isFriendRequest() { return isFriendRequest; }
-
@Override
public SpannableString getDisplayBody(@NonNull Context context) {
Recipient recipient = getRecipient();
diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
index b74af10069..79a875c817 100644
--- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
+++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
@@ -45,7 +45,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.logging.Log;
-import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
+import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
@@ -152,12 +152,11 @@ public class SignalCommunicationModule {
Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()),
Optional.of(new MessageSenderEventListener(context)),
TextSecurePreferences.getLocalNumber(context),
- TextSecurePreferences.getMasterHexEncodedPublicKey(context),
DatabaseFactory.getLokiAPIDatabase(context),
DatabaseFactory.getLokiThreadDatabase(context),
DatabaseFactory.getLokiMessageDatabase(context),
DatabaseFactory.getLokiPreKeyBundleDatabase(context),
- new LokiSessionResetImplementation(context),
+ new SessionResetImplementation(context),
DatabaseFactory.getLokiUserDatabase(context),
((ApplicationContext)context.getApplicationContext()).broadcaster);
} else {
diff --git a/src/org/thoughtcrime/securesms/giph/model/ChunkedImageUrl.java b/src/org/thoughtcrime/securesms/giph/model/ChunkedImageUrl.java
index d425c0ceac..bf5ee63659 100644
--- a/src/org/thoughtcrime/securesms/giph/model/ChunkedImageUrl.java
+++ b/src/org/thoughtcrime/securesms/giph/model/ChunkedImageUrl.java
@@ -14,7 +14,7 @@ public class ChunkedImageUrl implements Key {
public static final long SIZE_UNKNOWN = -1;
private final String url;
- private final long size;
+ private final long size;
public ChunkedImageUrl(@NonNull String url) {
this(url, SIZE_UNKNOWN);
@@ -22,7 +22,7 @@ public class ChunkedImageUrl implements Key {
public ChunkedImageUrl(@NonNull String url, long size) {
this.url = url;
- this.size = size;
+ this.size = size;
}
public String getUrl() {
@@ -35,6 +35,7 @@ public class ChunkedImageUrl implements Key {
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
+ if (url == null) { return; }
messageDigest.update(url.getBytes());
messageDigest.update(Conversions.longToByteArray(size));
}
@@ -45,12 +46,14 @@ public class ChunkedImageUrl implements Key {
ChunkedImageUrl that = (ChunkedImageUrl)other;
+ if (this.url == null || that.url == null) { return false; }
+
return this.url.equals(that.url) && this.size == that.size;
}
@Override
public int hashCode() {
+ if (url == null) { return 0; }
return url.hashCode() ^ (int)size;
}
-
}
diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
index 463093b5ca..849cc67a32 100644
--- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
+++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
@@ -20,7 +20,6 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
-import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
@@ -33,7 +32,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type;
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import java.util.Collections;
import java.util.HashSet;
@@ -131,10 +130,12 @@ public class GroupMessageProcessor {
String id = GroupUtil.getEncodedId(group);
String userMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
+ if (userMasterDevice == null) { userMasterDevice = TextSecurePreferences.getLocalNumber(context); }
if (group.getGroupType() == SignalServiceGroup.GroupType.SIGNAL) {
// Loki - Only update the group if the group admin sent the message
String masterDevice = MultiDeviceProtocol.shared.getMasterDevice(content.getSender());
+ if (masterDevice == null) { masterDevice = content.getSender(); }
if (!groupRecord.getAdmins().contains(Address.fromSerialized(masterDevice))) {
Log.d("Loki", "Received a group update message from a non-admin user for: " + id +"; ignoring.");
return null;
@@ -212,6 +213,7 @@ public class GroupMessageProcessor {
@NonNull GroupRecord record)
{
String masterDevice = MultiDeviceProtocol.shared.getMasterDevice(content.getSender());
+ if (masterDevice == null) { masterDevice = content.getSender(); }
if (record.getMembers().contains(Address.fromSerialized(masterDevice))) {
ApplicationContext.getInstance(context)
.getJobManager()
@@ -234,6 +236,7 @@ public class GroupMessageProcessor {
builder.setType(GroupContext.Type.QUIT);
String masterDevice = MultiDeviceProtocol.shared.getMasterDevice(content.getSender());
+ if (masterDevice == null) { masterDevice = content.getSender(); }
if (members.contains(Address.fromExternal(context, masterDevice))) {
database.remove(id, Address.fromExternal(context, masterDevice));
if (outgoing) database.setActive(id, false);
@@ -259,7 +262,7 @@ public class GroupMessageProcessor {
try {
if (outgoing) {
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
- Address address = Address.fromExternal(context, GroupUtil.getEncodedId(group));
+ Address address = Address.fromExternal(context, GroupUtil.getEncodedId(group));
Recipient recipient = Recipient.from(context, address, false);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, null, Collections.emptyList(), Collections.emptyList());
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
@@ -277,7 +280,7 @@ public class GroupMessageProcessor {
Optional insertResult = smsDatabase.insertMessageInbox(groupMessage);
if (insertResult.isPresent()) {
- MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, insertResult.get().getThreadId());
return insertResult.get().getThreadId();
} else {
return null;
diff --git a/src/org/thoughtcrime/securesms/jobmanager/migration/WorkManagerFactoryMappings.java b/src/org/thoughtcrime/securesms/jobmanager/migration/WorkManagerFactoryMappings.java
index da6e221978..c4aa802414 100644
--- a/src/org/thoughtcrime/securesms/jobmanager/migration/WorkManagerFactoryMappings.java
+++ b/src/org/thoughtcrime/securesms/jobmanager/migration/WorkManagerFactoryMappings.java
@@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.jobs.SmsSentJob;
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.jobs.UpdateApkJob;
+import org.thoughtcrime.securesms.loki.protocol.PushNullMessageSendJob;
import java.util.HashMap;
import java.util.Map;
@@ -74,6 +75,7 @@ public class WorkManagerFactoryMappings {
put(PushMediaSendJob.class.getName(), PushMediaSendJob.KEY);
put(PushNotificationReceiveJob.class.getName(), PushNotificationReceiveJob.KEY);
put(PushTextSendJob.class.getName(), PushTextSendJob.KEY);
+ put(PushNullMessageSendJob.class.getName(), PushNullMessageSendJob.KEY);
put(RefreshAttributesJob.class.getName(), RefreshAttributesJob.KEY);
put(RefreshPreKeysJob.class.getName(), RefreshPreKeysJob.KEY);
put(RefreshUnidentifiedDeliveryAbilityJob.class.getName(), RefreshUnidentifiedDeliveryAbilityJob.KEY);
diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
index 5b4b7809ca..5b34dc2f71 100644
--- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
@@ -5,6 +5,7 @@ import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import org.greenrobot.eventbus.EventBus;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
@@ -107,7 +108,7 @@ public class AttachmentDownloadJob extends BaseJob implements InjectableType {
@Override
public void onRun() throws IOException {
doWork();
- MessageNotifier.updateNotification(context, 0);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, 0);
}
public void doWork() throws IOException {
diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
index 16d1b09b85..99f5b1c7d5 100644
--- a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
@@ -27,7 +27,6 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
import java.io.IOException;
import java.io.InputStream;
@@ -53,7 +52,7 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
- .setMaxAttempts(3)
+ .setMaxAttempts(1)
.build(),
attachmentId, destination);
}
diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
index a366bafe8c..be5aef3962 100644
--- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
+++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
@@ -14,7 +14,8 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
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.PushSessionRequestMessageSendJob;
import java.util.Arrays;
import java.util.HashMap;
@@ -50,6 +51,7 @@ public final class JobManagerFactories {
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
+ put(PushNullMessageSendJob.KEY, new PushNullMessageSendJob.Factory());
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory());
put(RefreshUnidentifiedDeliveryAbilityJob.KEY, new RefreshUnidentifiedDeliveryAbilityJob.Factory());
@@ -70,7 +72,7 @@ public final class JobManagerFactories {
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
put(TypingSendJob.KEY, new TypingSendJob.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());
}};
}
diff --git a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
index 47411c08f0..5b1d81abcc 100644
--- a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
@@ -10,6 +10,7 @@ import com.google.android.mms.pdu_alt.PduBody;
import com.google.android.mms.pdu_alt.PduPart;
import com.google.android.mms.pdu_alt.RetrieveConf;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.database.Address;
@@ -57,6 +58,7 @@ public class MmsDownloadJob extends BaseJob {
private long messageId;
private long threadId;
private boolean automatic;
+ private MessageNotifier messageNotifier;
public MmsDownloadJob(long messageId, long threadId, boolean automatic) {
this(new Job.Parameters.Builder()
@@ -75,6 +77,7 @@ public class MmsDownloadJob extends BaseJob {
this.messageId = messageId;
this.threadId = threadId;
this.automatic = automatic;
+ this.messageNotifier = ApplicationContext.getInstance(context).messageNotifier;
}
@Override
@@ -94,7 +97,7 @@ public class MmsDownloadJob extends BaseJob {
public void onAdded() {
if (automatic && KeyCachingService.isLocked(context)) {
DatabaseFactory.getMmsDatabase(context).markIncomingNotificationReceived(threadId);
- MessageNotifier.updateNotification(context);
+ messageNotifier.updateNotification(context);
}
}
@@ -177,7 +180,7 @@ public class MmsDownloadJob extends BaseJob {
if (automatic) {
database.markIncomingNotificationReceived(threadId);
- MessageNotifier.updateNotification(context, threadId);
+ messageNotifier.updateNotification(context, threadId);
}
}
@@ -252,7 +255,7 @@ public class MmsDownloadJob extends BaseJob {
if (insertResult.isPresent()) {
database.delete(messageId);
- MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
+ messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
}
@@ -264,7 +267,7 @@ public class MmsDownloadJob extends BaseJob {
if (automatic) {
db.markIncomingNotificationReceived(threadId);
- MessageNotifier.updateNotification(context, threadId);
+ messageNotifier.updateNotification(context, threadId);
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java
index 315e13ad64..e3005573e3 100644
--- a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java
@@ -19,6 +19,7 @@ import com.google.android.mms.pdu_alt.SendReq;
import com.google.android.mms.smil.SmilHelper;
import com.klinker.android.send_message.Utils;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -304,7 +305,7 @@ public class MmsSendJob extends SendJob {
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
if (recipient != null) {
- MessageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
+ ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java
index 2c18845211..1a012b7270 100644
--- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java
@@ -122,7 +122,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy
}
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)
@@ -139,8 +139,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy
Optional identityRecord = DatabaseFactory.getIdentityDatabase(context).getIdentity(address);
Optional verifiedMessage = getVerifiedMessage(recipient, identityRecord);
- // Loki - Only sync contacts we are friends with
- if (SyncMessagesProtocol.shouldSyncContact(context, address)) {
+ if (SyncMessagesProtocol.shouldSyncContact(context, address.serialize())) {
out.write(new DeviceContact(address.toPhoneString(),
Optional.fromNullable(recipient.getName()),
getAvatar(recipient.getContactUri()),
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 9f90b19ed0..6183467232 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -48,7 +48,6 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.PushDatabase;
-import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
@@ -66,15 +65,11 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
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.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.PushEphemeralMessageSendJob;
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
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.utilities.MentionManagerUtilities;
import org.thoughtcrime.securesms.loki.utilities.PromiseUtilities;
@@ -102,7 +97,7 @@ import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
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.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
@@ -127,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.shared.SharedContact;
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.protocol.mentions.MentionsManager;
-import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus;
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation;
-import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
@@ -145,8 +138,6 @@ import javax.inject.Inject;
import network.loki.messenger.R;
-import static org.thoughtcrime.securesms.loki.utilities.RecipientUtilitiesKt.recipient;
-
public class PushDecryptJob extends BaseJob implements InjectableType {
public static final String KEY = "PushDecryptJob";
@@ -159,8 +150,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private long messageId;
private long smsMessageId;
+ private MessageNotifier messageNotifier;
+
@Inject SignalServiceMessageSender messageSender;
- private Address author;
public PushDecryptJob(Context context) {
this(context, -1);
@@ -178,6 +170,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
pushMessageId,
smsMessageId);
setContext(context);
+ this.messageNotifier = ApplicationContext.getInstance(context).messageNotifier;
}
private PushDecryptJob(@NonNull Job.Parameters parameters, long pushMessageId, long smsMessageId) {
@@ -223,9 +216,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
@Override
- public void onCanceled() {
-
- }
+ public void onCanceled() { }
public void processMessage(@NonNull SignalServiceEnvelope envelope, boolean isPushNotification) {
synchronized (PushReceivedJob.RECEIVE_LOCK) {
@@ -260,32 +251,21 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional smsMessageId, boolean isPushNotification) {
try {
- GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
- SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
- LokiSessionResetProtocol lokiSessionResetProtocol = new LokiSessionResetImplementation(context);
- SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
- LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiSessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
+ GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
+ SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
+ SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
+ SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
+ LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
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)) {
Log.i(TAG, "Ignoring message.");
return;
}
- // Loki - Handle pre key bundle message if needed
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);
if (content.getDeviceLink().isPresent()) {
@@ -294,15 +274,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
SignalServiceDataMessage message = content.getDataMessage().get();
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.isUnlinkingRequest()) {
+ if (message.isDeviceUnlinkingRequest()) {
MultiDeviceProtocol.handleUnlinkingRequestIfNeeded(context, content);
} 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()) {
handleEndSessionMessage(content, smsMessageId);
@@ -312,52 +286,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
handleExpirationUpdate(content, message, smsMessageId);
} else if (isMediaMessage) {
handleMediaMessage(content, message, smsMessageId, Optional.absent());
-
- // Loki - This is needed for compatibility with refactored desktop clients
- if (!message.isGroupMessage()) {
- 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()));
- }
-
- // Loki - Handle friend request message if needed
- FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
- }
} else if (message.getBody().isPresent()) {
handleTextMessage(content, message, smsMessageId, Optional.absent());
-
- // Loki - This is needed for compatibility with refactored desktop clients
- if (!message.isGroupMessage()) {
- 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()));
- }
-
- // Loki - Handle friend request message if needed
- FriendRequestProtocol.handleFriendRequestMessageIfNeeded(context, content.getSender(), content);
- }
- } else {
- // Loki - This is needed for compatibility with refactored desktop clients
- if (envelope.isFriendRequest()) {
- EphemeralMessage ephemeralMessage = EphemeralMessage.create(content.getSender());
- ApplicationContext.getInstance(context).getJobManager().add(new PushEphemeralMessageSendJob(ephemeralMessage));
- }
}
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
@@ -365,7 +295,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
- handleProfileKey(content, message);
+ SessionMetaProtocol.handleProfileKeyUpdate(context, content);
}
if (content.isNeedsReceipt()) {
@@ -385,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.getGroups().isPresent()) SyncMessagesProtocol.handleClosedGroupSyncMessage(context, content, syncMessage.getGroups().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 if (content.getCallMessage().isPresent()) {
Log.i(TAG, "Got call message...");
@@ -546,7 +477,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (threadId != null) {
SessionManagementProtocol.handleEndSessionMessageIfNeeded(context, content);
- MessageNotifier.updateNotification(context, threadId);
+ messageNotifier.updateNotification(context, threadId);
}
}
@@ -700,19 +631,17 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
}
- // Loki - Handle profile key update if needed
- handleProfileKey(content, message.getMessage());
+ SessionMetaProtocol.handleProfileKeyUpdate(context, content);
}
- // Loki - Update profile if needed
SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content);
if (threadId != null) {
DatabaseFactory.getThreadDatabase(context).setRead(threadId, true);
- MessageNotifier.updateNotification(context);
+ messageNotifier.updateNotification(context);
}
- MessageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp());
+ messageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp());
} catch (MmsException e) {
throw new StorageFailedException(e, content.getSender(), content.getSenderDevice());
}
@@ -775,9 +704,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
- MessageNotifier.setLastDesktopActivityTimestamp(envelopeTimestamp);
- MessageNotifier.cancelDelayedNotifications();
- MessageNotifier.updateNotification(context);
+ messageNotifier.setLastDesktopActivityTimestamp(envelopeTimestamp);
+ messageNotifier.cancelDelayedNotifications();
+ messageNotifier.updateNotification(context);
}
public void handleMediaMessage(@NonNull SignalServiceContent content,
@@ -809,7 +738,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
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()) {
return;
}
@@ -843,23 +772,31 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
if (insertResult.isPresent()) {
- MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
+ messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
- // Loki - Store message open group server ID if needed
- if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) {
- long messageID = insertResult.get().getMessageId();
- long messageServerID = messageServerIDOrNull.get();
- LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
- lokiMessageDatabase.setServerID(messageID, messageServerID);
- }
-
- // Loki - Update mapping of message ID to original thread ID
if (insertResult.isPresent()) {
- ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
- LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
- long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
- lokiMessageDatabase.setOriginalThreadID(insertResult.get().getMessageId(), originalThreadId);
+ 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
+ if (messageServerIDOrNull.isPresent()) {
+ long messageID = result.getMessageId();
+ long messageServerID = messageServerIDOrNull.get();
+ LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
+ lokiMessageDatabase.setServerID(messageID, messageServerID);
+ }
+
+ // Loki - Update mapping of message ID to original thread ID
+ if (result.getMessageId() > -1) {
+ ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
+ LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
+ long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
+ lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId);
+ }
}
}
@@ -1015,7 +952,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get());
if (threadId != null) {
- MessageNotifier.updateNotification(context, threadId);
+ messageNotifier.updateNotification(context, threadId);
}
if (insertResult.isPresent()) {
@@ -1023,17 +960,17 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
// Loki - Cache the user hex encoded public key (for mentions)
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
- if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) {
- long messageID = insertResult.get().getMessageId();
+ // Loki - Store message open group server ID if needed
+ if (messageServerIDOrNull.isPresent()) {
+ long messageID = result.getMessageId();
long messageServerID = messageServerIDOrNull.get();
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
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) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
@@ -1116,7 +1053,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (insertResult.isPresent()) {
smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId());
- MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
+ messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else {
smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get());
@@ -1133,7 +1070,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (insertResult.isPresent()) {
smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId());
- MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
+ messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else {
smsDatabase.markAsDecryptFailed(smsMessageId.get());
@@ -1151,7 +1088,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (insertResult.isPresent()) {
smsDatabase.markAsNoSession(insertResult.get().getMessageId());
- MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
+ messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else {
smsDatabase.markAsNoSession(smsMessageId.get());
@@ -1169,7 +1106,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (insertResult.isPresent()) {
smsDatabase.markAsLegacyVersion(insertResult.get().getMessageId());
- MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
+ messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else {
smsDatabase.markAsLegacyVersion(smsMessageId.get());
@@ -1192,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,
@NonNull SignalServiceDataMessage message)
{
@@ -1441,14 +1356,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else {
String publicKey = message.getDestination().get();
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
- Set allUserDevices = org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
+ Set allUserDevices = org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
if (allUserDevices.contains(publicKey)) {
return Recipient.from(context, Address.fromSerialized(userPublicKey), false);
} else {
try {
// TODO: Burn this with fire when we can
- PromiseUtilities.timeout(LokiFileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
- String masterPublicKey = org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
+ PromiseUtilities.timeout(FileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
+ String masterPublicKey = org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
if (masterPublicKey == null) {
masterPublicKey = publicKey;
}
@@ -1473,14 +1388,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return Recipient.from(context, Address.fromSerialized(publicKey), false);
} else {
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
- Set allUserDevices = org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
+ Set allUserDevices = org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
if (allUserDevices.contains(publicKey)) {
return Recipient.from(context, Address.fromSerialized(userPublicKey), false);
} else {
try {
// TODO: Burn this with fire when we can
- PromiseUtilities.timeout(LokiFileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
- String masterPublicKey = org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
+ PromiseUtilities.timeout(FileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
+ String masterPublicKey = org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
if (masterPublicKey == null) {
masterPublicKey = publicKey;
}
diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
index 3779252021..3a70c4fc18 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
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.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
@@ -44,7 +42,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
-import org.whispersystems.signalservice.loki.api.LokiAPI;
+import org.whispersystems.signalservice.loki.api.SnodeAPI;
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
import java.io.FileNotFoundException;
@@ -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_MESSAGE_ID = "message_id";
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;
- private long messageId; // The message ID
- private long templateMessageId; // The message ID of the message to template this send job from
-
- // 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
+ private long messageId;
+ private long templateMessageId;
+ private Address destination;
public PushMediaSendJob(long messageId, Address destination) {
this(messageId, messageId, 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) {
- this(constructParameters(destination), templateMessageId, messageId, destination, isLokiPreKeyBundleMessage, customFriendRequestMessage);
- }
-
- private PushMediaSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination, boolean isLokiPreKeyBundleMessage, String customFriendRequestMessage) {
+ private PushMediaSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination) {
super(parameters);
this.templateMessageId = templateMessageId;
this.messageId = messageId;
this.destination = destination;
- this.isLokiPreKeyBundleMessage = isLokiPreKeyBundleMessage;
- this.customFriendRequestMessage = customFriendRequestMessage;
}
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) {
- enqueue(context, jobManager, Collections.singletonList(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, 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)));
}
@WorkerThread
@@ -144,14 +130,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
@Override
public @NonNull Data serialize() {
- Data.Builder builder = new Data.Builder()
- .putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
- .putLong(KEY_MESSAGE_ID, messageId)
- .putString(KEY_DESTINATION, destination.serialize())
- .putBoolean(KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE, isLokiPreKeyBundleMessage);
-
- if (customFriendRequestMessage != null) { builder.putString(KEY_CUSTOM_FR_MESSAGE, customFriendRequestMessage); }
- return builder.build();
+ return new Data.Builder()
+ .putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
+ .putLong(KEY_MESSAGE_ID, messageId)
+ .putString(KEY_DESTINATION, destination.serialize()).build();
}
@Override
@@ -231,7 +213,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey());
database.markAsSentFailed(messageId);
}
- } catch (LokiAPI.Error e) {
+ } catch (SnodeAPI.Error e) {
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
if (messageId >= 0) {
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
@@ -257,7 +239,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
private boolean deliver(OutgoingMediaMessage message)
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
- UndeliverableMessageException, LokiAPI.Error
+ UndeliverableMessageException, SnodeAPI.Error
{
try {
Recipient recipient = Recipient.from(context, destination, false);
@@ -270,17 +252,8 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
List sharedContacts = getSharedContactsFor(message);
List 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()
- .withBody(body)
+ .withBody(message.getBody())
.withAttachments(serviceAttachments)
.withTimestamp(message.getSentTimeMillis())
.withExpiration((int)(message.getExpiresIn() / 1000))
@@ -290,8 +263,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
.withSharedContacts(sharedContacts)
.withPreviews(previews)
.asExpirationUpdate(message.isExpirationUpdate())
- .withPreKeyBundle(preKeyBundle)
- .asFriendRequest(isLokiPreKeyBundleMessage)
.build();
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 messageID = data.getLong(KEY_MESSAGE_ID);
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
- boolean isLokiPreKeyBundleMessage = data.getBoolean(KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE);
- String customFRMessage = data.hasString(KEY_CUSTOM_FR_MESSAGE) ? data.getString(KEY_CUSTOM_FR_MESSAGE) : null;
- return new PushMediaSendJob(parameters, templateMessageID, messageID, destination, isLokiPreKeyBundleMessage, customFRMessage);
+ return new PushMediaSendJob(parameters, templateMessageID, messageID, destination);
}
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java
index c93ee94cd0..63fc0f65d5 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java
@@ -36,7 +36,7 @@ public abstract class PushReceivedJob extends BaseJob {
if (envelope.isReceipt()) {
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);
} else {
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java
index 3d0eb2e259..b3551572fe 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java
@@ -182,7 +182,7 @@ public abstract class PushSendJob extends SendJob {
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
if (threadId != -1 && recipient != null) {
- MessageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
+ ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
index 3d018d14dc..6e28d4163c 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
@@ -16,7 +16,6 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
-import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
@@ -32,7 +31,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
-import org.whispersystems.signalservice.loki.api.LokiAPI;
+import org.whispersystems.signalservice.loki.api.SnodeAPI;
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
import java.io.IOException;
@@ -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_MESSAGE_ID = "message_id";
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;
- private long messageId; // The message ID
- private long templateMessageId; // The message ID of the message to template this send job from
-
- // 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
+ private long messageId;
+ private long templateMessageId;
+ private Address destination;
public PushTextSendJob(long messageId, Address destination) {
this(messageId, messageId, 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) {
- this(constructParameters(destination), templateMessageId, messageId, destination, isLokiPreKeyBundleMessage, customFriendRequestMessage);
- }
-
- private PushTextSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination, boolean isLokiPreKeyBundleMessage, String customFriendRequestMessage) {
+ private PushTextSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination) {
super(parameters);
this.templateMessageId = templateMessageId;
this.messageId = messageId;
this.destination = destination;
- this.isLokiPreKeyBundleMessage = isLokiPreKeyBundleMessage;
- this.customFriendRequestMessage = customFriendRequestMessage;
}
@Override
public @NonNull Data serialize() {
- Data.Builder builder = new Data.Builder()
- .putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
- .putLong(KEY_MESSAGE_ID, messageId)
- .putString(KEY_DESTINATION, destination.serialize())
- .putBoolean(KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE, isLokiPreKeyBundleMessage);
-
- if (customFriendRequestMessage != null) { builder.putString(KEY_CUSTOM_FR_MESSAGE, customFriendRequestMessage); }
- return builder.build();
+ return new Data.Builder()
+ .putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId)
+ .putLong(KEY_MESSAGE_ID, messageId)
+ .putString(KEY_DESTINATION, destination.serialize()).build();
}
@Override
@@ -164,7 +147,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
warn(TAG, "Couldn't send message due to error: ", e);
if (messageId >= 0) {
database.markAsPendingInsecureSmsFallback(record.getId());
- MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
+ ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
}
} catch (UntrustedIdentityException e) {
warn(TAG, "Couldn't send message due to error: ", e);
@@ -173,7 +156,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
database.markAsSentFailed(record.getId());
database.markAsPush(record.getId());
}
- } catch (LokiAPI.Error e) {
+ } catch (SnodeAPI.Error e) {
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
if (messageId >= 0) {
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
@@ -198,13 +181,13 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
if (threadId != -1 && recipient != null) {
- MessageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
+ ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
}
}
}
private boolean deliver(SmsMessageRecord message)
- throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException, LokiAPI.Error
+ throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException, SnodeAPI.Error
{
try {
Recipient recipient = Recipient.from(context, destination, false);
@@ -214,23 +197,18 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
log(TAG, "Have access key to use: " + unidentifiedAccess.isPresent());
- // Loki - Include a pre key bundle if needed
- PreKeyBundle preKeyBundle;
- if (isLokiPreKeyBundleMessage || message.isEndSession()) {
- preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.getNumber());
- } else {
- preKeyBundle = null;
+ PreKeyBundle preKeyBundle = null;
+ if (message.isEndSession()) {
+ preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(destination.serialize());
}
- String body = (isLokiPreKeyBundleMessage && customFriendRequestMessage != null) ? customFriendRequestMessage : message.getBody();
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getDateSent())
- .withBody(body)
+ .withBody(message.getBody())
.withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull())
- .asEndSessionMessage(message.isEndSession())
- .asFriendRequest(isLokiPreKeyBundleMessage)
.withPreKeyBundle(preKeyBundle)
+ .asEndSessionMessage(message.isEndSession())
.build();
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 messageID = data.getLong(KEY_MESSAGE_ID);
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
- boolean isLokiPreKeyBundleMessage = data.getBoolean(KEY_IS_LOKI_PRE_KEY_BUNDLE_MESSAGE);
- String customFRMessage = data.hasString(KEY_CUSTOM_FR_MESSAGE) ? data.getString(KEY_CUSTOM_FR_MESSAGE) : null;
- return new PushTextSendJob(parameters, templateMessageID, messageID, destination, isLokiPreKeyBundleMessage, customFRMessage);
+ return new PushTextSendJob(parameters, templateMessageID, messageID, destination);
}
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java b/src/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java
index 82b721c845..c8d0932ec5 100644
--- a/src/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java
@@ -52,6 +52,8 @@ public class RotateSignedPreKeyJob extends BaseJob implements InjectableType {
public void onRun() throws Exception {
Log.i(TAG, "Rotating signed prekey...");
+ if (!IdentityKeyUtil.hasIdentityKey(context)) { return; }
+
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey, false);
diff --git a/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java
index 69a6e5c12b..e6463eb11e 100644
--- a/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java
@@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.telephony.SmsMessage;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
@@ -79,7 +80,7 @@ public class SmsReceiveJob extends BaseJob {
Optional insertResult = storeMessage(message.get());
if (insertResult.isPresent()) {
- MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else if (message.isPresent()) {
Log.w(TAG, "*** Received blocked SMS, ignoring...");
diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java
index 282099393b..ed4ebcfdf4 100644
--- a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java
@@ -9,6 +9,7 @@ import android.support.annotation.NonNull;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint;
@@ -88,7 +89,7 @@ public class SmsSendJob extends SendJob {
} catch (UndeliverableMessageException ude) {
warn(TAG, ude);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(record.getId());
- MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
+ ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
}
}
@@ -106,7 +107,7 @@ public class SmsSendJob extends SendJob {
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
if (threadId != -1 && recipient != null) {
- MessageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
+ ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java
index cadecd0b81..43fc8d4170 100644
--- a/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java
@@ -108,7 +108,7 @@ public class SmsSentJob extends BaseJob {
break;
default:
database.markAsSentFailed(messageId);
- MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
+ ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
}
} catch (NoSuchMessageException e) {
Log.w(TAG, e);
diff --git a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java
index 1e9fff7f65..c213963d75 100644
--- a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java
+++ b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java
@@ -21,7 +21,6 @@ import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.net.CallRequestController;
import org.thoughtcrime.securesms.net.CompositeRequestController;
import org.thoughtcrime.securesms.net.ContentProxySafetyInterceptor;
-import org.thoughtcrime.securesms.net.ContentProxySelector;
import org.thoughtcrime.securesms.net.RequestController;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
@@ -63,7 +62,7 @@ public class LinkPreviewRepository implements InjectableType {
public LinkPreviewRepository(@NonNull Context context) {
this.client = new OkHttpClient.Builder()
- .proxySelector(new ContentProxySelector())
+ // .proxySelector(new ContentProxySelector()) // Loki: Signal's proxy appears to have been banned by YouTube
.addNetworkInterceptor(new ContentProxySafetyInterceptor())
.cache(null)
.build();
diff --git a/src/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt
index 5b784de808..aa325d8719 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt
@@ -7,9 +7,12 @@ import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentPagerAdapter
+import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_create_private_chat.*
import kotlinx.android.synthetic.main.fragment_enter_public_key.*
@@ -25,6 +28,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
+
class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
private val adapter = CreatePrivateChatActivityAdapter(this)
@@ -111,7 +115,18 @@ class EnterPublicKeyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- publicKeyTextView.imeOptions = publicKeyTextView.imeOptions or 16777216 // Always use incognito keyboard
+ publicKeyEditText.imeOptions = EditorInfo.IME_ACTION_DONE or 16777216 // Always use incognito keyboard
+ publicKeyEditText.setRawInputType(InputType.TYPE_CLASS_TEXT)
+ publicKeyEditText.setOnEditorActionListener { v, actionID, _ ->
+ if (actionID == EditorInfo.IME_ACTION_DONE) {
+ val imm = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(v.windowToken, 0)
+ createPrivateChatIfPossible()
+ true
+ } else {
+ false
+ }
+ }
publicKeyTextView.text = hexEncodedPublicKey
copyButton.setOnClickListener { copyPublicKey() }
shareButton.setOnClickListener { sharePublicKey() }
diff --git a/src/org/thoughtcrime/securesms/loki/activities/DisplayNameActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/DisplayNameActivity.kt
index d5afafa364..09c7aac667 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/DisplayNameActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/DisplayNameActivity.kt
@@ -41,9 +41,6 @@ class DisplayNameActivity : BaseActionBarActivity() {
if (displayName.isEmpty()) {
return Toast.makeText(this, R.string.activity_display_name_display_name_missing_error, Toast.LENGTH_SHORT).show()
}
- if (!displayName.matches(Regex("[a-zA-Z0-9_]+"))) {
- return Toast.makeText(this, R.string.activity_display_name_display_name_invalid_error, Toast.LENGTH_SHORT).show()
- }
if (displayName.toByteArray().size > ProfileCipher.NAME_PADDED_LENGTH) {
return Toast.makeText(this, R.string.activity_display_name_display_name_too_long_error, Toast.LENGTH_SHORT).show()
}
diff --git a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
index 8df5d5f99f..cc2c3e5793 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
@@ -1,21 +1,20 @@
package org.thoughtcrime.securesms.loki.activities
-import android.annotation.SuppressLint
import android.app.AlertDialog
import android.arch.lifecycle.Observer
+import android.content.BroadcastReceiver
+import android.content.Context
import android.content.Intent
+import android.content.IntentFilter
import android.database.Cursor
-import android.graphics.BitmapFactory
-import android.graphics.Canvas
-import android.graphics.Paint
+import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.os.Handler
import android.support.v4.app.LoaderManager
import android.support.v4.content.Loader
+import android.support.v4.content.LocalBroadcastManager
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.SpannableString
import android.text.style.ForegroundColorSpan
@@ -31,35 +30,36 @@ import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord
-import org.thoughtcrime.securesms.loki.dialogs.PNModeBottomSheet
+import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob
+import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
+import org.thoughtcrime.securesms.loki.dialogs.ConversationOptionsBottomSheet
+import org.thoughtcrime.securesms.loki.dialogs.MultiDeviceRemovalBottomSheet
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.views.ConversationView
import org.thoughtcrime.securesms.loki.views.NewConversationButtonSetViewDelegate
import org.thoughtcrime.securesms.loki.views.SeedReminderViewDelegate
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
-import org.thoughtcrime.securesms.notifications.MessageNotifier
import org.thoughtcrime.securesms.util.TextSecurePreferences
-import org.whispersystems.signalservice.loki.protocol.friendrequests.FriendRequestProtocol
+import org.thoughtcrime.securesms.util.Util
+import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol
-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
+import org.whispersystems.signalservice.loki.protocol.shelved.syncmessages.SyncMessagesProtocol
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate {
private lateinit var glide: GlideRequests
+ private var broadcastReceiver: BroadcastReceiver? = null
- private val hexEncodedPublicKey: String
+ private val publicKey: String
get() {
- val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this)
- val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
- return masterHexEncodedPublicKey ?: userHexEncodedPublicKey
+ val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this)
+ val userPublicKey = TextSecurePreferences.getLocalNumber(this)
+ return masterPublicKey ?: userPublicKey
}
// region Lifecycle
@@ -77,7 +77,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val threadID = archivedConversations.getLong(archivedConversations.getColumnIndex(ThreadDatabase.ID))
AsyncTask.execute {
threadDatabase.deleteConversation(threadID)
- MessageNotifier.updateNotification(this)
+ (applicationContext as ApplicationContext).messageNotifier.updateNotification(this)
}
}
deleteThreadAtCurrentPosition()
@@ -95,7 +95,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
glide = GlideApp.with(this)
// Set up toolbar buttons
profileButton.glide = glide
- profileButton.hexEncodedPublicKey = hexEncodedPublicKey
+ profileButton.publicKey = publicKey
profileButton.update()
profileButton.setOnClickListener { openSettings() }
pathStatusViewContainer.setOnClickListener { showPath() }
@@ -119,7 +119,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
homeAdapter.conversationClickListener = this
recyclerView.adapter = homeAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
- ItemTouchHelper(SwipeCallback(this)).attachToRecyclerView(recyclerView)
// Set up empty state view
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
// This is a workaround for the fact that CursorRecyclerViewAdapter doesn't actually auto-update (even though it says it will)
@@ -157,62 +156,66 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
val userDB = DatabaseFactory.getLokiUserDatabase(this)
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
- val sessionResetImpl = LokiSessionResetImplementation(this)
+ val sessionResetImpl = SessionResetImplementation(this)
if (userPublicKey != null) {
- FriendRequestProtocol.configureIfNeeded(apiDB, userPublicKey)
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
SyncMessagesProtocol.configureIfNeeded(apiDB, userPublicKey)
- application.lokiPublicChatManager.startPollersIfNeeded()
+ application.publicChatManager.startPollersIfNeeded()
}
SessionManagementProtocol.configureIfNeeded(sessionResetImpl, threadDB, application)
MultiDeviceProtocol.configureIfNeeded(apiDB)
IP2Country.configureIfNeeded(this)
- // TODO: Temporary hack to unbork existing clients
- val allContacts = DatabaseFactory.getRecipientDatabase(this).allAddresses.map {
- MultiDeviceProtocol.shared.getMasterDevice(it.serialize()) ?: it.serialize()
+ // Preload device links to make message sending quicker
+ val publicKeys = ContactUtilities.getAllContacts(this).filter { contact ->
+ !contact.recipient.isGroupRecipient && !contact.isOurDevice && !contact.isSlave
+ }.map {
+ it.recipient.address.toPhoneString()
}.toSet()
- val lokiMessageDB = DatabaseFactory.getLokiMessageDatabase(this)
- 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)
- }
- }
+ FileServerAPI.shared.getDeviceLinks(publicKeys)
+ // Observe blocked contacts changed events
+ val broadcastReceiver = object : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ recyclerView.adapter!!.notifyDataSetChanged()
}
}
+ this.broadcastReceiver = broadcastReceiver
+ LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged"))
+ // Clear all data if this is a secondary device
+ if (TextSecurePreferences.getMasterHexEncodedPublicKey(this) != null) {
+ TextSecurePreferences.setWasUnlinked(this, true)
+ ApplicationContext.getInstance(this).clearData()
+ }
}
override fun onResume() {
super.onResume()
+ if (TextSecurePreferences.getLocalNumber(this) == null) { return; } // This can be the case after a secondary device is auto-cleared
profileButton.update()
val isMasterDevice = (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == null)
val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this)
if (hasViewedSeed || !isMasterDevice) {
seedReminderView.visibility = View.GONE
}
- if (!TextSecurePreferences.hasSeenPNModeSheet(this)) {
- val bottomSheet = PNModeBottomSheet()
- bottomSheet.onConfirmTapped = { isUsingFCM ->
- TextSecurePreferences.setHasSeenPNModeSheet(this, true)
- TextSecurePreferences.setIsUsingFCM(this, isUsingFCM)
- ApplicationContext.getInstance(this).registerForFCMIfNeeded(true)
- bottomSheet.dismiss()
+ val hasSeenMultiDeviceRemovalSheet = TextSecurePreferences.getHasSeenMultiDeviceRemovalSheet(this)
+ if (!hasSeenMultiDeviceRemovalSheet) {
+ TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(this)
+ val userPublicKey = TextSecurePreferences.getLocalNumber(this)
+ val deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey)
+ if (deviceLinks.isNotEmpty()) {
+ val bottomSheet = MultiDeviceRemovalBottomSheet()
+ bottomSheet.onOKTapped = {
+ bottomSheet.dismiss()
+ }
+ bottomSheet.onLinkTapped = {
+ bottomSheet.dismiss()
+ val url = "https://getsession.org/faq"
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ startActivity(intent)
+ }
+ bottomSheet.show(supportFragmentManager, bottomSheet.tag)
}
- bottomSheet.onSkipTapped = {
- TextSecurePreferences.setHasSeenPNModeSheet(this, true)
- bottomSheet.dismiss()
- }
- bottomSheet.show(supportFragmentManager, bottomSheet.tag)
}
}
@@ -222,6 +225,14 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
createNewPrivateChat()
}
}
+
+ override fun onDestroy() {
+ val broadcastReceiver = this.broadcastReceiver
+ if (broadcastReceiver != null) {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)
+ }
+ super.onDestroy()
+ }
// endregion
// region Updating
@@ -243,7 +254,104 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
}
override fun onLongConversationClick(view: ConversationView) {
- // Do nothing
+ 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
+ }
+ dialog.create().show()
}
private fun openConversation(thread: ThreadRecord) {
@@ -281,92 +389,5 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val intent = Intent(this, JoinPublicChatActivity::class.java)
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)
- 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
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/activities/LandingActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/LandingActivity.kt
index 4d08c6725b..8cd45d95f0 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/LandingActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/LandingActivity.kt
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialog
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.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
@@ -27,12 +27,11 @@ import org.whispersystems.curve25519.Curve25519
import org.whispersystems.libsignal.ecc.Curve
import org.whispersystems.libsignal.ecc.ECKeyPair
import org.whispersystems.libsignal.util.KeyHelper
-import org.whispersystems.signalservice.loki.protocol.friendrequests.FriendRequestProtocol
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol
-import org.whispersystems.signalservice.loki.protocol.syncmessages.SyncMessagesProtocol
+import org.whispersystems.signalservice.loki.protocol.shelved.syncmessages.SyncMessagesProtocol
import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
@@ -45,7 +44,7 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
fakeChatView.startAnimating()
registerButton.setOnClickListener { register() }
restoreButton.setOnClickListener { restore() }
- linkButton.setOnClickListener { linkDevice() }
+// linkButton.setOnClickListener { linkDevice() }
if (TextSecurePreferences.getWasUnlinked(this)) {
Toast.makeText(this, R.string.activity_landing_device_unlinked_dialog_title, Toast.LENGTH_LONG).show()
}
@@ -110,11 +109,10 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
val userDB = DatabaseFactory.getLokiUserDatabase(this)
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
- val sessionResetImpl = LokiSessionResetImplementation(this)
- FriendRequestProtocol.configureIfNeeded(apiDB, userPublicKey)
+ val sessionResetImpl = SessionResetImplementation(this)
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
- org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.configureIfNeeded(apiDB)
+ org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol.configureIfNeeded(apiDB)
SessionManagementProtocol.configureIfNeeded(sessionResetImpl, threadDB, application)
SyncMessagesProtocol.configureIfNeeded(apiDB, userPublicKey)
application.setUpP2PAPIIfNeeded()
@@ -124,13 +122,13 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
linkDeviceDialog.show(supportFragmentManager, "Link Device Dialog")
AsyncTask.execute {
retryIfNeeded(8) {
- MultiDeviceProtocol.sendDeviceLinkMessage(this@LandingActivity, deviceLink.masterHexEncodedPublicKey, deviceLink)
+ MultiDeviceProtocol.sendDeviceLinkMessage(this@LandingActivity, deviceLink.masterPublicKey, deviceLink)
}
}
}
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
- TextSecurePreferences.setMasterHexEncodedPublicKey(this, deviceLink.masterHexEncodedPublicKey)
+ TextSecurePreferences.setMasterHexEncodedPublicKey(this, deviceLink.masterPublicKey)
val intent = Intent(this, HomeActivity::class.java)
show(intent)
finish()
@@ -145,5 +143,7 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
TextSecurePreferences.removeLocalNumber(this)
TextSecurePreferences.setHasSeenWelcomeScreen(this, false)
TextSecurePreferences.setPromptedPushRegistration(this, false)
+ val application = ApplicationContext.getInstance(this)
+ application.stopPolling()
}
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesActivity.kt
index f36bee9179..041e8a8efa 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesActivity.kt
@@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.loki.utilities.recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress
-import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
+import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
import java.util.*
import kotlin.concurrent.schedule
@@ -126,30 +126,30 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
val apiDB = DatabaseFactory.getLokiAPIDatabase(this)
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) {
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)
deviceLinks.forEach { deviceLink ->
// 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
val unlinkingRequest = SignalServiceDataMessage.newBuilder()
.withTimestamp(System.currentTimeMillis())
- .asUnlinkingRequest(true)
+ .asDeviceUnlinkingRequest(true)
val messageSender = ApplicationContext.getInstance(this@LinkedDevicesActivity).communicationModule.provideSignalMessageSender()
- val address = SignalServiceAddress(deviceLink.slaveHexEncodedPublicKey)
+ val address = SignalServiceAddress(deviceLink.slavePublicKey)
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
} catch (e: Exception) {
Log.d("Loki", "Failed to send unlinking request due to error: $e.")
throw e
}
- DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)
+ DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slavePublicKey)
val sessionStore = TextSecureSessionStore(this@LinkedDevicesActivity)
- sessionStore.deleteAllSessions(deviceLink.slaveHexEncodedPublicKey)
+ sessionStore.deleteAllSessions(deviceLink.slavePublicKey)
}
LoaderManager.getInstance(this).restartLoader(0, null, this)
Toast.makeText(this, R.string.activity_linked_devices_unlinking_successful_message, Toast.LENGTH_LONG).show()
diff --git a/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesLoader.kt b/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesLoader.kt
index 14f6573e4c..785566b2d7 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesLoader.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesLoader.kt
@@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.AsyncLoader
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import java.io.File
class LinkedDevicesLoader(context: Context) : AsyncLoader>(context) {
diff --git a/src/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt
index 763c3cf6ba..5e4275d946 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt
@@ -3,10 +3,14 @@ package org.thoughtcrime.securesms.loki.activities
import android.app.AlertDialog
import android.content.Intent
import android.graphics.drawable.TransitionDrawable
+import android.net.Uri
import android.os.Bundle
import android.support.annotation.DrawableRes
+import android.view.Menu
+import android.view.MenuItem
import android.view.View
import android.widget.LinearLayout
+import android.widget.Toast
import kotlinx.android.synthetic.main.activity_display_name.registerButton
import kotlinx.android.synthetic.main.activity_pn_mode.*
import network.loki.messenger.R
@@ -28,6 +32,11 @@ class PNModeActivity : BaseActionBarActivity() {
backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
registerButton.setOnClickListener { register() }
}
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.menu_pn_mode, menu)
+ return true
+ }
// endregion
// region Animation
@@ -39,6 +48,25 @@ class PNModeActivity : BaseActionBarActivity() {
// endregion
// region Interaction
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ val id = item.itemId
+ when(id) {
+ R.id.learnMoreButton -> learnMore()
+ else -> { /* Do nothing */ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun learnMore() {
+ try {
+ val url = "https://getsession.org/faq/#privacy"
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ startActivity(intent)
+ } catch (e: Exception) {
+ Toast.makeText(this, R.string.invalid_url, Toast.LENGTH_SHORT).show()
+ }
+ }
+
private fun toggleFCM() {
when (selectedOptionView) {
null -> {
@@ -87,7 +115,7 @@ class PNModeActivity : BaseActionBarActivity() {
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setPromptedPushRegistration(this, true)
TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView))
- TextSecurePreferences.setHasSeenPNModeSheet(this, true) // Shouldn't be shown to users who've done the new onboarding
+ TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(this)
val application = ApplicationContext.getInstance(this)
application.setUpStorageAPIIfNeeded()
application.setUpP2PAPIIfNeeded()
diff --git a/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt
index 929e5de266..1133865cad 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt
@@ -121,6 +121,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
titleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.medium_font_size))
titleTextView.text = title
+ titleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
val titleContainer = LinearLayout(this)
titleContainer.orientation = LinearLayout.VERTICAL
titleContainer.addView(titleTextView)
@@ -133,6 +134,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
subtitleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
subtitleTextView.text = subtitle
+ subtitleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
titleContainer.addView(subtitleTextView)
}
return mainContainer
diff --git a/src/org/thoughtcrime/securesms/loki/activities/SelectContactsLoader.kt b/src/org/thoughtcrime/securesms/loki/activities/SelectContactsLoader.kt
index 59a7ef6ad1..44a3d8cd1f 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/SelectContactsLoader.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/SelectContactsLoader.kt
@@ -8,9 +8,8 @@ class SelectContactsLoader(context: Context) : AsyncLoader>(context
override fun loadInBackground(): List {
val contacts = ContactUtilities.getAllContacts(context)
- // Only show the master devices of the users we are friends with
return contacts.filter { contact ->
- !contact.recipient.isGroupRecipient && contact.isFriend && !contact.isOurDevice && !contact.isSlave
+ !contact.recipient.isGroupRecipient && !contact.isOurDevice && !contact.isSlave
}.map {
it.recipient.address.toPhoneString()
}
diff --git a/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt
index 91cf61b401..79cf26157f 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt
@@ -42,7 +42,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.crypto.ProfileCipher
import org.whispersystems.signalservice.api.util.StreamDetails
-import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
+import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
import java.io.ByteArrayInputStream
import java.io.File
import java.security.SecureRandom
@@ -73,7 +73,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
showQRCodeButton.setOnClickListener { showQRCode() }
glide = GlideApp.with(this)
profilePictureView.glide = glide
- profilePictureView.hexEncodedPublicKey = hexEncodedPublicKey
+ profilePictureView.publicKey = hexEncodedPublicKey
profilePictureView.isLarge = true
profilePictureView.update()
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
@@ -83,16 +83,16 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
copyButton.setOnClickListener { copyPublicKey() }
shareButton.setOnClickListener { sharePublicKey() }
val isMasterDevice = (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == null)
+ linkedDevicesButtonTopSeparator.visibility = View.GONE
+ linkedDevicesButton.visibility = View.GONE
if (!isMasterDevice) {
- linkedDevicesButtonTopSeparator.visibility = View.GONE
- linkedDevicesButton.visibility = View.GONE
seedButtonTopSeparator.visibility = View.GONE
seedButton.visibility = View.GONE
}
privacyButton.setOnClickListener { showPrivacySettings() }
notificationsButton.setOnClickListener { showNotificationSettings() }
chatsButton.setOnClickListener { showChatSettings() }
- linkedDevicesButton.setOnClickListener { showLinkedDevices() }
+// linkedDevicesButton.setOnClickListener { showLinkedDevices() }
seedButton.setOnClickListener { showSeed() }
clearAllDataButton.setOnClickListener { clearAllData() }
versionTextView.text = String.format(getString(R.string.version_s), BuildConfig.VERSION_NAME)
@@ -151,7 +151,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
val promises = mutableListOf>()
val displayName = displayNameToBeUploaded
if (displayName != null) {
- val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
+ val publicChatAPI = ApplicationContext.getInstance(this).publicChatAPI
if (publicChatAPI != null) {
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
promises.addAll(servers.map { publicChatAPI.setDisplayName(displayName, it) })
@@ -162,7 +162,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey)
if (isUpdatingProfilePicture && profilePicture != null) {
- val storageAPI = LokiFileServerAPI.shared
+ val storageAPI = FileServerAPI.shared
val deferred = deferred()
AsyncTask.execute {
val stream = StreamDetails(ByteArrayInputStream(profilePicture), "image/jpeg", profilePicture.size.toLong())
@@ -202,9 +202,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
if (displayName.isEmpty()) {
return Toast.makeText(this, R.string.activity_settings_display_name_missing_error, Toast.LENGTH_SHORT).show()
}
- if (!displayName.matches(Regex("[a-zA-Z0-9_]+"))) {
- return Toast.makeText(this, R.string.activity_settings_invalid_display_name_error, Toast.LENGTH_SHORT).show()
- }
if (displayName.toByteArray().size > ProfileCipher.NAME_PADDED_LENGTH) {
return Toast.makeText(this, R.string.activity_settings_display_name_too_long_error, Toast.LENGTH_SHORT).show()
}
diff --git a/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt b/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt
index 7265083dc5..46ffe29abb 100644
--- a/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt
+++ b/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt
@@ -9,13 +9,13 @@ import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
import org.thoughtcrime.securesms.service.PersistentAlarmManagerListener
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
-import org.whispersystems.signalservice.loki.api.LokiAPI
+import org.whispersystems.signalservice.loki.api.SnodeAPI
import java.util.concurrent.TimeUnit
class BackgroundPollWorker : PersistentAlarmManagerListener() {
companion object {
- private val pollInterval = TimeUnit.MINUTES.toMillis(15)
+ private val pollInterval = TimeUnit.MINUTES.toMillis(20)
@JvmStatic
fun schedule(context: Context) {
@@ -29,14 +29,14 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
override fun onAlarm(context: Context, scheduledTime: Long): Long {
if (scheduledTime != 0L) {
- if (TextSecurePreferences.isUsingFCM(context)) {
+ if (!TextSecurePreferences.isUsingFCM(context)) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
try {
val applicationContext = context.applicationContext as ApplicationContext
val broadcaster = applicationContext.broadcaster
- LokiAPI.configureIfNeeded(userPublicKey, lokiAPIDatabase, broadcaster)
- LokiAPI.shared.getMessages().map { messages ->
+ SnodeAPI.configureIfNeeded(userPublicKey, lokiAPIDatabase, broadcaster)
+ SnodeAPI.shared.getMessages().map { messages ->
messages.forEach {
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
}
@@ -47,7 +47,7 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
}
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value }
for (openGroup in openGroups) {
- val poller = LokiPublicChatPoller(context, openGroup)
+ val poller = PublicChatPoller(context, openGroup)
poller.stop()
poller.pollForNewMessages()
}
diff --git a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt
index 1180d0e68b..39a9c46078 100644
--- a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt
+++ b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt
@@ -5,14 +5,14 @@ import okhttp3.*
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.logging.Log
import org.whispersystems.signalservice.internal.util.JsonUtil
-import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement
+import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement
import java.io.IOException
object LokiPushNotificationManager {
private val connection = OkHttpClient()
private val server by lazy {
- LokiPushNotificationAcknowledgement.shared.server
+ PushNotificationAcknowledgement.shared.server
}
private const val tokenExpirationInterval = 12 * 60 * 60 * 1000
@@ -47,11 +47,11 @@ object LokiPushNotificationManager {
}
@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 lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context)
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 body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
val request = Request.Builder().url(url).post(body).build()
diff --git a/src/org/thoughtcrime/securesms/loki/api/LokiPublicChatManager.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt
similarity index 75%
rename from src/org/thoughtcrime/securesms/loki/api/LokiPublicChatManager.kt
rename to src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt
index 29b6feac52..8095731318 100644
--- a/src/org/thoughtcrime/securesms/loki/api/LokiPublicChatManager.kt
+++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt
@@ -12,19 +12,37 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.util.TextSecurePreferences
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) {
- private var chats = mutableMapOf()
- private val pollers = mutableMapOf()
+class PublicChatManager(private val context: Context) {
+ private var chats = mutableMapOf()
+ private val pollers = mutableMapOf()
private val observers = mutableMapOf()
private var isPolling = false
+ public fun areAllCaughtUp():Boolean {
+ var areAllCaughtUp = true
+ refreshChatsAndPollers()
+ for ((threadID, chat) in chats) {
+ val poller = pollers[threadID] ?: PublicChatPoller(context, chat)
+ areAllCaughtUp = areAllCaughtUp && poller.isCaughtUp
+ }
+ return areAllCaughtUp
+ }
+
+ public fun markAllAsNotCaughtUp() {
+ refreshChatsAndPollers()
+ for ((threadID, chat) in chats) {
+ val poller = pollers[threadID] ?: PublicChatPoller(context, chat)
+ poller.isCaughtUp = false
+ }
+ }
+
public fun startPollersIfNeeded() {
refreshChatsAndPollers()
for ((threadId, chat) in chats) {
- val poller = pollers[threadId] ?: LokiPublicChatPoller(context, chat)
+ val poller = pollers[threadId] ?: PublicChatPoller(context, chat)
poller.startIfNeeded()
listenToThreadDeletion(threadId)
if (!pollers.containsKey(threadId)) { pollers[threadId] = poller }
@@ -37,8 +55,8 @@ class LokiPublicChatManager(private val context: Context) {
isPolling = false
}
- public fun addChat(server: String, channel: Long): Promise {
- val groupChatAPI = ApplicationContext.getInstance(context).lokiPublicChatAPI ?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!"))
+ public fun addChat(server: String, channel: Long): Promise {
+ val groupChatAPI = ApplicationContext.getInstance(context).publicChatAPI ?: return Promise.ofFail(IllegalStateException("LokiPublicChatAPI is not set!"))
return groupChatAPI.getAuthToken(server).bind {
groupChatAPI.getChannelInfo(channel, server)
}.map {
@@ -46,8 +64,8 @@ class LokiPublicChatManager(private val context: Context) {
}
}
- public fun addChat(server: String, channel: Long, name: String): LokiPublicChat {
- val chat = LokiPublicChat(channel, server, name, true)
+ public fun addChat(server: String, channel: Long, name: String): PublicChat {
+ val chat = PublicChat(channel, server, name, true)
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
// Create the group if we don't have one
if (threadID < 0) {
@@ -58,7 +76,7 @@ class LokiPublicChatManager(private val context: Context) {
// Set our name on the server
val displayName = TextSecurePreferences.getProfileName(context)
if (!TextUtils.isEmpty(displayName)) {
- ApplicationContext.getInstance(context).lokiPublicChatAPI?.setDisplayName(displayName, server)
+ ApplicationContext.getInstance(context).publicChatAPI?.setDisplayName(displayName, server)
}
// Start polling
Util.runOnMain{ startPollersIfNeeded() }
diff --git a/src/org/thoughtcrime/securesms/loki/api/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt
similarity index 82%
rename from src/org/thoughtcrime/securesms/loki/api/LokiPublicChatPoller.kt
rename to src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt
index abefd33c40..7aaf2322f7 100644
--- a/src/org/thoughtcrime/securesms/loki/api/LokiPublicChatPoller.kt
+++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt
@@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobs.PushDecryptJob
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
+import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol
import org.thoughtcrime.securesms.loki.utilities.successBackground
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
@@ -22,29 +23,29 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress
-import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatAPI
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChatMessage
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
-import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
+import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChatAPI
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChatMessage
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import java.security.MessageDigest
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 var hasStarted = false
+ public var isCaughtUp = false
// region Convenience
private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
private var displayNameUpdatees = setOf()
- private val api: LokiPublicChatAPI
+ private val api: PublicChatAPI
get() = {
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
- LokiPublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
+ PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase)
}()
// endregion
@@ -111,16 +112,16 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
// endregion
// region Polling
- private fun getDataMessage(message: LokiPublicChatMessage): SignalServiceDataMessage {
+ private fun getDataMessage(message: PublicChatMessage): SignalServiceDataMessage {
val id = group.id.toByteArray()
val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.PUBLIC_CHAT, null, null, null, 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 {
null
}
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(
attachment.serverID,
attachment.contentType,
@@ -134,7 +135,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
Optional.fromNullable(attachment.caption),
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()
if (linkPreview != null) {
val attachment = SignalServiceAttachmentPointer(
@@ -156,16 +157,16 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
}
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
- val masterHexEncodedPublicKey = MultiDeviceProtocol.shared.getMasterDevice(message.hexEncodedPublicKey)
+ val masterHexEncodedPublicKey = MultiDeviceProtocol.shared.getMasterDevice(message.senderPublicKey)
if (masterHexEncodedPublicKey == null) {
- val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
- DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)
+ val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})"
+ DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName)
}
- val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.hexEncodedPublicKey
+ val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.senderPublicKey
val serviceDataMessage = getDataMessage(message)
- val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, 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) {
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
} else {
@@ -181,22 +182,16 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
database.setProfileKey(senderAsRecipient, profileKey)
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: LokiPublicChatMessage) {
+ fun processOutgoingMessage(message: PublicChatMessage) {
val messageServerID = message.serverID ?: return
val isDuplicate = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID) != null
if (isDuplicate) { return }
if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { return }
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val dataMessage = getDataMessage(message)
+ SessionMetaProtocol.dropFromTimestampCacheIfNeeded(dataMessage.timestamp)
val transcript = SentTranscriptMessage(userHexEncodedPublicKey, dataMessage.timestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(userHexEncodedPublicKey, false))
transcript.messageServerID = messageServerID
if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) {
@@ -205,7 +200,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript)
}
// 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.senderPublicKey), false)
if (recipient.isUserMasterDevice && message.profilePicture != null) {
val profileKey = message.profilePicture!!.profileKey
val url = message.profilePicture!!.url
@@ -221,34 +216,39 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
var uniqueDevices = setOf()
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
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
- api.getMessages(group.channel, group.server).bind(LokiPublicChatAPI.sharedContext) { messages ->
+ api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages ->
+ /*
if (messages.isNotEmpty()) {
// We need to fetch the device mapping for any devices we don't have
- uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet()
- val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && LokiFileServerAPI.shared.hasDeviceLinkCacheExpired(hexEncodedPublicKey = it) }
+ uniqueDevices = messages.map { it.senderPublicKey }.toSet()
+ val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && FileServerAPI.shared.hasDeviceLinkCacheExpired(publicKey = it) }
if (devicesToUpdate.isNotEmpty()) {
- return@bind LokiFileServerAPI.shared.getDeviceLinks(devicesToUpdate.toSet()).then { messages }
+ return@bind FileServerAPI.shared.getDeviceLinks(devicesToUpdate.toSet()).then { messages }
}
}
+ */
Promise.of(messages)
}.successBackground {
+ /*
val newDisplayNameUpdatees = uniqueDevices.mapNotNull {
// This will return null if the current device is a master device
MultiDeviceProtocol.shared.getMasterDevice(it)
}.toSet()
// Fetch the display names of the master devices
displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees)
+ */
}.successBackground { messages ->
// Process messages in the background
messages.forEach { message ->
- if (userDevices.contains(message.hexEncodedPublicKey)) {
+ if (userDevices.contains(message.senderPublicKey)) {
processOutgoingMessage(message)
} else {
processIncomingMessage(message)
}
}
+ isCaughtUp = true
}.fail {
Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.")
}
diff --git a/src/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt b/src/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt
index bdc55d10dc..a5a6fabd90 100644
--- a/src/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt
+++ b/src/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt
@@ -7,15 +7,15 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.logging.Log
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
import org.whispersystems.signalservice.internal.util.Base64
-import org.whispersystems.signalservice.loki.api.LokiMessageWrapper
+import org.whispersystems.signalservice.loki.api.MessageWrapper
class PushNotificationService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d("Loki", "New FCM token: $token.")
- val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return
- LokiPushNotificationManager.register(token, userHexEncodedPublicKey, this, false)
+ val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return
+ LokiPushNotificationManager.register(token, userPublicKey, this, false)
}
override fun onMessageReceived(message: RemoteMessage) {
@@ -23,7 +23,7 @@ class PushNotificationService : FirebaseMessagingService() {
val data = base64EncodedData?.let { Base64.decode(it) }
if (data != null) {
try {
- val envelope = LokiMessageWrapper.unwrap(data)
+ val envelope = MessageWrapper.unwrap(data)
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true)
} catch (e: Exception) {
Log.d("Loki", "Failed to unwrap data for message.")
diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt
index 5f953a4b7b..fefcfcc211 100644
--- a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt
+++ b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt
@@ -8,15 +8,18 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.TextSecurePreferences
-import org.whispersystems.signalservice.loki.api.LokiAPITarget
+import org.whispersystems.signalservice.loki.api.Snode
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol {
private val userPublicKey get() = TextSecurePreferences.getLocalNumber(context)
companion object {
+ // Shared
+ private val publicKey = "public_key"
+ private val timestamp = "timestamp"
// Snode pool cache
private val snodePoolCache = "loki_snode_pool_cache"
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);"
// 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"
- @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
private val lastMessageHashValueCache = "loki_api_last_message_hash_value_cache"
private val target = "target"
@@ -59,25 +62,36 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
@JvmStatic val createLastDeletionServerIDCacheCommand = "CREATE TABLE $lastDeletionServerIDCache ($lastDeletionServerIDCacheIndex STRING PRIMARY KEY, $lastDeletionServerID INTEGER DEFAULT 0);"
// Device link cache
private val deviceLinkCache = "loki_pairing_authorisation_cache"
- private val masterHexEncodedPublicKey = "primary_device"
- private val slaveHexEncodedPublicKey = "secondary_device"
+ private val masterPublicKey = "primary_device"
+ private val slavePublicKey = "secondary_device"
private val requestSignature = "request_signature"
private val authorizationSignature = "grant_signature"
- @JvmStatic val createDeviceLinkCacheCommand = "CREATE TABLE $deviceLinkCache ($masterHexEncodedPublicKey TEXT, $slaveHexEncodedPublicKey TEXT, " +
- "$requestSignature TEXT NULLABLE DEFAULT NULL, $authorizationSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($masterHexEncodedPublicKey, $slaveHexEncodedPublicKey));"
+ @JvmStatic val createDeviceLinkCacheCommand = "CREATE TABLE $deviceLinkCache ($masterPublicKey TEXT, $slavePublicKey TEXT, " +
+ "$requestSignature TEXT NULLABLE DEFAULT NULL, $authorizationSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($masterPublicKey, $slavePublicKey));"
// User count cache
private val userCountCache = "loki_user_count_cache"
private val publicChatID = "public_chat_id"
private val userCount = "user_count"
@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);"
+ // Open group public keys
+ private val openGroupPublicKeyDB = "open_group_public_keys"
+ @JvmStatic val createOpenGroupPublicKeyDBCommand = "CREATE TABLE $openGroupPublicKeyDB ($server STRING PRIMARY KEY, $publicKey INTEGER DEFAULT 0);"
+
+
+
+ // region Deprecated
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 STRING);"
+ // endregion
}
- override fun getSnodePool(): Set {
+ override fun getSnodePool(): Set {
val database = databaseHelper.readableDatabase
return database.get(snodePoolCache, "${Companion.dummyKey} = ?", wrap("dummy_key")) { cursor ->
val snodePoolAsString = cursor.getString(cursor.getColumnIndexOrThrow(snodePool))
@@ -87,12 +101,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
- LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
+ Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
}
}?.toSet() ?: setOf()
}
- override fun setSnodePool(newValue: Set) {
+ override fun setSnodePool(newValue: Set) {
val database = databaseHelper.writableDatabase
val snodePoolAsString = newValue.joinToString(", ") { snode ->
var string = "${snode.address}-${snode.port}"
@@ -106,9 +120,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
database.insertOrUpdate(snodePoolCache, row, "${Companion.dummyKey} = ?", wrap("dummy_key"))
}
- override fun getOnionRequestPaths(): List> {
+ override fun getOnionRequestPaths(): List> {
val database = databaseHelper.readableDatabase
- fun get(indexPath: String): LokiAPITarget? {
+ fun get(indexPath: String): Snode? {
return database.get(onionRequestPathCache, "${Companion.indexPath} = ?", wrap(indexPath)) { cursor ->
val snodeAsString = cursor.getString(cursor.getColumnIndexOrThrow(snode))
val components = snodeAsString.split("-")
@@ -117,7 +131,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val ed25519Key = components.getOrNull(2)
val x25519Key = components.getOrNull(3)
if (port != null && ed25519Key != null && x25519Key != null) {
- LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
+ Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
} else {
null
}
@@ -139,7 +153,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
delete("1-1"); delete("1-2")
}
- override fun setOnionRequestPaths(newValue: List>) {
+ override fun setOnionRequestPaths(newValue: List>) {
// FIXME: This is a bit of a dirty approach that assumes 2 paths of length 3 each. We should do better than this.
if (newValue.count() != 2) { return }
val path0 = newValue[0]
@@ -147,7 +161,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
if (path0.count() != 3 || path1.count() != 3) { return }
Log.d("Loki", "Persisting onion request paths to database.")
val database = databaseHelper.writableDatabase
- fun set(indexPath: String ,snode: LokiAPITarget) {
+ fun set(indexPath: String ,snode: Snode) {
var snodeAsString = "${snode.address}-${snode.port}"
val keySet = snode.publicKeySet
if (keySet != null) {
@@ -161,9 +175,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
set("1-1", path1[1]); set("1-2", path1[2])
}
- override fun getSwarm(hexEncodedPublicKey: String): Set? {
+ override fun getSwarm(publicKey: String): Set? {
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))
swarmAsString.split(", ").mapNotNull { targetAsString ->
val components = targetAsString.split("-")
@@ -171,12 +185,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
- LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
+ Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
}
}?.toSet()
}
- override fun setSwarm(hexEncodedPublicKey: String, newValue: Set) {
+ override fun setSwarm(publicKey: String, newValue: Set) {
val database = databaseHelper.writableDatabase
val swarmAsString = newValue.joinToString(", ") { target ->
var string = "${target.address}-${target.port}"
@@ -186,21 +200,21 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
}
string
}
- val row = wrap(mapOf(Companion.hexEncodedPublicKey to hexEncodedPublicKey, swarm to swarmAsString))
- database.insertOrUpdate(swarmCache, row, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey))
+ val row = wrap(mapOf(Companion.swarmPublicKey to publicKey, swarm to swarmAsString))
+ database.insertOrUpdate(swarmCache, row, "${Companion.swarmPublicKey} = ?", wrap(publicKey))
}
- override fun getLastMessageHashValue(target: LokiAPITarget): String? {
+ override fun getLastMessageHashValue(snode: Snode): String? {
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))
}
}
- override fun setLastMessageHashValue(target: LokiAPITarget, newValue: String) {
+ override fun setLastMessageHashValue(snode: Snode, newValue: String) {
val database = databaseHelper.writableDatabase
- val row = wrap(mapOf(Companion.target to target.address, lastMessageHashValue to newValue))
- database.insertOrUpdate(lastMessageHashValueCache, row, "${Companion.target} = ?", wrap(target.address))
+ val row = wrap(mapOf(Companion.target to snode.address, lastMessageHashValue to newValue))
+ database.insertOrUpdate(lastMessageHashValueCache, row, "${Companion.target} = ?", wrap(snode.address))
}
override fun getReceivedMessageHashValues(): Set? {
@@ -277,35 +291,44 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
database.delete(lastDeletionServerIDCache,"$lastDeletionServerIDCacheIndex = ?", wrap(index))
}
- override fun getDeviceLinks(hexEncodedPublicKey: String): Set {
+ override fun getDeviceLinks(publicKey: String): Set {
+ return setOf()
+ /*
val database = databaseHelper.readableDatabase
- return database.getAll(deviceLinkCache, "$masterHexEncodedPublicKey = ? OR $slaveHexEncodedPublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) { cursor ->
- val masterHexEncodedPublicKey = cursor.getString(masterHexEncodedPublicKey)
- val slaveHexEncodedPublicKey = cursor.getString(slaveHexEncodedPublicKey)
+ return database.getAll(deviceLinkCache, "$masterPublicKey = ? OR $slavePublicKey = ?", arrayOf( publicKey, publicKey )) { cursor ->
+ val masterHexEncodedPublicKey = cursor.getString(masterPublicKey)
+ val slaveHexEncodedPublicKey = cursor.getString(slavePublicKey)
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)
DeviceLink(masterHexEncodedPublicKey, slaveHexEncodedPublicKey, requestSignature, authorizationSignature)
}.toSet()
+ */
}
- override fun clearDeviceLinks(hexEncodedPublicKey: String) {
+ override fun clearDeviceLinks(publicKey: String) {
+ /*
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) {
+ /*
val database = databaseHelper.writableDatabase
val values = ContentValues()
- values.put(masterHexEncodedPublicKey, deviceLink.masterHexEncodedPublicKey)
- values.put(slaveHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey)
+ values.put(masterPublicKey, deviceLink.masterPublicKey)
+ values.put(slavePublicKey, deviceLink.slavePublicKey)
if (deviceLink.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(deviceLink.requestSignature)) }
if (deviceLink.authorizationSignature != null) { values.put(authorizationSignature, Base64.encodeBytes(deviceLink.authorizationSignature)) }
- database.insertOrUpdate(deviceLinkCache, values, "$masterHexEncodedPublicKey = ? AND $slaveHexEncodedPublicKey = ?", arrayOf( deviceLink.masterHexEncodedPublicKey, deviceLink.slaveHexEncodedPublicKey ))
+ database.insertOrUpdate(deviceLinkCache, values, "$masterPublicKey = ? AND $slavePublicKey = ?", arrayOf( deviceLink.masterPublicKey, deviceLink.slavePublicKey ))
+ */
}
override fun removeDeviceLink(deviceLink: DeviceLink) {
+ /*
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? {
@@ -316,24 +339,50 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
}?.toInt()
}
- override fun setUserCount(userCount: Int, group: Long, server: String) {
+ override fun setUserCount(group: Long, server: String, newValue: Int) {
val database = databaseHelper.writableDatabase
val index = "$server.$group"
- val row = wrap(mapOf(publicChatID to index, Companion.userCount to userCount.toString()))
+ val row = wrap(mapOf(publicChatID to index, Companion.userCount to newValue.toString()))
database.insertOrUpdate(userCountCache, row, "$publicChatID = ?", wrap(index))
}
- override fun getSessionRequestTimestamp(publicKey: String): Long? {
+ override fun getSessionRequestSentTimestamp(publicKey: String): Long? {
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)
}?.toLong()
}
- override fun setSessionRequestTimestamp(publicKey: String, timestamp: Long) {
+ override fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long) {
val database = databaseHelper.writableDatabase
- val row = wrap(mapOf(LokiAPIDatabase.publicKey to publicKey, LokiAPIDatabase.timestamp to timestamp.toString()))
- database.insertOrUpdate(sessionRequestTimestampCache, row, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey))
+ val row = wrap(mapOf(LokiAPIDatabase.publicKey to publicKey, LokiAPIDatabase.timestamp to newValue.toString()))
+ 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, newValue: Long) {
+ val database = databaseHelper.writableDatabase
+ val row = wrap(mapOf(LokiAPIDatabase.publicKey to publicKey, LokiAPIDatabase.timestamp to newValue.toString()))
+ database.insertOrUpdate(sessionRequestProcessedTimestampCache, row, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey))
+ }
+
+ override fun getOpenGroupPublicKey(server: String): String? {
+ val database = databaseHelper.readableDatabase
+ return database.get(openGroupPublicKeyDB, "${LokiAPIDatabase.server} = ?", wrap(server)) { cursor ->
+ cursor.getString(LokiAPIDatabase.publicKey)
+ }
+ }
+
+ override fun setOpenGroupPublicKey(server: String, newValue: String) {
+ val database = databaseHelper.writableDatabase
+ val row = wrap(mapOf(LokiAPIDatabase.server to server, LokiAPIDatabase.publicKey to newValue))
+ database.insertOrUpdate(openGroupPublicKeyDB, row, "${LokiAPIDatabase.server} = ?", wrap(server))
}
}
diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt
index 81adc0be03..675fbf3ce7 100644
--- a/src/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt
+++ b/src/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt
@@ -11,39 +11,38 @@ import org.thoughtcrime.securesms.loki.utilities.getInt
import org.thoughtcrime.securesms.loki.utilities.getString
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
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 {
companion object {
- private val messageFriendRequestTableName = "loki_message_friend_request_database"
- private val messageThreadMappingTableName = "loki_message_thread_mapping_database"
- private val errorMessageTableName = "loki_error_message_database"
+ private val messageIDTable = "loki_message_friend_request_database"
+ private val messageThreadMappingTable = "loki_message_thread_mapping_database"
+ private val errorMessageTable = "loki_error_message_database"
private val messageID = "message_id"
private val serverID = "server_id"
private val friendRequestStatus = "friend_request_status"
private val threadID = "thread_id"
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 createMessageToThreadMappingTableCommand = "CREATE TABLE IF NOT EXISTS $messageThreadMappingTableName ($messageID INTEGER PRIMARY KEY, $threadID INTEGER);"
- @JvmStatic val createErrorMessageTableCommand = "CREATE TABLE IF NOT EXISTS $errorMessageTableName ($messageID INTEGER PRIMARY KEY, $errorMessage STRING);"
+ @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 $messageThreadMappingTable ($messageID INTEGER PRIMARY KEY, $threadID INTEGER);"
+ @JvmStatic val createErrorMessageTableCommand = "CREATE TABLE IF NOT EXISTS $errorMessageTable ($messageID INTEGER PRIMARY KEY, $errorMessage STRING);"
}
- override fun getQuoteServerID(quoteID: Long, quoteeHexEncodedPublicKey: String): Long? {
- val message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quoteID, Address.fromSerialized(quoteeHexEncodedPublicKey))
+ override fun getQuoteServerID(quoteID: Long, quoteePublicKey: String): Long? {
+ val message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quoteID, Address.fromSerialized(quoteePublicKey))
return if (message != null) getServerID(message.getId()) else null
}
fun getServerID(messageID: Long): Long? {
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)
}?.toLong()
}
fun getMessageID(serverID: Long): Long? {
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)
}?.toLong()
}
@@ -53,12 +52,12 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
val contentValues = ContentValues(2)
contentValues.put(Companion.messageID, messageID)
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 {
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)
}?.toLong() ?: -1L
}
@@ -68,38 +67,12 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
val contentValues = ContentValues(2)
contentValues.put(Companion.messageID, messageID)
contentValues.put(Companion.threadID, threadID)
- database.insertOrUpdate(messageThreadMappingTableName, 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
+ database.insertOrUpdate(messageThreadMappingTable, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
}
fun getErrorMessage(messageID: Long): String? {
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)
}
}
@@ -109,6 +82,6 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
val contentValues = ContentValues(2)
contentValues.put(Companion.messageID, messageID)
contentValues.put(Companion.errorMessage, errorMessage)
- database.insertOrUpdate(errorMessageTableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
+ database.insertOrUpdate(errorMessageTable, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() ))
}
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiPreKeyBundleDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiPreKeyBundleDatabase.kt
index 7fcb034f85..6e89f77a40 100644
--- a/src/org/thoughtcrime/securesms/loki/database/LokiPreKeyBundleDatabase.kt
+++ b/src/org/thoughtcrime/securesms/loki/database/LokiPreKeyBundleDatabase.kt
@@ -26,8 +26,8 @@ import org.whispersystems.signalservice.loki.database.LokiPreKeyBundleDatabasePr
class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiPreKeyBundleDatabaseProtocol {
companion object {
- private val tableName = "loki_pre_key_bundle_database"
- private val hexEncodedPublicKey = "public_key"
+ private val table = "loki_pre_key_bundle_database"
+ private val publicKey = "public_key"
private val preKeyID = "pre_key_id"
private val preKeyPublic = "pre_key_public"
private val signedPreKeyID = "signed_pre_key_id"
@@ -36,16 +36,16 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
private val identityKey = "identity_key"
private val deviceID = "device_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," +
"$signedPreKeySignature TEXT," + "$identityKey TEXT NOT NULL," + "$deviceID INTEGER," + "$registrationID INTEGER" + ");"
}
- fun generatePreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? {
+ fun generatePreKeyBundle(publicKey: String): PreKeyBundle? {
var failureCount = 0
while (failureCount < 3) {
try {
- val preKey = generatePreKeyBundle(hexEncodedPublicKey, failureCount > 0) ?: return null
+ val preKey = generatePreKeyBundle(publicKey, failureCount > 0) ?: return null
// Verify the bundle is correct
if (!Curve.verifySignature(preKey.identityKey.publicKey, preKey.signedPreKey.serialize(), preKey.signedPreKeySignature)) {
throw InvalidKeyException()
@@ -55,19 +55,19 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
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
}
- private fun generatePreKeyBundle(hexEncodedPublicKey: String, forceClean: Boolean): PreKeyBundle? {
- if (hexEncodedPublicKey.isEmpty()) return null
+ private fun generatePreKeyBundle(publicKey: String, forceClean: Boolean): PreKeyBundle? {
+ if (publicKey.isEmpty()) return null
var registrationID = TextSecurePreferences.getLocalRegistrationId(context)
if (registrationID == 0) {
registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(context, registrationID)
}
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)
if (!forceClean && TextSecurePreferences.isSignedPreKeyRegistered(context)) {
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)
}
- override fun getPreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? {
+ override fun getPreKeyBundle(publicKey: String): PreKeyBundle? {
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 deviceID = cursor.getInt(deviceID)
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 values = ContentValues(9)
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(signedPreKeySignature, Base64.encodeBytes(preKeyBundle.signedPreKeySignature))
values.put(identityKey, Base64.encodeBytes(preKeyBundle.identityKey.serialize()))
- values.put(Companion.hexEncodedPublicKey, hexEncodedPublicKey)
- database.insertOrUpdate(tableName, values, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ))
+ values.put(Companion.publicKey, publicKey)
+ database.insertOrUpdate(table, values, "${Companion.publicKey} = ?", arrayOf( publicKey ))
}
- override fun removePreKeyBundle(hexEncodedPublicKey: String) {
+ override fun removePreKeyBundle(publicKey: String) {
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
var cursor: Cursor? = null
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
} catch (e: Exception) {
false
diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiPreKeyRecordDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiPreKeyRecordDatabase.kt
index 934368c085..ab92ee50d4 100644
--- a/src/org/thoughtcrime/securesms/loki/database/LokiPreKeyRecordDatabase.kt
+++ b/src/org/thoughtcrime/securesms/loki/database/LokiPreKeyRecordDatabase.kt
@@ -14,38 +14,38 @@ import org.whispersystems.signalservice.loki.database.LokiPreKeyRecordDatabasePr
class LokiPreKeyRecordDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiPreKeyRecordDatabaseProtocol {
companion object {
- private val tableName = "loki_pre_key_record_database"
- private val hexEncodedPublicKey = "public_key"
+ private val table = "loki_pre_key_record_database"
+ private val publicKey = "public_key"
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
- 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
- return database.get(tableName, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey )) { cursor ->
+ return database.get(table, "${Companion.publicKey} = ?", arrayOf( publicKey )) { cursor ->
val preKeyID = cursor.getInt(preKeyID)
PreKeyUtil.loadPreKey(context, preKeyID)
}
}
- fun getOrCreatePreKeyRecord(hexEncodedPublicKey: String): PreKeyRecord {
- return getPreKeyRecord(hexEncodedPublicKey) ?: generateAndStorePreKeyRecord(hexEncodedPublicKey)
+ fun getOrCreatePreKeyRecord(publicKey: String): PreKeyRecord {
+ return getPreKeyRecord(publicKey) ?: generateAndStorePreKeyRecord(publicKey)
}
- private fun generateAndStorePreKeyRecord(hexEncodedPublicKey: String): PreKeyRecord {
+ private fun generateAndStorePreKeyRecord(publicKey: String): PreKeyRecord {
val records = PreKeyUtil.generatePreKeyRecords(context, 1)
PreKeyUtil.storePreKeyRecords(context, records)
val record = records.first()
val database = databaseHelper.writableDatabase
val values = ContentValues(2)
- values.put(Companion.hexEncodedPublicKey, hexEncodedPublicKey)
+ values.put(Companion.publicKey, publicKey)
values.put(preKeyID, record.id)
- database.insertOrUpdate(tableName, values, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ))
+ database.insertOrUpdate(table, values, "${Companion.publicKey} = ?", arrayOf( publicKey ))
return record
}
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt
index a2e47425e2..5a4697b00c 100644
--- a/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt
+++ b/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt
@@ -11,27 +11,24 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.recipients.Recipient
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.loki.api.opengroups.LokiPublicChat
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
import org.whispersystems.signalservice.loki.database.LokiThreadDatabaseProtocol
-import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
var delegate: LokiThreadDatabaseDelegate? = null
companion object {
- private val friendRequestTableName = "loki_thread_friend_request_database"
- private val sessionResetTableName = "loki_thread_session_reset_database"
- val publicChatTableName = "loki_public_chat_database"
+ private val sessionResetTable = "loki_thread_session_reset_database"
+ val publicChatTable = "loki_public_chat_database"
val threadID = "thread_id"
private val friendRequestStatus = "friend_request_status"
private val sessionResetStatus = "session_reset_status"
val publicChat = "public_chat"
- @JvmStatic val createFriendRequestTableCommand = "CREATE TABLE $friendRequestTableName ($threadID INTEGER PRIMARY KEY, $friendRequestStatus INTEGER DEFAULT 0);"
- @JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTableName ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
- @JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTableName ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
+ @JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTable ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
+ @JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
}
override fun getThreadID(hexEncodedPublicKey: String): Long {
@@ -44,74 +41,40 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
return DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID)
}
- fun getFriendRequestStatus(threadID: Long): LokiThreadFriendRequestStatus {
- 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 {
+ fun getSessionResetStatus(hexEncodedPublicKey: String): SessionResetStatus {
val threadID = getThreadID(hexEncodedPublicKey)
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)
}
return if (result != null) {
- LokiSessionResetStatus.values().first { it.rawValue == result }
+ SessionResetStatus.values().first { it.rawValue == result }
} else {
- LokiSessionResetStatus.NONE
+ SessionResetStatus.NONE
}
}
- fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: LokiSessionResetStatus) {
+ fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: SessionResetStatus) {
val threadID = getThreadID(hexEncodedPublicKey)
val database = databaseHelper.writableDatabase
val contentValues = ContentValues(2)
contentValues.put(Companion.threadID, threadID)
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()
notifyConversationListeners(threadID)
}
- fun getAllPublicChats(): Map {
+ fun getAllPublicChats(): Map {
val database = databaseHelper.readableDatabase
var cursor: Cursor? = null
- val result = mutableMapOf()
+ val result = mutableMapOf()
try {
- cursor = database.rawQuery("select * from $publicChatTableName", null)
+ cursor = database.rawQuery("select * from $publicChatTable", null)
while (cursor != null && cursor.moveToNext()) {
val threadID = cursor.getLong(threadID)
val string = cursor.getString(publicChat)
- val publicChat = LokiPublicChat.fromJSON(string)
+ val publicChat = PublicChat.fromJSON(string)
if (publicChat != null) { result[threadID] = publicChat }
}
} 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) }
}
- override fun getPublicChat(threadID: Long): LokiPublicChat? {
+ override fun getPublicChat(threadID: Long): PublicChat? {
if (threadID < 0) { return null }
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)
- LokiPublicChat.fromJSON(publicChatAsJSON)
+ PublicChat.fromJSON(publicChatAsJSON)
}
}
- override fun setPublicChat(publicChat: LokiPublicChat, threadID: Long) {
+ override fun setPublicChat(publicChat: PublicChat, threadID: Long) {
if (threadID < 0) { return }
val database = databaseHelper.writableDatabase
val contentValues = ContentValues(2)
contentValues.put(Companion.threadID, threadID)
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) {
- 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()
- if (devices.add(hexEncodedPublicKey)) {
+ if (devices.add(publicKey)) {
TextSecurePreferences.setStringPreference(context, "session_restore_devices_$threadID", devices.joinToString(","))
delegate?.handleSessionRestoreDevicesChanged(threadID)
}
diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabaseDelegate.kt b/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabaseDelegate.kt
index ed64bc5929..a27ea6409e 100644
--- a/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabaseDelegate.kt
+++ b/src/org/thoughtcrime/securesms/loki/database/LokiThreadDatabaseDelegate.kt
@@ -2,6 +2,5 @@ package org.thoughtcrime.securesms.loki.database
interface LokiThreadDatabaseDelegate {
- fun handleThreadFriendRequestStatusChanged(threadID: Long)
fun handleSessionRestoreDevicesChanged(threadID: Long)
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt
index 0e307499e2..236ffb6fe3 100644
--- a/src/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt
+++ b/src/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt
@@ -20,60 +20,60 @@ class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database
private val displayName = "display_name"
// Display name cache
private val displayNameTable = "loki_user_display_name_database"
- private val hexEncodedPublicKey = "hex_encoded_public_key"
- @JvmStatic val createDisplayNameTableCommand = "CREATE TABLE $displayNameTable ($hexEncodedPublicKey TEXT PRIMARY KEY, $displayName TEXT);"
+ private val publicKey = "hex_encoded_public_key"
+ @JvmStatic val createDisplayNameTableCommand = "CREATE TABLE $displayNameTable ($publicKey TEXT PRIMARY KEY, $displayName TEXT);"
// Server display name cache
private val serverDisplayNameTable = "loki_user_server_display_name_database"
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? {
- if (hexEncodedPublicKey == TextSecurePreferences.getLocalNumber(context)) {
+ override fun getDisplayName(publicKey: String): String? {
+ if (publicKey == TextSecurePreferences.getLocalNumber(context)) {
return TextSecurePreferences.getProfileName(context)
} else {
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))
}
}
}
- fun setDisplayName(hexEncodedPublicKey: String, displayName: String) {
+ fun setDisplayName(publicKey: String, displayName: String) {
val database = databaseHelper.writableDatabase
val row = ContentValues(2)
- row.put(Companion.hexEncodedPublicKey, hexEncodedPublicKey)
+ row.put(Companion.publicKey, publicKey)
row.put(Companion.displayName, displayName)
- database.insertOrUpdate(displayNameTable, row, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ))
- Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).notifyListeners()
+ database.insertOrUpdate(displayNameTable, row, "${Companion.publicKey} = ?", arrayOf( publicKey ))
+ 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
- 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))
}
}
- fun setServerDisplayName(serverID: String, hexEncodedPublicKey: String, displayName: String) {
+ fun setServerDisplayName(serverID: String, publicKey: String, displayName: String) {
val database = databaseHelper.writableDatabase
val values = ContentValues(3)
values.put(Companion.serverID, serverID)
- values.put(Companion.hexEncodedPublicKey, hexEncodedPublicKey)
+ values.put(Companion.publicKey, publicKey)
values.put(Companion.displayName, displayName)
try {
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) {
Log.d("Loki", "Couldn't save server display name due to exception: $e.")
}
}
- override fun getProfilePictureURL(hexEncodedPublicKey: String): String? {
- return if (hexEncodedPublicKey == TextSecurePreferences.getLocalNumber(context)) {
+ override fun getProfilePictureURL(publicKey: String): String? {
+ return if (publicKey == TextSecurePreferences.getLocalNumber(context)) {
TextSecurePreferences.getProfilePictureURL(context)
} else {
- Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).resolve().profileAvatar
+ Recipient.from(context, Address.fromSerialized(publicKey), false).resolve().profileAvatar
}
}
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/ConversationOptionsBottomSheet.kt b/src/org/thoughtcrime/securesms/loki/dialogs/ConversationOptionsBottomSheet.kt
new file mode 100644
index 0000000000..498cb1f0e9
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/dialogs/ConversationOptionsBottomSheet.kt
@@ -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() }
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/LinkDeviceMasterModeDialog.kt b/src/org/thoughtcrime/securesms/loki/dialogs/LinkDeviceMasterModeDialog.kt
index ab899c717c..fbb476e527 100644
--- a/src/org/thoughtcrime/securesms/loki/dialogs/LinkDeviceMasterModeDialog.kt
+++ b/src/org/thoughtcrime/securesms/loki/dialogs/LinkDeviceMasterModeDialog.kt
@@ -21,12 +21,12 @@ import org.thoughtcrime.securesms.loki.utilities.QRCodeUtilities
import org.thoughtcrime.securesms.loki.utilities.toPx
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
-import org.whispersystems.signalservice.loki.api.LokiAPI
-import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
+import org.whispersystems.signalservice.loki.api.SnodeAPI
+import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSessionListener
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLinkingSession
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLinkingSessionListener
class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListener {
private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) }
@@ -52,7 +52,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
}
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 {
this.deviceLink = deviceLink
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.explanationTextView.text = resources.getString(R.string.dialog_link_device_master_mode_explanation_2)
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
}
}
@@ -84,7 +84,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
contentView.cancelButton.visibility = View.GONE
contentView.authorizeButton.visibility = View.GONE
}
- LokiFileServerAPI.shared.addDeviceLink(deviceLink).bind(LokiAPI.sharedContext) {
+ FileServerAPI.shared.addDeviceLink(deviceLink).bind(SnodeAPI.sharedContext) {
MultiDeviceProtocol.signAndSendDeviceLinkMessage(context!!, deviceLink)
}.success {
TextSecurePreferences.setMultiDevice(context!!, true)
@@ -92,8 +92,8 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
delegate?.onDeviceLinkRequestAuthorized()
dismiss()
}.fail {
- LokiFileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
- DatabaseFactory.getLokiPreKeyBundleDatabase(context!!).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)
+ FileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
+ DatabaseFactory.getLokiPreKeyBundleDatabase(context!!).removePreKeyBundle(deviceLink.slavePublicKey)
}.failUi {
delegate?.onDeviceLinkAuthorizationFailed()
dismiss()
@@ -104,7 +104,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this)
if (deviceLink != null) {
- DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(deviceLink!!.slaveHexEncodedPublicKey)
+ DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(deviceLink!!.slavePublicKey)
}
dismiss()
delegate?.onDeviceLinkCanceled()
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/LinkDeviceSlaveModeDialog.kt b/src/org/thoughtcrime/securesms/loki/dialogs/LinkDeviceSlaveModeDialog.kt
index 744cdaa29a..b87530bfa0 100644
--- a/src/org/thoughtcrime/securesms/loki/dialogs/LinkDeviceSlaveModeDialog.kt
+++ b/src/org/thoughtcrime/securesms/loki/dialogs/LinkDeviceSlaveModeDialog.kt
@@ -16,9 +16,9 @@ import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSessionListener
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLinkingSession
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLinkingSessionListener
class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener {
private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) }
@@ -41,7 +41,7 @@ class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener
}
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 {
this.deviceLink = deviceLink
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/MultiDeviceRemovalBottomSheet.kt b/src/org/thoughtcrime/securesms/loki/dialogs/MultiDeviceRemovalBottomSheet.kt
new file mode 100644
index 0000000000..e236751d98
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/dialogs/MultiDeviceRemovalBottomSheet.kt
@@ -0,0 +1,86 @@
+package org.thoughtcrime.securesms.loki.dialogs
+
+import android.graphics.Typeface
+import android.os.Bundle
+import android.support.design.widget.BottomSheetDialogFragment
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.text.method.LinkMovementMethod
+import android.text.style.ClickableSpan
+import android.text.style.ForegroundColorSpan
+import android.text.style.StyleSpan
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import kotlinx.android.synthetic.main.fragment_multi_device_removal_bottom_sheet.*
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.loki.utilities.getColorWithID
+import org.thoughtcrime.securesms.util.TextSecurePreferences
+import java.text.SimpleDateFormat
+import java.util.*
+
+class MultiDeviceRemovalBottomSheet : BottomSheetDialogFragment() {
+ var onOKTapped: (() -> Unit)? = null
+ var onLinkTapped: (() -> Unit)? = null
+
+ private val removalDate by lazy {
+ val timeZone = TimeZone.getTimeZone("Australia/Melbourne")
+ val calendar = GregorianCalendar.getInstance(timeZone)
+ calendar.set(2020, 8 - 1, 6, 17, 0, 0)
+ calendar.time
+ }
+
+ private val removalDateDescription by lazy {
+ val formatter = SimpleDateFormat("MMMM d", Locale.getDefault())
+ formatter.format(removalDate)
+ }
+
+ private val explanation by lazy {
+ if (TextSecurePreferences.getMasterHexEncodedPublicKey(context!!) != null) {
+ "You’re seeing this because this is a secondary device in a multi-device setup. To improve reliability and stability, we’ve decided to temporarily disable Session’s multi-device functionality. Device linking has been disabled, and existing secondary clients will be erased on $removalDateDescription.\n\nTo read more about this change, visit the Session FAQ at getsession.org/faq."
+ } else {
+ "You’re seeing this because you have a secondary device linked to your Session ID. To improve reliability and stability, we’ve decided to temporarily disable Session’s multi-device functionality. Device linking has been disabled, and existing secondary clients will be erased on $removalDateDescription.\n\nTo read more about this change, visit the Session FAQ at getsession.org/faq"
+ }
+ }
+
+ private val decoratedExplanation by lazy {
+ val result = SpannableStringBuilder(explanation)
+ val removalDateStartIndex = explanation.indexOf(removalDateDescription)
+ val removalDateEndIndex = removalDateStartIndex + removalDateDescription.count()
+ result.setSpan(StyleSpan(Typeface.BOLD), removalDateStartIndex, removalDateEndIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ result.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, context!!.theme)), removalDateStartIndex, removalDateEndIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ val link = "getsession.org/faq"
+ val linkStartIndex = explanation.indexOf(link)
+ val linkEndIndex = linkStartIndex + link.count()
+ result.setSpan(object : ClickableSpan() {
+
+ override fun onClick(widget: View) {
+ try {
+ onLinkTapped?.invoke()
+ } catch (e: Exception) {
+ Toast.makeText(context!!, R.string.invalid_url, Toast.LENGTH_SHORT).show()
+ }
+ }
+ }, linkStartIndex, linkEndIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ result.setSpan(StyleSpan(Typeface.BOLD), linkStartIndex, linkEndIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ result.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, context!!.theme)), linkStartIndex, linkEndIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ result
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setStyle(STYLE_NORMAL, R.style.SessionBottomSheetDialogTheme)
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_multi_device_removal_bottom_sheet, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ explanationTextView.movementMethod = LinkMovementMethod.getInstance()
+ explanationTextView.text = decoratedExplanation
+ okButton.setOnClickListener { onOKTapped?.invoke() }
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/PNModeBottomSheet.kt b/src/org/thoughtcrime/securesms/loki/dialogs/PNModeBottomSheet.kt
deleted file mode 100644
index 276f81ee43..0000000000
--- a/src/org/thoughtcrime/securesms/loki/dialogs/PNModeBottomSheet.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-package org.thoughtcrime.securesms.loki.dialogs
-
-import android.app.AlertDialog
-import android.content.DialogInterface
-import android.graphics.drawable.TransitionDrawable
-import android.os.Bundle
-import android.support.annotation.DrawableRes
-import android.support.design.widget.BottomSheetDialogFragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import kotlinx.android.synthetic.main.fragment_pn_mode_bottom_sheet.*
-import network.loki.messenger.R
-import org.thoughtcrime.securesms.util.TextSecurePreferences
-
-class PNModeBottomSheet : BottomSheetDialogFragment() {
- private var selectedOptionView: LinearLayout? = null
- var onConfirmTapped: ((Boolean) -> Unit)? = null
- var onSkipTapped: (() -> Unit)? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setStyle(STYLE_NORMAL, R.style.SessionBottomSheetDialogTheme)
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return inflater.inflate(R.layout.fragment_pn_mode_bottom_sheet, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- fcmOptionView.setOnClickListener { toggleFCM() }
- backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
- confirmButton.setOnClickListener { confirm() }
- skipButton.setOnClickListener { onSkipTapped?.invoke() }
- }
-
- override fun onDismiss(dialog: DialogInterface?) {
- TextSecurePreferences.setHasSeenPNModeSheet(context, true)
- super.onDismiss(dialog)
- }
-
- // region Animation
- private fun performTransition(@DrawableRes transitionID: Int, subject: View) {
- val drawable = resources.getDrawable(transitionID, context!!.theme) as TransitionDrawable
- subject.background = drawable
- drawable.startTransition(250)
- }
- // endregion
-
- // region Interaction
- private fun toggleFCM() {
- when (selectedOptionView) {
- null -> {
- performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
- selectedOptionView = fcmOptionView
- }
- fcmOptionView -> {
- performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
- selectedOptionView = null
- }
- backgroundPollingOptionView -> {
- performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
- performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
- selectedOptionView = fcmOptionView
- }
- }
- }
-
- private fun toggleBackgroundPolling() {
- when (selectedOptionView) {
- null -> {
- performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
- selectedOptionView = backgroundPollingOptionView
- }
- backgroundPollingOptionView -> {
- performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
- selectedOptionView = null
- }
- fcmOptionView -> {
- performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
- performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
- selectedOptionView = backgroundPollingOptionView
- }
- }
- }
-
- private fun confirm() {
- if (selectedOptionView == null) {
- val dialog = AlertDialog.Builder(context)
- dialog.setTitle(R.string.sheet_pn_mode_no_option_picked_dialog_title)
- dialog.setPositiveButton(R.string.ok) { _, _ -> }
- dialog.create().show()
- return
- }
- onConfirmTapped?.invoke(selectedOptionView == fcmOptionView)
- }
- // endregion
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/fragments/ContactSelectionListLoader.kt b/src/org/thoughtcrime/securesms/loki/fragments/ContactSelectionListLoader.kt
index bd04316726..4bfacb84a0 100644
--- a/src/org/thoughtcrime/securesms/loki/fragments/ContactSelectionListLoader.kt
+++ b/src/org/thoughtcrime/securesms/loki/fragments/ContactSelectionListLoader.kt
@@ -15,10 +15,10 @@ sealed class ContactSelectionListItem {
class ContactSelectionListLoader(context: Context, val mode: Int, val filter: String?) : AsyncLoader>(context) {
object DisplayMode {
- const val FLAG_FRIENDS = 1
+ const val FLAG_CONTACTS = 1
const val FLAG_CLOSED_GROUPS = 1 shl 1
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 {
@@ -39,15 +39,15 @@ class ContactSelectionListLoader(context: Context, val mode: Int, val filter: St
if (isFlagSet(DisplayMode.FLAG_OPEN_GROUPS)) {
list.addAll(getOpenGroups(contacts))
}
- if (isFlagSet(DisplayMode.FLAG_FRIENDS)) {
- list.addAll(getFriends(contacts))
+ if (isFlagSet(DisplayMode.FLAG_CONTACTS)) {
+ list.addAll(getContacts(contacts))
}
return list
}
- private fun getFriends(contacts: List): List {
+ private fun getContacts(contacts: List): List {
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
}
}
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt
index 6df6fa85f6..8bcff5516d 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt
@@ -17,8 +17,9 @@ import org.whispersystems.libsignal.SignalProtocolAddress
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.push.SignalServiceAddress
-import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
+import org.whispersystems.signalservice.loki.api.SnodeAPI
+import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import java.util.*
object ClosedGroupsProtocol {
@@ -31,7 +32,7 @@ object ClosedGroupsProtocol {
if (!conversation.address.isClosedGroup || groupID == null) { return false }
// A closed group's members should never include slave devices
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 publicKeyToCheckFor = senderMasterPublicKey ?: senderPublicKey
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, true)
@@ -56,7 +57,7 @@ object ClosedGroupsProtocol {
} else {
// A closed group's members should never include slave devices
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 ->
MultiDeviceProtocol.shared.getAllLinkedDevices(member.address.serialize()).map { Address.fromSerialized(it) }
}.toMutableSet()
@@ -105,12 +106,7 @@ object ClosedGroupsProtocol {
allDevices.remove(userPublicKey)
}
for (device in allDevices) {
- val deviceAsAddress = SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID)
- val hasSession = TextSecureSessionStore(context).containsSession(deviceAsAddress)
- if (hasSession) { continue }
- DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestTimestamp(device, Date().time)
- val sessionRequest = EphemeralMessage.createSessionRequest(device)
- ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
+ ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(device)
}
}
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/EphemeralMessage.kt b/src/org/thoughtcrime/securesms/loki/protocol/EphemeralMessage.kt
deleted file mode 100644
index ca46684223..0000000000
--- a/src/org/thoughtcrime/securesms/loki/protocol/EphemeralMessage.kt
+++ /dev/null
@@ -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 get(key: String, defaultValue: T): T {
- return data[key] as? T ?: defaultValue
- }
-
- fun serialize(): String {
- return JsonUtil.toJson(data)
- }
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/FriendRequestProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/FriendRequestProtocol.kt
deleted file mode 100644
index 50d95cf379..0000000000
--- a/src/org/thoughtcrime/securesms/loki/protocol/FriendRequestProtocol.kt
+++ /dev/null
@@ -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()
- 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()
- 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)
- }
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/LokiSessionResetImplementation.kt b/src/org/thoughtcrime/securesms/loki/protocol/LokiSessionResetImplementation.kt
deleted file mode 100644
index 067a25b27c..0000000000
--- a/src/org/thoughtcrime/securesms/loki/protocol/LokiSessionResetImplementation.kt
+++ /dev/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." }
- }
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceOpenGroupUpdateJob.kt b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceOpenGroupUpdateJob.kt
index 2c6ed90ff0..70de694044 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceOpenGroupUpdateJob.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceOpenGroupUpdateJob.kt
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.SignalServiceMessageSender
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 javax.inject.Inject
@@ -27,7 +27,7 @@ class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters)
constructor() : this(Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
- .setQueue("MultiDeviceOpenGroupUpdateJob")
+ .setQueue(KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build())
@@ -43,7 +43,7 @@ class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters)
return
}
// Gather open groups
- val openGroups = mutableListOf()
+ val openGroups = mutableListOf()
DatabaseFactory.getGroupDatabase(context).groups.use { reader ->
while (true) {
val record = reader.next ?: return@use
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt
index 4486d76795..4d37ab1e58 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt
@@ -18,12 +18,11 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
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.multidevice.DeviceLink
-import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
-import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLinkingSession
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
object MultiDeviceProtocol {
@@ -31,46 +30,16 @@ object MultiDeviceProtocol {
enum class MessageType { Text, Media }
@JvmStatic
- fun sendTextPush(context: Context, recipient: Recipient, messageID: Long, isEndSession: Boolean) {
- sendMessagePush(context, recipient, messageID, MessageType.Text, isEndSession)
+ fun sendTextPush(context: Context, recipient: Recipient, messageID: Long) {
+ sendMessagePush(context, recipient, messageID, MessageType.Text)
}
@JvmStatic
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 {
- 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) {
+ private fun sendMessagePush(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType) {
val jobManager = ApplicationContext.getInstance(context).jobManager
val isMultiDeviceRequired = !recipient.address.isOpenGroup
if (!isMultiDeviceRequired) {
@@ -80,9 +49,14 @@ object MultiDeviceProtocol {
}
}
val publicKey = recipient.address.serialize()
- LokiFileServerAPI.shared.getDeviceLinks(publicKey).success {
+ FileServerAPI.shared.getDeviceLinks(publicKey).success {
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")
when (messageType) {
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.
if (deviceLink.type == DeviceLink.Type.REQUEST) {
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
- message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
+ message.withPreKeyBundle(preKeyBundle)
} else {
// Include the user's profile key so that the slave device can get the user's profile picture
message.withProfileKey(ProfileKeyUtil.getProfileKey(context))
@@ -135,7 +109,7 @@ object MultiDeviceProtocol {
return Promise.ofFail(Exception("Failed to sign device link."))
}
return retryIfNeeded(8) {
- sendDeviceLinkMessage(context, deviceLink.slaveHexEncodedPublicKey, signedDeviceLink)
+ sendDeviceLinkMessage(context, deviceLink.slavePublicKey, signedDeviceLink)
}
}
@@ -144,7 +118,7 @@ object MultiDeviceProtocol {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
if (deviceLink.type == DeviceLink.Type.REQUEST) {
handleDeviceLinkRequestMessage(context, deviceLink, content)
- } else if (deviceLink.slaveHexEncodedPublicKey == userPublicKey) {
+ } else if (deviceLink.slavePublicKey == userPublicKey) {
handleDeviceLinkAuthorizedMessage(context, deviceLink, content)
}
}
@@ -158,10 +132,10 @@ object MultiDeviceProtocol {
} else if (isRequest && TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null) {
Log.d("Loki", "Ignoring unexpected device link message (the device is a slave device).")
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.")
return false
- } else if (isRequest && deviceLink.slaveHexEncodedPublicKey == userPublicKey) {
+ } else if (isRequest && deviceLink.slavePublicKey == userPublicKey) {
Log.d("Loki", "Ignoring device linking request message from self.")
return false
}
@@ -188,16 +162,14 @@ object MultiDeviceProtocol {
}
val isValid = isValidDeviceLinkMessage(context, deviceLink)
if (!isValid) { return }
- SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(context, content)
linkingSession.processLinkingAuthorization(deviceLink)
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userPublicKey)
DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink)
- TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.masterHexEncodedPublicKey)
+ TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.masterPublicKey)
TextSecurePreferences.setMultiDevice(context, true)
- LokiFileServerAPI.shared.addDeviceLink(deviceLink)
- org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content)
- org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.duplicate_handleProfileKey(context, content)
+ FileServerAPI.shared.addDeviceLink(deviceLink)
+ org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.handleProfileKeyUpdate(context, content)
}
@JvmStatic
@@ -210,19 +182,19 @@ object MultiDeviceProtocol {
// Ignore the request if we don't know about the device link in question
val masterDeviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getDeviceLinks(masterDevicePublicKey)
if (masterDeviceLinks.none {
- it.masterHexEncodedPublicKey == masterDevicePublicKey && it.slaveHexEncodedPublicKey == userPublicKey
+ it.masterPublicKey == masterDevicePublicKey && it.slavePublicKey == userPublicKey
}) {
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.
// 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.
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
- 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)
ApplicationContext.getInstance(context).clearData()
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/PushEphemeralMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/protocol/PushEphemeralMessageSendJob.kt
deleted file mode 100644
index 565d25f670..0000000000
--- a/src/org/thoughtcrime/securesms/loki/protocol/PushEphemeralMessageSendJob.kt
+++ /dev/null
@@ -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("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 {
-
- 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)
- }
- }
- }
-}
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/PushNullMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/protocol/PushNullMessageSendJob.kt
new file mode 100644
index 0000000000..90b8ef0e00
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/protocol/PushNullMessageSendJob.kt
@@ -0,0 +1,84 @@
+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.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 PushNullMessageSendJob private constructor(parameters: Parameters, private val publicKey: String) : BaseJob(parameters) {
+
+ companion object {
+ const val KEY = "PushNullMessageSendJob"
+ }
+
+ constructor(publicKey: String) : this(Parameters.Builder()
+ .addConstraint(NetworkConstraint.KEY)
+ .setQueue(KEY)
+ .setLifespan(TimeUnit.DAYS.toMillis(1))
+ .setMaxAttempts(1)
+ .build(),
+ publicKey)
+
+ override fun serialize(): Data {
+ return Data.Builder().putString("publicKey", publicKey).build()
+ }
+
+ override fun getFactoryKey(): String { return KEY }
+
+ public override fun onRun() {
+ val contentMessage = SignalServiceProtos.Content.newBuilder()
+ 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()
+ 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.Ephemeral)
+ try {
+ messageSender.sendMessage(0, address, udAccess.get().targetUnidentifiedAccess,
+ Date().time, serializedContentMessage, false, ttl, false,
+ false, false, false)
+ } catch (e: Exception) {
+ Log.d("Loki", "Failed to send null message 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() { }
+
+ class Factory : Job.Factory {
+
+ override fun create(parameters: Parameters, data: Data): PushNullMessageSendJob {
+ try {
+ val publicKey = data.getString("publicKey")
+ return PushNullMessageSendJob(parameters, publicKey)
+ } catch (e: IOException) {
+ throw AssertionError(e)
+ }
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt
new file mode 100644
index 0000000000..f530468235
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt
@@ -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 {
+
+ 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)
+ }
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt
index 81ca56eaa0..4bd08e275f 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt
@@ -11,12 +11,14 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob
import org.thoughtcrime.securesms.loki.utilities.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.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.loki.protocol.multidevice.MultiDeviceProtocol
-import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
+import java.util.*
object SessionManagementProtocol {
@@ -27,8 +29,8 @@ object SessionManagementProtocol {
val smsDB = DatabaseFactory.getSmsDatabase(context)
val devices = lokiThreadDB.getSessionRestoreDevices(threadID)
for (device in devices) {
- val sessionRestorationRequest = EphemeralMessage.createSessionRestorationRequest(recipient.address.serialize())
- ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRestorationRequest))
+ val endSessionMessage = OutgoingEndSessionMessage(OutgoingTextMessage(recipient, "TERMINATE", 0, -1))
+ MessageSender.send(context, endSessionMessage, threadID, false, null)
}
val infoMessage = OutgoingTextMessage(recipient, "", 0, 0)
val infoMessageID = smsDB.insertMessageOutbox(threadID, infoMessage, false, System.currentTimeMillis(), null)
@@ -52,28 +54,30 @@ object SessionManagementProtocol {
}
@JvmStatic
- fun handlePreKeyBundleMessageIfNeeded(context: Context, content: SignalServiceContent) {
- val recipient = recipient(context, content.sender)
- if (recipient.isGroupRecipient) { return }
- val preKeyBundleMessage = content.lokiServiceMessage.orNull()?.preKeyBundleMessage ?: return
- val registrationID = TextSecurePreferences.getLocalRegistrationId(context) // TODO: It seems wrong to use the local registration ID for this?
- val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context)
- Log.d("Loki", "Received a pre key bundle from: " + content.sender.toString() + ".")
- val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
- lokiPreKeyBundleDatabase.setPreKeyBundle(content.sender, preKeyBundle)
+ fun shouldProcessSessionRequest(context: Context, publicKey: String, timestamp: Long): Boolean {
+ val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
+ val sentTimestamp = apiDB.getSessionRequestSentTimestamp(publicKey) ?: 0
+ val processedTimestamp = apiDB.getSessionRequestProcessedTimestamp(publicKey) ?: 0
+ return timestamp > sentTimestamp && timestamp > processedTimestamp
}
@JvmStatic
- fun handleSessionRequestIfNeeded(context: Context, content: SignalServiceContent): Boolean {
- if (!content.dataMessage.isPresent || !content.dataMessage.get().isSessionRequest) { return false }
- val sentSessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender)
- if (sentSessionRequestTimestamp != null && content.timestamp < sentSessionRequestTimestamp) {
- // We sent a session request after this one was sent
- return false
+ fun handlePreKeyBundleMessageIfNeeded(context: Context, content: SignalServiceContent) {
+ val preKeyBundleMessage = content.preKeyBundleMessage.orNull() ?: return
+ val publicKey = content.sender
+ if (recipient(context, publicKey).isGroupRecipient) { return } // Should never occur
+ Log.d("Loki", "Received a pre key bundle from: $publicKey.")
+ if (!shouldProcessSessionRequest(context, publicKey, content.timestamp)) {
+ Log.d("Loki", "Ignoring session request from: $publicKey.")
+ return
}
- val ephemeralMessage = EphemeralMessage.create(content.sender)
- ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
- return true
+ val registrationID = TextSecurePreferences.getLocalRegistrationId(context)
+ val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context)
+ val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
+ lokiPreKeyBundleDatabase.setPreKeyBundle(publicKey, preKeyBundle)
+ DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestProcessedTimestamp(publicKey, Date().time)
+ val job = PushNullMessageSendJob(publicKey)
+ ApplicationContext.getInstance(context).jobManager.add(job)
}
@JvmStatic
@@ -83,10 +87,10 @@ object SessionManagementProtocol {
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
Log.d("Loki", "Received a session reset request from: ${content.sender}; archiving the session.")
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}.")
- val ephemeralMessage = EphemeralMessage.create(content.sender)
- ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
+ val job = PushNullMessageSendJob(content.sender)
+ ApplicationContext.getInstance(context).jobManager.add(job)
SecurityEvent.broadcastSecurityUpdateEvent(context)
}
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt
index 2276539323..c0dc77f4b1 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt
@@ -10,14 +10,18 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
-import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import java.security.MessageDigest
object SessionMetaProtocol {
private val timestamps = mutableSetOf()
+ @JvmStatic
+ fun dropFromTimestampCacheIfNeeded(timestamp: Long) {
+ timestamps.remove(timestamp)
+ }
+
@JvmStatic
fun shouldIgnoreMessage(content: SignalServiceContent): Boolean {
val timestamp = content.timestamp
@@ -48,9 +52,8 @@ object SessionMetaProtocol {
}
}
- // FIXME: Basically a duplicate of PushDecryptJob's handleProfileKey
@JvmStatic
- fun duplicate_handleProfileKey(context: Context, content: SignalServiceContent) {
+ fun handleProfileKeyUpdate(context: Context, content: SignalServiceContent) {
val message = content.dataMessage.get()
if (!message.profileKey.isPresent) { return }
val database = DatabaseFactory.getRecipientDatabase(context)
@@ -60,46 +63,34 @@ object SessionMetaProtocol {
database.setUnidentifiedAccessMode(recipient, RecipientDatabase.UnidentifiedAccessMode.UNKNOWN)
val url = content.senderProfilePictureURL.or("")
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(recipient, url))
- handleProfileKeyUpdateIfNeeded(context, content)
+ val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
+ if (userMasterPublicKey == content.sender) {
+ ApplicationContext.getInstance(context).updateOpenGroupProfilePicturesIfNeeded()
+ }
}
}
+ /**
+ * Should be invoked for the recipient's master device.
+ */
@JvmStatic
- fun handleProfileKeyUpdateIfNeeded(context: Context, content: SignalServiceContent) {
- val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
- if (userMasterPublicKey != content.sender) { return }
- ApplicationContext.getInstance(context).updateOpenGroupProfilePicturesIfNeeded()
+ fun canUserReplyToNotification(recipient: Recipient): Boolean {
+ return !recipient.address.isRSSFeed
}
/**
* 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
+ fun shouldSendReadReceipt(address: Address): Boolean {
+ return !address.isGroup
}
/**
* Should be invoked for the recipient's master device.
*/
@JvmStatic
- fun shouldSendReadReceipt(address: Address, context: Context): Boolean {
- if (address.isGroup) { return false }
- 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.
- */
- @JvmStatic
- fun shouldSendTypingIndicator(recipient: Recipient, context: Context): Boolean {
- if (recipient.isGroupRecipient) { return false }
- val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
- return DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
+ fun shouldSendTypingIndicator(address: Address): Boolean {
+ return !address.isGroup
}
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/SessionResetImplementation.kt b/src/org/thoughtcrime/securesms/loki/protocol/SessionResetImplementation.kt
new file mode 100644
index 0000000000..4c34d64b64
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/protocol/SessionResetImplementation.kt
@@ -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." }
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/SyncMessagesProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/SyncMessagesProtocol.kt
index db0a8a85d7..5ec37a41dd 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/SyncMessagesProtocol.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/SyncMessagesProtocol.kt
@@ -7,10 +7,12 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData
import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
+import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.groups.GroupMessageProcessor
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob
+import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import org.thoughtcrime.securesms.loki.utilities.recipient
import org.thoughtcrime.securesms.recipients.Recipient
@@ -19,13 +21,12 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
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.DeviceContactsInputStream
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat
-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.api.opengroups.PublicChat
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
import java.util.*
@@ -34,7 +35,7 @@ object SyncMessagesProtocol {
@JvmStatic
fun shouldIgnoreSyncMessage(context: Context, sender: Recipient): Boolean {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
- return !MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey).contains(sender.address.serialize())
+ return userPublicKey == sender.address.serialize() // return !MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey).contains(sender.address.serialize())
}
@JvmStatic
@@ -49,27 +50,28 @@ object SyncMessagesProtocol {
@JvmStatic
fun getContactsToSync(context: Context): List {
- val allAddresses = ArrayList(DatabaseFactory.getRecipientDatabase(context).allAddresses)
+ val contacts = ContactUtilities.getAllContacts(context)
val result = mutableSetOf()
- for (address in allAddresses) {
- if (!shouldSyncContact(context, address)) { continue }
- val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, address, false))
- val displayName = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(address.serialize())
+ for (contact in contacts) {
+ val contactPublicKey = contact.recipient.address.serialize()
+ if (!shouldSyncContact(context, contactPublicKey)) { continue }
+ val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient(context, contactPublicKey))
+ if (threadID < 0) { continue }
+ val displayName = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(contactPublicKey)
val contactData = ContactData(threadID, displayName)
- contactData.numbers.add(NumberData("TextSecure", address.serialize()))
+ contactData.numbers.add(NumberData("TextSecure", contactPublicKey))
result.add(contactData)
}
return result.toList()
}
@JvmStatic
- fun shouldSyncContact(context: Context, address: Address): Boolean {
- if (!PublicKeyValidation.isValid(address.serialize())) { return false }
- if (address.serialize() == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { return false }
- if (address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
- val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, address, false))
- val isFriend = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
- return isFriend
+ fun shouldSyncContact(context: Context, publicKey: String): Boolean {
+ if (!PublicKeyValidation.isValid(publicKey)) { return false }
+ if (publicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { return false }
+ if (publicKey == TextSecurePreferences.getLocalNumber(context)) { return false }
+ if (MultiDeviceProtocol.shared.getSlaveDevices(publicKey).contains(publicKey)) { return false }
+ return true
}
@JvmStatic
@@ -95,33 +97,13 @@ object SyncMessagesProtocol {
if (!allUserDevices.contains(content.sender)) { return }
Log.d("Loki", "Received a contact sync message.")
val contactsInputStream = DeviceContactsInputStream(message.contactsStream.asStream().inputStream)
- val contactPublicKeys = contactsInputStream.readAll().map { it.number }
- for (contactPublicKey in contactPublicKeys) {
+ val contacts = contactsInputStream.readAll()
+ for (contact in contacts) {
+ val contactPublicKey = contact.number
if (contactPublicKey == userPublicKey || !PublicKeyValidation.isValid(contactPublicKey)) { return }
- val recipient = recipient(context, contactPublicKey)
- val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
- val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
- 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
- }
- }
+ val applicationContext = context.applicationContext as ApplicationContext
+ applicationContext.sendSessionRequestIfNeeded(contactPublicKey)
+ DatabaseFactory.getRecipientDatabase(context).setBlocked(recipient(context, contactPublicKey), contact.isBlocked)
}
}
@@ -136,22 +118,22 @@ object SyncMessagesProtocol {
val closedGroups = closedGroupsInputStream.readAll()
for (closedGroup in closedGroups) {
val signalServiceGroup = SignalServiceGroup(
- SignalServiceGroup.Type.UPDATE,
- closedGroup.id,
- SignalServiceGroup.GroupType.SIGNAL,
- closedGroup.name.orNull(),
- closedGroup.members,
- closedGroup.avatar.orNull(),
- closedGroup.admins
+ SignalServiceGroup.Type.UPDATE,
+ closedGroup.id,
+ SignalServiceGroup.GroupType.SIGNAL,
+ closedGroup.name.orNull(),
+ closedGroup.members,
+ closedGroup.avatar.orNull(),
+ closedGroup.admins
)
- val signalServiceDataMessage = SignalServiceDataMessage(content.timestamp, signalServiceGroup, null, null)
+ val dataMessage = SignalServiceDataMessage(content.timestamp, signalServiceGroup, null, null)
// This establishes sessions internally
- GroupMessageProcessor.process(context, content, signalServiceDataMessage, false)
+ GroupMessageProcessor.process(context, content, dataMessage, false)
}
}
@JvmStatic
- fun handleOpenGroupSyncMessage(context: Context, content: SignalServiceContent, openGroups: List) {
+ fun handleOpenGroupSyncMessage(context: Context, content: SignalServiceContent, openGroups: List) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
if (!allUserDevices.contains(content.sender)) { return }
@@ -164,4 +146,27 @@ object SyncMessagesProtocol {
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()
+ 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")
+ }
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/shelved/LokiRSSFeedPoller.kt b/src/org/thoughtcrime/securesms/loki/shelved/LokiRSSFeedPoller.kt
deleted file mode 100644
index 18e3c6e307..0000000000
--- a/src/org/thoughtcrime/securesms/loki/shelved/LokiRSSFeedPoller.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.thoughtcrime.securesms.loki.shelved
-
-import android.content.Context
-import android.os.Handler
-import android.text.Html
-import android.util.Log
-import com.prof.rssparser.engine.XMLParser
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.Runnable
-import org.thoughtcrime.securesms.jobs.PushDecryptJob
-import org.thoughtcrime.securesms.loki.utilities.successBackground
-import org.whispersystems.libsignal.util.guava.Optional
-import org.whispersystems.signalservice.api.messages.SignalServiceContent
-import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
-import org.whispersystems.signalservice.api.messages.SignalServiceGroup
-import org.whispersystems.signalservice.api.push.SignalServiceAddress
-import org.whispersystems.signalservice.loki.api.rssfeeds.LokiRSSFeed
-import org.whispersystems.signalservice.loki.api.rssfeeds.LokiRSSFeedProxy
-import java.text.SimpleDateFormat
-import java.util.regex.Pattern
-
-class LokiRSSFeedPoller(private val context: Context, private val feed: LokiRSSFeed) {
- private val handler = Handler()
- private val job = Job()
- private var hasStarted = false
-
- private val task = object : Runnable {
-
- override fun run() {
- poll()
- handler.postDelayed(this, interval)
- }
- }
-
- companion object {
- private val interval: Long = 8 * 60 * 1000
- }
-
- fun startIfNeeded() {
- if (hasStarted) return
- task.run()
- hasStarted = true
- }
-
- fun stop() {
- handler.removeCallbacks(task)
- job.cancel()
- hasStarted = false
- }
-
- private fun poll() {
- LokiRSSFeedProxy.fetch(feed.url).successBackground { xml ->
- val items = XMLParser(xml).call()
- items.reversed().forEach { item ->
- val title = item.title ?: return@forEach
- val description = item.description ?: return@forEach
- val dateAsString = item.pubDate ?: return@forEach
- val formatter = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z") // e.g. Tue, 27 Aug 2019 03:52:05 +0000
- val date = formatter.parse(dateAsString)
- val timestamp = date.time
- var bodyAsHTML = "$title
$description"
- val urlRegex = Pattern.compile("]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>")
- val matcher = urlRegex.matcher(bodyAsHTML)
- bodyAsHTML = matcher.replaceAll("$2 ($1)")
- val body = Html.fromHtml(bodyAsHTML).toString().trim()
- val id = feed.id.toByteArray()
- val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.RSS_FEED, null, null, null, null)
- val x2 = SignalServiceDataMessage(timestamp, x1, null, body)
- val x3 = SignalServiceContent(x2, "Loki", SignalServiceAddress.DEFAULT_DEVICE_ID, timestamp, false, false, false, false, false)
- PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.absent())
- }
- }.fail { exception ->
- Log.d("Loki", "Couldn't update RSS feed with ID: $feed.id due to exception: $exception.")
- }
- }
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/utilities/ContactUtilities.kt b/src/org/thoughtcrime/securesms/loki/utilities/ContactUtilities.kt
index b6a9dad67b..4bc9db0e3a 100644
--- a/src/org/thoughtcrime/securesms/loki/utilities/ContactUtilities.kt
+++ b/src/org/thoughtcrime/securesms/loki/utilities/ContactUtilities.kt
@@ -4,12 +4,10 @@ import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
-import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
data class Contact(
val recipient: Recipient,
- val isFriend: Boolean,
val isSlave: Boolean,
val isOurDevice: Boolean
) {
@@ -31,10 +29,9 @@ object ContactUtilities {
@JvmStatic
fun getAllContacts(context: Context): Set {
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
- val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
- val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
+ val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
- val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userHexEncodedPublicKey)
+ val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
val cursor = threadDatabase.conversationList
val result = mutableSetOf()
threadDatabase.readerFor(cursor).use { reader ->
@@ -43,13 +40,12 @@ object ContactUtilities {
val recipient = thread.recipient
val publicKey = recipient.address.serialize()
val isUserDevice = userDevices.contains(publicKey)
- val isFriend = lokiThreadDatabase.getFriendRequestStatus(thread.threadId) == LokiThreadFriendRequestStatus.FRIENDS
var isSlave = false
if (!recipient.isGroupRecipient) {
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
diff --git a/src/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt b/src/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt
index af5b80d868..203e74a478 100644
--- a/src/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt
+++ b/src/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt
@@ -28,22 +28,22 @@ object MentionUtilities {
val mentions = mutableListOf, String>>()
var startIndex = 0
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
- val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
+ val userPublicKey = TextSecurePreferences.getLocalNumber(context)
if (matcher.find(startIndex)) {
while (true) {
- val hexEncodedPublicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
- val userDisplayName: String? = if (hexEncodedPublicKey.toLowerCase() == userHexEncodedPublicKey.toLowerCase()) {
+ val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
+ val userDisplayName: String? = if (publicKey.toLowerCase() == userPublicKey.toLowerCase()) {
TextSecurePreferences.getProfileName(context)
} else if (publicChat != null) {
- DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
+ DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey)
} else {
- DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
+ DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey)
}
if (userDisplayName != null) {
text = text.subSequence(0, matcher.start()).toString() + "@" + userDisplayName + text.subSequence(matcher.end(), text.length)
val endIndex = matcher.start() + 1 + userDisplayName.length
startIndex = endIndex
- mentions.add(Tuple2(Range.create(matcher.start(), endIndex), hexEncodedPublicKey))
+ mentions.add(Tuple2(Range.create(matcher.start(), endIndex), publicKey))
} else {
startIndex = matcher.end()
}
diff --git a/src/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt b/src/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt
index 013f18fa7b..69a00115ec 100644
--- a/src/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt
+++ b/src/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt
@@ -8,11 +8,11 @@ import org.thoughtcrime.securesms.recipients.Recipient
fun getOpenGroupDisplayName(recipient: Recipient, threadRecipient: Recipient, context: Context): String {
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient)
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
- val hexEncodedPublicKey = recipient.address.toString()
+ val publicKey = recipient.address.toString()
val displayName = if (publicChat != null) {
- DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, hexEncodedPublicKey)
+ DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey)
} else {
- DatabaseFactory.getLokiUserDatabase(context).getDisplayName(hexEncodedPublicKey)
+ DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey)
}
- return displayName ?: hexEncodedPublicKey
+ return displayName ?: publicKey
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt b/src/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt
index ea4a9f02ba..c2abd36dd5 100644
--- a/src/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt
+++ b/src/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt
@@ -8,21 +8,21 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.util.TextSecurePreferences
-import org.whispersystems.signalservice.loki.api.opengroups.LokiPublicChat
+import org.whispersystems.signalservice.loki.api.opengroups.PublicChat
object OpenGroupUtilities {
- @JvmStatic fun addGroup(context: Context, url: String, channel: Long): Promise {
+ @JvmStatic fun addGroup(context: Context, url: String, channel: Long): Promise {
// Check for an existing group
- val groupID = LokiPublicChat.getId(channel, url)
+ val groupID = PublicChat.getId(channel, url)
val threadID = GroupManager.getOpenGroupThreadID(groupID, context)
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
if (openGroup != null) { return Promise.of(openGroup) }
// Add the new group
val application = ApplicationContext.getInstance(context)
val displayName = TextSecurePreferences.getProfileName(context)
- val lokiPublicChatAPI = application.lokiPublicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.")
- return application.lokiPublicChatManager.addChat(url, channel).then { group ->
+ val lokiPublicChatAPI = application.publicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.")
+ return application.publicChatManager.addChat(url, channel).then { group ->
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url)
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url)
lokiPublicChatAPI.getMessages(channel, url)
diff --git a/src/org/thoughtcrime/securesms/loki/views/ConversationView.kt b/src/org/thoughtcrime/securesms/loki/views/ConversationView.kt
index 375cd23a16..f859faa85e 100644
--- a/src/org/thoughtcrime/securesms/loki/views/ConversationView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/ConversationView.kt
@@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities.populat
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities.highlightMentions
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.DateUtils
+import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
import java.util.*
@@ -46,22 +47,34 @@ class ConversationView : LinearLayout {
// region Updating
fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) {
this.thread = thread
- populateUserPublicKeyCacheIfNeeded(thread.threadId, context) // FIXME: This is a terrible place to do this
- unreadMessagesIndicatorView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE
+ populateUserPublicKeyCacheIfNeeded(thread.threadId, context) // FIXME: This is a bad place to do this
+ 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 ("Session Public Chat" == thread.recipient.name) {
- profilePictureView.hexEncodedPublicKey = ""
+ profilePictureView.publicKey = ""
+ profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = true
} 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
- profilePictureView.hexEncodedPublicKey = randomUsers.getOrNull(0) ?: ""
- profilePictureView.additionalHexEncodedPublicKey = randomUsers.getOrNull(1) ?: ""
+ profilePictureView.publicKey = randomUsers.getOrNull(0) ?: ""
+ profilePictureView.additionalPublicKey = randomUsers.getOrNull(1) ?: ""
profilePictureView.isRSSFeed = thread.recipient.name == "Loki News" || thread.recipient.name == "Session Updates"
}
} else {
- profilePictureView.hexEncodedPublicKey = thread.recipient.address.toString()
- profilePictureView.additionalHexEncodedPublicKey = null
+ profilePictureView.publicKey = thread.recipient.address.toString()
+ profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = false
}
profilePictureView.glide = glide
diff --git a/src/org/thoughtcrime/securesms/loki/views/FriendRequestView.kt b/src/org/thoughtcrime/securesms/loki/views/FriendRequestView.kt
deleted file mode 100644
index ea6556088d..0000000000
--- a/src/org/thoughtcrime/securesms/loki/views/FriendRequestView.kt
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt b/src/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt
index cba87e5bee..6208150b54 100644
--- a/src/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt
@@ -9,7 +9,7 @@ import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_mention_candidate.view.*
import network.loki.messenger.R
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
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() {
displayNameTextView.text = mentionCandidate.displayName
- profilePictureView.hexEncodedPublicKey = mentionCandidate.hexEncodedPublicKey
- profilePictureView.additionalHexEncodedPublicKey = null
+ profilePictureView.publicKey = mentionCandidate.publicKey
+ profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = false
profilePictureView.glide = glide!!
profilePictureView.update()
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
} else {
moderatorIconImageView.visibility = View.GONE
diff --git a/src/org/thoughtcrime/securesms/loki/views/NewConversationButtonSetView.kt b/src/org/thoughtcrime/securesms/loki/views/NewConversationButtonSetView.kt
index 3f94883c6c..1b26e1d917 100644
--- a/src/org/thoughtcrime/securesms/loki/views/NewConversationButtonSetView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/NewConversationButtonSetView.kt
@@ -16,6 +16,7 @@ import android.os.Vibrator
import android.support.annotation.ColorRes
import android.support.annotation.DrawableRes
import android.util.AttributeSet
+import android.view.Gravity
import android.view.MotionEvent
import android.widget.ImageView
import android.widget.RelativeLayout
@@ -91,6 +92,7 @@ class NewConversationButtonSetView : RelativeLayout {
addView(imageView)
imageView.x = collapsedImageViewPosition.x
imageView.y = collapsedImageViewPosition.y
+ gravity = Gravity.TOP or Gravity.LEFT // Intentionally not Gravity.START
}
fun expand() {
diff --git a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt
index 0e566d6b91..1250be7514 100644
--- a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt
@@ -21,8 +21,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
class ProfilePictureView : RelativeLayout {
lateinit var glide: GlideRequests
- var hexEncodedPublicKey: String? = null
- var additionalHexEncodedPublicKey: String? = null
+ var publicKey: String? = null
+ var additionalPublicKey: String? = null
var isRSSFeed = false
var isLarge = false
@@ -52,11 +52,11 @@ class ProfilePictureView : RelativeLayout {
// region Updating
fun update() {
- val hexEncodedPublicKey = hexEncodedPublicKey ?: return
- val additionalHexEncodedPublicKey = additionalHexEncodedPublicKey
- doubleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
- singleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey == null && !isRSSFeed && !isLarge) View.VISIBLE else View.INVISIBLE
- largeSingleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey == null && !isRSSFeed && isLarge) View.VISIBLE else View.INVISIBLE
+ val publicKey = publicKey ?: return
+ val additionalPublicKey = additionalPublicKey
+ doubleModeImageViewContainer.visibility = if (additionalPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
+ singleModeImageViewContainer.visibility = if (additionalPublicKey == 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
fun setProfilePictureIfNeeded(imageView: ImageView, hexEncodedPublicKey: String, @DimenRes sizeID: Int) {
glide.clear(imageView)
@@ -76,10 +76,10 @@ class ProfilePictureView : RelativeLayout {
imageView.setImageDrawable(null)
}
}
- setProfilePictureIfNeeded(doubleModeImageView1, hexEncodedPublicKey, R.dimen.small_profile_picture_size)
- setProfilePictureIfNeeded(doubleModeImageView2, additionalHexEncodedPublicKey ?: "", R.dimen.small_profile_picture_size)
- setProfilePictureIfNeeded(singleModeImageView, hexEncodedPublicKey, R.dimen.medium_profile_picture_size)
- setProfilePictureIfNeeded(largeSingleModeImageView, hexEncodedPublicKey, R.dimen.large_profile_picture_size)
+ setProfilePictureIfNeeded(doubleModeImageView1, publicKey, R.dimen.small_profile_picture_size)
+ setProfilePictureIfNeeded(doubleModeImageView2, additionalPublicKey ?: "", R.dimen.small_profile_picture_size)
+ setProfilePictureIfNeeded(singleModeImageView, publicKey, R.dimen.medium_profile_picture_size)
+ setProfilePictureIfNeeded(largeSingleModeImageView, publicKey, R.dimen.large_profile_picture_size)
}
// endregion
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/views/UserView.kt b/src/org/thoughtcrime/securesms/loki/views/UserView.kt
index 4bd6ae503a..98e4a77955 100644
--- a/src/org/thoughtcrime/securesms/loki/views/UserView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/UserView.kt
@@ -48,22 +48,22 @@ class UserView : LinearLayout {
val address = user.address.serialize()
if (user.isGroupRecipient) {
if ("Session Public Chat" == user.name || user.address.isRSSFeed) {
- profilePictureView.hexEncodedPublicKey = ""
- profilePictureView.additionalHexEncodedPublicKey = null
+ profilePictureView.publicKey = ""
+ profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = true
} else {
val threadID = GroupManager.getThreadIDFromGroupID(address, context)
val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toList()
?: listOf()
val randomUsers = users.sorted() // Sort to provide a level of stability
- profilePictureView.hexEncodedPublicKey = randomUsers.getOrNull(0) ?: ""
- profilePictureView.additionalHexEncodedPublicKey = randomUsers.getOrNull(1) ?: ""
+ profilePictureView.publicKey = randomUsers.getOrNull(0) ?: ""
+ profilePictureView.additionalPublicKey = randomUsers.getOrNull(1) ?: ""
profilePictureView.isRSSFeed = false
}
} else {
- profilePictureView.hexEncodedPublicKey = address
- profilePictureView.additionalHexEncodedPublicKey = null
+ profilePictureView.publicKey = address
+ profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = false
}
tickImageView.setImageResource(R.drawable.ic_edit_white_24dp)
diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java
index de1f2c9240..091c2e81fc 100644
--- a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java
+++ b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java
@@ -23,7 +23,6 @@ public class OutgoingMediaMessage {
private final int distributionType;
private final int subscriptionId;
private final long expiresIn;
- public boolean isFriendRequest = false;
private final QuoteModel outgoingQuote;
private final List networkFailures = new LinkedList<>();
diff --git a/src/org/thoughtcrime/securesms/mms/PartAuthority.java b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
index 28fe6f6fdc..f6a0b71a9b 100644
--- a/src/org/thoughtcrime/securesms/mms/PartAuthority.java
+++ b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
@@ -36,9 +36,9 @@ public class PartAuthority {
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- uriMatcher.addURI("org.thoughtcrime.securesms", "part/*/#", PART_ROW);
- uriMatcher.addURI("org.thoughtcrime.securesms", "thumb/*/#", THUMB_ROW);
- uriMatcher.addURI("org.thoughtcrime.securesms", "sticker/#", STICKER_ROW);
+ uriMatcher.addURI("network.loki.provider.securesms", "part/*/#", PART_ROW);
+ uriMatcher.addURI("network.loki.provider.securesms", "thumb/*/#", THUMB_ROW);
+ uriMatcher.addURI("network.loki.provider.securesms", "sticker/#", STICKER_ROW);
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
uriMatcher.addURI(BlobProvider.AUTHORITY, BlobProvider.PATH, BLOB_ROW);
diff --git a/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java b/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
index 27f7ddd87f..418b591195 100644
--- a/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
+++ b/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context;
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 {
@@ -22,26 +22,26 @@ public class PushMediaConstraints extends MediaConstraints {
@Override
public int getImageMaxSize(Context context) {
- return LokiFileServerAPI.Companion.getMaxFileSize();
+ return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier());
}
@Override
public int getGifMaxSize(Context context) {
- return LokiFileServerAPI.Companion.getMaxFileSize();
+ return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier());
}
@Override
public int getVideoMaxSize(Context context) {
- return LokiFileServerAPI.Companion.getMaxFileSize();
+ return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier());
}
@Override
public int getAudioMaxSize(Context context) {
- return LokiFileServerAPI.Companion.getMaxFileSize();
+ return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier());
}
@Override
public int getDocumentMaxSize(Context context) {
- return LokiFileServerAPI.Companion.getMaxFileSize();
+ return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier());
}
}
diff --git a/src/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java b/src/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java
index dfa974a1f8..c77e2753b7 100644
--- a/src/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java
+++ b/src/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java
@@ -24,6 +24,7 @@ import android.content.Intent;
import android.os.AsyncTask;
import android.support.v4.app.NotificationManagerCompat;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.whispersystems.libsignal.logging.Log;
@@ -66,7 +67,7 @@ public class AndroidAutoHeardReceiver extends BroadcastReceiver {
messageIdsCollection.addAll(messageIds);
}
- MessageNotifier.updateNotification(context);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
MarkReadReceiver.process(context, messageIdsCollection);
return null;
diff --git a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java
index d196fe432e..d55df50a33 100644
--- a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java
+++ b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java
@@ -25,6 +25,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.RemoteInput;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
@@ -86,7 +87,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver {
List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(replyThreadId, true);
- MessageNotifier.updateNotification(context);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
MarkReadReceiver.process(context, messageIds);
return null;
diff --git a/src/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java
new file mode 100644
index 0000000000..660e1a4ecd
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2011 Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms.notifications;
+
+import android.annotation.SuppressLint;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.text.TextUtils;
+
+import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.contactshare.Contact;
+import org.thoughtcrime.securesms.contactshare.ContactUtil;
+import org.thoughtcrime.securesms.conversation.ConversationActivity;
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
+import org.thoughtcrime.securesms.database.MmsSmsDatabase;
+import org.thoughtcrime.securesms.database.ThreadDatabase;
+import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
+import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
+import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
+import org.thoughtcrime.securesms.mms.SlideDeck;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.service.IncomingMessageObserver;
+import org.thoughtcrime.securesms.service.KeyCachingService;
+import org.thoughtcrime.securesms.util.ServiceUtil;
+import org.thoughtcrime.securesms.util.SpanUtil;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
+import org.whispersystems.signalservice.internal.util.Util;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import me.leolin.shortcutbadger.ShortcutBadger;
+import network.loki.messenger.R;
+
+/**
+ * Handles posting system notifications for new messages.
+ *
+ *
+ * @author Moxie Marlinspike
+ */
+
+public class DefaultMessageNotifier implements MessageNotifier {
+
+ private static final String TAG = DefaultMessageNotifier.class.getSimpleName();
+
+ public static final String EXTRA_REMOTE_REPLY = "extra_remote_reply";
+
+ private static final int SUMMARY_NOTIFICATION_ID = 1338;
+ private static final int PENDING_MESSAGES_ID = 1111;
+ private static final String NOTIFICATION_GROUP = "messages";
+ private static final long MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(2);
+ private static final long DESKTOP_ACTIVITY_PERIOD = TimeUnit.MINUTES.toMillis(1);
+
+ private volatile static long visibleThread = -1;
+ private volatile static long lastDesktopActivityTimestamp = -1;
+ private volatile static long lastAudibleNotification = -1;
+ private static final CancelableExecutor executor = new CancelableExecutor();
+
+ @Override
+ public void setVisibleThread(long threadId) {
+ visibleThread = threadId;
+ }
+
+ @Override
+ public void setLastDesktopActivityTimestamp(long timestamp) {
+ lastDesktopActivityTimestamp = timestamp;
+ }
+
+ @Override
+ public void notifyMessageDeliveryFailed(Context context, Recipient recipient, long threadId) {
+ if (visibleThread == threadId) {
+ sendInThreadNotification(context, recipient);
+ } else {
+ Intent intent = new Intent(context, ConversationActivity.class);
+ intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
+ intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
+ intent.setData((Uri.parse("custom://" + System.currentTimeMillis())));
+
+ FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent);
+ ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))
+ .notify((int)threadId, builder.build());
+ }
+ }
+
+ public void notifyMessagesPending(Context context) {
+ if (!TextSecurePreferences.isNotificationsEnabled(context)) {
+ return;
+ }
+
+ PendingMessageNotificationBuilder builder = new PendingMessageNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context));
+ ServiceUtil.getNotificationManager(context).notify(PENDING_MESSAGES_ID, builder.build());
+ }
+
+ @Override
+ public void cancelDelayedNotifications() {
+ executor.cancel();
+ }
+
+ private void cancelActiveNotifications(@NonNull Context context) {
+ NotificationManager notifications = ServiceUtil.getNotificationManager(context);
+ notifications.cancel(SUMMARY_NOTIFICATION_ID);
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ try {
+ StatusBarNotification[] activeNotifications = notifications.getActiveNotifications();
+
+ for (StatusBarNotification activeNotification : activeNotifications) {
+ if (activeNotification.getId() != CallNotificationBuilder.WEBRTC_NOTIFICATION) {
+ notifications.cancel(activeNotification.getId());
+ }
+ }
+ } catch (Throwable e) {
+ // XXX Appears to be a ROM bug, see #6043
+ Log.w(TAG, e);
+ notifications.cancelAll();
+ }
+ }
+ }
+
+ private void cancelOrphanedNotifications(@NonNull Context context, NotificationState notificationState) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ try {
+ NotificationManager notifications = ServiceUtil.getNotificationManager(context);
+ StatusBarNotification[] activeNotifications = notifications.getActiveNotifications();
+
+ for (StatusBarNotification notification : activeNotifications) {
+ boolean validNotification = false;
+
+ if (notification.getId() != SUMMARY_NOTIFICATION_ID &&
+ notification.getId() != CallNotificationBuilder.WEBRTC_NOTIFICATION &&
+ notification.getId() != KeyCachingService.SERVICE_RUNNING_ID &&
+ notification.getId() != IncomingMessageObserver.FOREGROUND_ID &&
+ notification.getId() != PENDING_MESSAGES_ID)
+ {
+ for (NotificationItem item : notificationState.getNotifications()) {
+ if (notification.getId() == (SUMMARY_NOTIFICATION_ID + item.getThreadId())) {
+ validNotification = true;
+ break;
+ }
+ }
+
+ if (!validNotification) {
+ notifications.cancel(notification.getId());
+ }
+ }
+ }
+ } catch (Throwable e) {
+ // XXX Android ROM Bug, see #6043
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ @Override
+ public void updateNotification(@NonNull Context context) {
+ if (!TextSecurePreferences.isNotificationsEnabled(context)) {
+ return;
+ }
+
+ updateNotification(context, false, 0);
+ }
+
+ @Override
+ public void updateNotification(@NonNull Context context, long threadId)
+ {
+ if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) {
+ Log.i(TAG, "Scheduling delayed notification...");
+ executor.execute(new DelayedNotification(context, threadId));
+ } else {
+ updateNotification(context, threadId, true);
+ }
+ }
+
+ @Override
+ public void updateNotification(@NonNull Context context, long threadId, boolean signal)
+ {
+ boolean isVisible = visibleThread == threadId;
+
+ ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
+ Recipient recipients = DatabaseFactory.getThreadDatabase(context)
+ .getRecipientForThreadId(threadId);
+
+ if (isVisible) {
+ List messageIds = threads.setRead(threadId, false);
+ MarkReadReceiver.process(context, messageIds);
+ }
+
+ if (!TextSecurePreferences.isNotificationsEnabled(context) ||
+ (recipients != null && recipients.isMuted()))
+ {
+ return;
+ }
+
+ if (isVisible) {
+ sendInThreadNotification(context, threads.getRecipientForThreadId(threadId));
+ } else {
+ updateNotification(context, signal, 0);
+ }
+ }
+
+ @Override
+ public void updateNotification(@NonNull Context context, boolean signal, int reminderCount)
+ {
+ Cursor telcoCursor = null;
+ Cursor pushCursor = null;
+
+ try {
+ telcoCursor = DatabaseFactory.getMmsSmsDatabase(context).getUnread();
+ pushCursor = DatabaseFactory.getPushDatabase(context).getPending();
+
+ if ((telcoCursor == null || telcoCursor.isAfterLast()) &&
+ (pushCursor == null || pushCursor.isAfterLast()))
+ {
+ cancelActiveNotifications(context);
+ updateBadge(context, 0);
+ clearReminder(context);
+ return;
+ }
+
+ NotificationState notificationState = constructNotificationState(context, telcoCursor);
+
+ if (signal && (System.currentTimeMillis() - lastAudibleNotification) < MIN_AUDIBLE_PERIOD_MILLIS) {
+ signal = false;
+ } else if (signal) {
+ lastAudibleNotification = System.currentTimeMillis();
+ }
+
+ if (notificationState.hasMultipleThreads()) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ for (long threadId : notificationState.getThreads()) {
+ sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), false, true);
+ }
+ }
+
+ sendMultipleThreadNotification(context, notificationState, signal);
+ } else {
+ sendSingleThreadNotification(context, notificationState, signal, false);
+ }
+
+ cancelOrphanedNotifications(context, notificationState);
+ updateBadge(context, notificationState.getMessageCount());
+
+ if (signal) {
+ scheduleReminder(context, reminderCount);
+ }
+ } finally {
+ if (telcoCursor != null) telcoCursor.close();
+ if (pushCursor != null) pushCursor.close();
+ }
+ }
+
+ private void sendSingleThreadNotification(@NonNull Context context,
+ @NonNull NotificationState notificationState,
+ boolean signal, boolean bundled)
+ {
+ Log.i(TAG, "sendSingleThreadNotification() signal: " + signal + " bundled: " + bundled);
+
+ if (notificationState.getNotifications().isEmpty()) {
+ if (!bundled) cancelActiveNotifications(context);
+ Log.i(TAG, "Empty notification state. Skipping.");
+ return;
+ }
+
+ SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context));
+ List notifications = notificationState.getNotifications();
+ Recipient recipient = notifications.get(0).getRecipient();
+ int notificationId = (int) (SUMMARY_NOTIFICATION_ID + (bundled ? notifications.get(0).getThreadId() : 0));
+
+
+ builder.setThread(notifications.get(0).getRecipient());
+ builder.setMessageCount(notificationState.getMessageCount());
+ builder.setPrimaryMessageBody(recipient, notifications.get(0).getIndividualRecipient(),
+ notifications.get(0).getText(), notifications.get(0).getSlideDeck());
+ builder.setContentIntent(notifications.get(0).getPendingIntent(context));
+ builder.setDeleteIntent(notificationState.getDeleteIntent(context));
+ builder.setOnlyAlertOnce(!signal);
+ builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
+ builder.setAutoCancel(true);
+
+ long timestamp = notifications.get(0).getTimestamp();
+ if (timestamp != 0) builder.setWhen(timestamp);
+
+ long threadID = notifications.get(0).getThreadId();
+
+ ReplyMethod replyMethod = ReplyMethod.forRecipient(context, recipient);
+
+ boolean canReply = SessionMetaProtocol.canUserReplyToNotification(recipient);
+
+ PendingIntent quickReplyIntent = canReply ? notificationState.getQuickReplyIntent(context, recipient) : null;
+ PendingIntent remoteReplyIntent = canReply ? notificationState.getRemoteReplyIntent(context, recipient, replyMethod) : null;
+
+ builder.addActions(notificationState.getMarkAsReadIntent(context, notificationId),
+ quickReplyIntent,
+ remoteReplyIntent,
+ replyMethod);
+
+ if (canReply) {
+ builder.addAndroidAutoAction(notificationState.getAndroidAutoReplyIntent(context, recipient),
+ notificationState.getAndroidAutoHeardIntent(context, notificationId),
+ notifications.get(0).getTimestamp());
+ }
+
+ ListIterator iterator = notifications.listIterator(notifications.size());
+
+ while(iterator.hasPrevious()) {
+ NotificationItem item = iterator.previous();
+ builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText());
+ }
+
+ if (signal) {
+ builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate());
+ builder.setTicker(notifications.get(0).getIndividualRecipient(),
+ notifications.get(0).getText());
+ }
+
+ if (bundled) {
+ builder.setGroup(NOTIFICATION_GROUP);
+ builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
+ }
+
+ Notification notification = builder.build();
+ NotificationManagerCompat.from(context).notify(notificationId, notification);
+ Log.i(TAG, "Posted notification. " + notification.toString());
+ }
+
+ private void sendMultipleThreadNotification(@NonNull Context context,
+ @NonNull NotificationState notificationState,
+ boolean signal)
+ {
+ Log.i(TAG, "sendMultiThreadNotification() signal: " + signal);
+
+ MultipleRecipientNotificationBuilder builder = new MultipleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context));
+ List notifications = notificationState.getNotifications();
+
+ builder.setMessageCount(notificationState.getMessageCount(), notificationState.getThreadCount());
+ builder.setMostRecentSender(notifications.get(0).getIndividualRecipient(), notifications.get(0).getRecipient());
+ builder.setGroup(NOTIFICATION_GROUP);
+ builder.setDeleteIntent(notificationState.getDeleteIntent(context));
+ builder.setOnlyAlertOnce(!signal);
+ builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
+ builder.setAutoCancel(true);
+
+ long timestamp = notifications.get(0).getTimestamp();
+ if (timestamp != 0) builder.setWhen(timestamp);
+
+ builder.addActions(notificationState.getMarkAsReadIntent(context, SUMMARY_NOTIFICATION_ID));
+
+ ListIterator iterator = notifications.listIterator(notifications.size());
+
+ while(iterator.hasPrevious()) {
+ NotificationItem item = iterator.previous();
+ builder.addMessageBody(item.getIndividualRecipient(), item.getRecipient(), item.getText());
+ }
+
+ if (signal) {
+ builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate());
+ builder.setTicker(notifications.get(0).getIndividualRecipient(),
+ notifications.get(0).getText());
+ }
+
+ Notification notification = builder.build();
+ NotificationManagerCompat.from(context).notify(SUMMARY_NOTIFICATION_ID, builder.build());
+ Log.i(TAG, "Posted notification. " + notification.toString());
+ }
+
+ private void sendInThreadNotification(Context context, Recipient recipient) {
+ if (!TextSecurePreferences.isInThreadNotifications(context) ||
+ ServiceUtil.getAudioManager(context).getRingerMode() != AudioManager.RINGER_MODE_NORMAL)
+ {
+ return;
+ }
+
+ Uri uri = null;
+ if (recipient != null) {
+ uri = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context, recipient) : recipient.getMessageRingtone();
+ }
+
+ if (uri == null) {
+ uri = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context) : TextSecurePreferences.getNotificationRingtone(context);
+ }
+
+ if (uri.toString().isEmpty()) {
+ Log.d(TAG, "ringtone uri is empty");
+ return;
+ }
+
+ Ringtone ringtone = RingtoneManager.getRingtone(context, uri);
+
+ if (ringtone == null) {
+ Log.w(TAG, "ringtone is null");
+ return;
+ }
+
+ if (Build.VERSION.SDK_INT >= 21) {
+ ringtone.setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
+ .build());
+ } else {
+ ringtone.setStreamType(AudioManager.STREAM_NOTIFICATION);
+ }
+
+ ringtone.play();
+ }
+
+ private NotificationState constructNotificationState(@NonNull Context context,
+ @NonNull Cursor cursor)
+ {
+ NotificationState notificationState = new NotificationState();
+ MmsSmsDatabase.Reader reader = DatabaseFactory.getMmsSmsDatabase(context).readerFor(cursor);
+
+ MessageRecord record;
+
+ while ((record = reader.getNext()) != null) {
+ long id = record.getId();
+ boolean mms = record.isMms() || record.isMmsNotification();
+ Recipient recipient = record.getIndividualRecipient();
+ Recipient conversationRecipient = record.getRecipient();
+ long threadId = record.getThreadId();
+ CharSequence body = record.getDisplayBody(context);
+ Recipient threadRecipients = null;
+ SlideDeck slideDeck = null;
+ long timestamp = record.getTimestamp();
+
+
+ if (threadId != -1) {
+ threadRecipients = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
+ }
+
+ if (KeyCachingService.isLocked(context)) {
+ body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message));
+ } else if (record.isMms() && !((MmsMessageRecord) record).getSharedContacts().isEmpty()) {
+ Contact contact = ((MmsMessageRecord) record).getSharedContacts().get(0);
+ body = ContactUtil.getStringSummary(context, contact);
+ } else if (record.isMms() && ((MmsMessageRecord) record).getSlideDeck().getStickerSlide() != null) {
+ body = SpanUtil.italic(context.getString(R.string.MessageNotifier_sticker));
+ slideDeck = ((MmsMessageRecord) record).getSlideDeck();
+ } else if (record.isMms() && TextUtils.isEmpty(body) && !((MmsMessageRecord) record).getSlideDeck().getSlides().isEmpty()) {
+ body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message));
+ slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
+ } else if (record.isMms() && !record.isMmsNotification() && !((MmsMessageRecord) record).getSlideDeck().getSlides().isEmpty()) {
+ String message = context.getString(R.string.MessageNotifier_media_message_with_text, body);
+ int italicLength = message.length() - body.length();
+ body = SpanUtil.italic(message, italicLength);
+ slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
+ }
+
+ if (threadRecipients == null || !threadRecipients.isMuted()) {
+ notificationState.addNotification(new NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck));
+ }
+ }
+
+ reader.close();
+ return notificationState;
+ }
+
+ private void updateBadge(Context context, int count) {
+ try {
+ if (count == 0) ShortcutBadger.removeCount(context);
+ else ShortcutBadger.applyCount(context, count);
+ } catch (Throwable t) {
+ // NOTE :: I don't totally trust this thing, so I'm catching
+ // everything.
+ Log.w("MessageNotifier", t);
+ }
+ }
+
+ private void scheduleReminder(Context context, int count) {
+ if (count >= TextSecurePreferences.getRepeatAlertsCount(context)) {
+ return;
+ }
+
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION);
+ alarmIntent.putExtra("reminder_count", count);
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ long timeout = TimeUnit.MINUTES.toMillis(2);
+
+ alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent);
+ }
+
+ @Override
+ public void clearReminder(Context context) {
+ Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ alarmManager.cancel(pendingIntent);
+ }
+
+ public static class ReminderReceiver extends BroadcastReceiver {
+
+ public static final String REMINDER_ACTION = "network.loki.securesms.MessageNotifier.REMINDER_ACTION";
+
+ @SuppressLint("StaticFieldLeak")
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ int reminderCount = intent.getIntExtra("reminder_count", 0);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, true, reminderCount + 1);
+
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+ }
+
+ private static class DelayedNotification implements Runnable {
+
+ private static final long DELAY = TimeUnit.SECONDS.toMillis(5);
+
+ private final AtomicBoolean canceled = new AtomicBoolean(false);
+
+ private final Context context;
+ private final long threadId;
+ private final long delayUntil;
+
+ private DelayedNotification(Context context, long threadId) {
+ this.context = context;
+ this.threadId = threadId;
+ this.delayUntil = System.currentTimeMillis() + DELAY;
+ }
+
+ @Override
+ public void run() {
+ long delayMillis = delayUntil - System.currentTimeMillis();
+ Log.i(TAG, "Waiting to notify: " + delayMillis);
+
+ if (delayMillis > 0) {
+ Util.sleep(delayMillis);
+ }
+
+ if (!canceled.get()) {
+ Log.i(TAG, "Not canceled, notifying...");
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, threadId, true);
+ ApplicationContext.getInstance(context).messageNotifier.cancelDelayedNotifications();
+ } else {
+ Log.w(TAG, "Canceled, not notifying...");
+ }
+ }
+
+ public void cancel() {
+ canceled.set(true);
+ }
+ }
+
+ private static class CancelableExecutor {
+
+ private final Executor executor = Executors.newSingleThreadExecutor();
+ private final Set tasks = new HashSet<>();
+
+ public void execute(final DelayedNotification runnable) {
+ synchronized (tasks) {
+ tasks.add(runnable);
+ }
+
+ Runnable wrapper = new Runnable() {
+ @Override
+ public void run() {
+ runnable.run();
+
+ synchronized (tasks) {
+ tasks.remove(runnable);
+ }
+ }
+ };
+
+ executor.execute(wrapper);
+ }
+
+ public void cancel() {
+ synchronized (tasks) {
+ for (DelayedNotification task : tasks) {
+ task.cancel();
+ }
+ }
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java b/src/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java
index ea065fd18a..ed2727f647 100644
--- a/src/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java
+++ b/src/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java
@@ -6,6 +6,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.DatabaseFactory;
public class DeleteNotificationReceiver extends BroadcastReceiver {
@@ -18,7 +19,7 @@ public class DeleteNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
if (DELETE_NOTIFICATION_ACTION.equals(intent.getAction())) {
- MessageNotifier.clearReminder(context);
+ ApplicationContext.getInstance(context).messageNotifier.clearReminder(context);
final long[] ids = intent.getLongArrayExtra(EXTRA_IDS);
final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java
index 04aa6d8ead..942de714c3 100644
--- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java
+++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java
@@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.thoughtcrime.securesms.loki.protocol.SyncMessagesProtocol;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
-import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol;
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import java.util.LinkedList;
import java.util.List;
@@ -61,7 +61,7 @@ public class MarkReadReceiver extends BroadcastReceiver {
process(context, messageIdsCollection);
- MessageNotifier.updateNotification(context);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
return null;
}
@@ -93,7 +93,7 @@ public class MarkReadReceiver extends BroadcastReceiver {
for (Address address : addressMap.keySet()) {
List timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList();
// 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
Set linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(address.serialize());
for (String device : linkedDevices) {
diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
index c3fd7ad1e3..16f4343bea 100644
--- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
+++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
@@ -1,615 +1,19 @@
-/*
- * Copyright (C) 2011 Whisper Systems
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
package org.thoughtcrime.securesms.notifications;
-import android.annotation.SuppressLint;
-import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.service.notification.StatusBarNotification;
-import android.support.annotation.NonNull;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
-import android.text.TextUtils;
-import org.thoughtcrime.securesms.contactshare.Contact;
-import org.thoughtcrime.securesms.contactshare.ContactUtil;
-import org.thoughtcrime.securesms.conversation.ConversationActivity;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
-import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
-import org.thoughtcrime.securesms.database.MmsSmsDatabase;
-import org.thoughtcrime.securesms.database.ThreadDatabase;
-import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
-import org.thoughtcrime.securesms.database.model.MessageRecord;
-import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
-import org.thoughtcrime.securesms.logging.Log;
-import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
-import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.service.IncomingMessageObserver;
-import org.thoughtcrime.securesms.service.KeyCachingService;
-import org.thoughtcrime.securesms.util.ServiceUtil;
-import org.thoughtcrime.securesms.util.SpanUtil;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
-import org.whispersystems.signalservice.internal.util.Util;
-import java.util.HashSet;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import me.leolin.shortcutbadger.ShortcutBadger;
-import network.loki.messenger.R;
-
-/**
- * Handles posting system notifications for new messages.
- *
- *
- * @author Moxie Marlinspike
- */
-
-public class MessageNotifier {
-
- private static final String TAG = MessageNotifier.class.getSimpleName();
-
- public static final String EXTRA_REMOTE_REPLY = "extra_remote_reply";
-
- private static final int SUMMARY_NOTIFICATION_ID = 1338;
- private static final int PENDING_MESSAGES_ID = 1111;
- private static final String NOTIFICATION_GROUP = "messages";
- private static final long MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(2);
- private static final long DESKTOP_ACTIVITY_PERIOD = TimeUnit.MINUTES.toMillis(1);
-
- private volatile static long visibleThread = -1;
- private volatile static long lastDesktopActivityTimestamp = -1;
- private volatile static long lastAudibleNotification = -1;
- private static final CancelableExecutor executor = new CancelableExecutor();
-
- public static void setVisibleThread(long threadId) {
- visibleThread = threadId;
- }
-
- public static void setLastDesktopActivityTimestamp(long timestamp) {
- lastDesktopActivityTimestamp = timestamp;
- }
-
- public static void notifyMessageDeliveryFailed(Context context, Recipient recipient, long threadId) {
- if (visibleThread == threadId) {
- sendInThreadNotification(context, recipient);
- } else {
- Intent intent = new Intent(context, ConversationActivity.class);
- intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
- intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
- intent.setData((Uri.parse("custom://" + System.currentTimeMillis())));
-
- FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent);
- ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))
- .notify((int)threadId, builder.build());
- }
- }
-
- public static void notifyMessagesPending(Context context) {
- if (!TextSecurePreferences.isNotificationsEnabled(context)) {
- return;
- }
-
- PendingMessageNotificationBuilder builder = new PendingMessageNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context));
- ServiceUtil.getNotificationManager(context).notify(PENDING_MESSAGES_ID, builder.build());
- }
-
- public static void cancelDelayedNotifications() {
- executor.cancel();
- }
-
- private static void cancelActiveNotifications(@NonNull Context context) {
- NotificationManager notifications = ServiceUtil.getNotificationManager(context);
- notifications.cancel(SUMMARY_NOTIFICATION_ID);
-
- if (Build.VERSION.SDK_INT >= 23) {
- try {
- StatusBarNotification[] activeNotifications = notifications.getActiveNotifications();
-
- for (StatusBarNotification activeNotification : activeNotifications) {
- if (activeNotification.getId() != CallNotificationBuilder.WEBRTC_NOTIFICATION) {
- notifications.cancel(activeNotification.getId());
- }
- }
- } catch (Throwable e) {
- // XXX Appears to be a ROM bug, see #6043
- Log.w(TAG, e);
- notifications.cancelAll();
- }
- }
- }
-
- private static void cancelOrphanedNotifications(@NonNull Context context, NotificationState notificationState) {
- if (Build.VERSION.SDK_INT >= 23) {
- try {
- NotificationManager notifications = ServiceUtil.getNotificationManager(context);
- StatusBarNotification[] activeNotifications = notifications.getActiveNotifications();
-
- for (StatusBarNotification notification : activeNotifications) {
- boolean validNotification = false;
-
- if (notification.getId() != SUMMARY_NOTIFICATION_ID &&
- notification.getId() != CallNotificationBuilder.WEBRTC_NOTIFICATION &&
- notification.getId() != KeyCachingService.SERVICE_RUNNING_ID &&
- notification.getId() != IncomingMessageObserver.FOREGROUND_ID &&
- notification.getId() != PENDING_MESSAGES_ID)
- {
- for (NotificationItem item : notificationState.getNotifications()) {
- if (notification.getId() == (SUMMARY_NOTIFICATION_ID + item.getThreadId())) {
- validNotification = true;
- break;
- }
- }
-
- if (!validNotification) {
- notifications.cancel(notification.getId());
- }
- }
- }
- } catch (Throwable e) {
- // XXX Android ROM Bug, see #6043
- Log.w(TAG, e);
- }
- }
- }
-
- public static void updateNotification(@NonNull Context context) {
- if (!TextSecurePreferences.isNotificationsEnabled(context)) {
- return;
- }
-
- updateNotification(context, false, 0);
- }
-
- public static void updateNotification(@NonNull Context context, long threadId)
- {
- if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) {
- Log.i(TAG, "Scheduling delayed notification...");
- executor.execute(new DelayedNotification(context, threadId));
- } else {
- updateNotification(context, threadId, true);
- }
- }
-
- public static void updateNotification(@NonNull Context context,
- long threadId,
- boolean signal)
- {
- boolean isVisible = visibleThread == threadId;
-
- ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
- Recipient recipients = DatabaseFactory.getThreadDatabase(context)
- .getRecipientForThreadId(threadId);
-
- if (isVisible) {
- List messageIds = threads.setRead(threadId, false);
- MarkReadReceiver.process(context, messageIds);
- }
-
- if (!TextSecurePreferences.isNotificationsEnabled(context) ||
- (recipients != null && recipients.isMuted()))
- {
- return;
- }
-
- if (isVisible) {
- sendInThreadNotification(context, threads.getRecipientForThreadId(threadId));
- } else {
- updateNotification(context, signal, 0);
- }
- }
-
- private static void updateNotification(@NonNull Context context,
- boolean signal,
- int reminderCount)
- {
- Cursor telcoCursor = null;
- Cursor pushCursor = null;
-
- try {
- telcoCursor = DatabaseFactory.getMmsSmsDatabase(context).getUnread();
- pushCursor = DatabaseFactory.getPushDatabase(context).getPending();
-
- if ((telcoCursor == null || telcoCursor.isAfterLast()) &&
- (pushCursor == null || pushCursor.isAfterLast()))
- {
- cancelActiveNotifications(context);
- updateBadge(context, 0);
- clearReminder(context);
- return;
- }
-
- NotificationState notificationState = constructNotificationState(context, telcoCursor);
-
- if (signal && (System.currentTimeMillis() - lastAudibleNotification) < MIN_AUDIBLE_PERIOD_MILLIS) {
- signal = false;
- } else if (signal) {
- lastAudibleNotification = System.currentTimeMillis();
- }
-
- if (notificationState.hasMultipleThreads()) {
- if (Build.VERSION.SDK_INT >= 23) {
- for (long threadId : notificationState.getThreads()) {
- sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), false, true);
- }
- }
-
- sendMultipleThreadNotification(context, notificationState, signal);
- } else {
- sendSingleThreadNotification(context, notificationState, signal, false);
- }
-
- cancelOrphanedNotifications(context, notificationState);
- updateBadge(context, notificationState.getMessageCount());
-
- if (signal) {
- scheduleReminder(context, reminderCount);
- }
- } finally {
- if (telcoCursor != null) telcoCursor.close();
- if (pushCursor != null) pushCursor.close();
- }
- }
-
- private static void sendSingleThreadNotification(@NonNull Context context,
- @NonNull NotificationState notificationState,
- boolean signal, boolean bundled)
- {
- Log.i(TAG, "sendSingleThreadNotification() signal: " + signal + " bundled: " + bundled);
-
- if (notificationState.getNotifications().isEmpty()) {
- if (!bundled) cancelActiveNotifications(context);
- Log.i(TAG, "Empty notification state. Skipping.");
- return;
- }
-
- SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context));
- List notifications = notificationState.getNotifications();
- Recipient recipient = notifications.get(0).getRecipient();
- int notificationId = (int) (SUMMARY_NOTIFICATION_ID + (bundled ? notifications.get(0).getThreadId() : 0));
-
-
- builder.setThread(notifications.get(0).getRecipient());
- builder.setMessageCount(notificationState.getMessageCount());
- builder.setPrimaryMessageBody(recipient, notifications.get(0).getIndividualRecipient(),
- notifications.get(0).getText(), notifications.get(0).getSlideDeck());
- builder.setContentIntent(notifications.get(0).getPendingIntent(context));
- builder.setDeleteIntent(notificationState.getDeleteIntent(context));
- builder.setOnlyAlertOnce(!signal);
- builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
- builder.setAutoCancel(true);
-
- long timestamp = notifications.get(0).getTimestamp();
- if (timestamp != 0) builder.setWhen(timestamp);
-
- long threadID = notifications.get(0).getThreadId();
-
- ReplyMethod replyMethod = ReplyMethod.forRecipient(context, recipient);
-
- boolean canReply = SessionMetaProtocol.canUserReplyToNotification(recipient, context);
-
- PendingIntent quickReplyIntent = canReply ? notificationState.getQuickReplyIntent(context, recipient) : null;
- PendingIntent remoteReplyIntent = canReply ? notificationState.getRemoteReplyIntent(context, recipient, replyMethod) : null;
-
- builder.addActions(notificationState.getMarkAsReadIntent(context, notificationId),
- quickReplyIntent,
- remoteReplyIntent,
- replyMethod);
-
- if (canReply) {
- builder.addAndroidAutoAction(notificationState.getAndroidAutoReplyIntent(context, recipient),
- notificationState.getAndroidAutoHeardIntent(context, notificationId),
- notifications.get(0).getTimestamp());
- }
-
- ListIterator iterator = notifications.listIterator(notifications.size());
-
- while(iterator.hasPrevious()) {
- NotificationItem item = iterator.previous();
- builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText());
- }
-
- if (signal) {
- builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate());
- builder.setTicker(notifications.get(0).getIndividualRecipient(),
- notifications.get(0).getText());
- }
-
- if (bundled) {
- builder.setGroup(NOTIFICATION_GROUP);
- builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
- }
-
- Notification notification = builder.build();
- NotificationManagerCompat.from(context).notify(notificationId, notification);
- Log.i(TAG, "Posted notification. " + notification.toString());
- }
-
- private static void sendMultipleThreadNotification(@NonNull Context context,
- @NonNull NotificationState notificationState,
- boolean signal)
- {
- Log.i(TAG, "sendMultiThreadNotification() signal: " + signal);
-
- MultipleRecipientNotificationBuilder builder = new MultipleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context));
- List notifications = notificationState.getNotifications();
-
- builder.setMessageCount(notificationState.getMessageCount(), notificationState.getThreadCount());
- builder.setMostRecentSender(notifications.get(0).getIndividualRecipient(), notifications.get(0).getRecipient());
- builder.setGroup(NOTIFICATION_GROUP);
- builder.setDeleteIntent(notificationState.getDeleteIntent(context));
- builder.setOnlyAlertOnce(!signal);
- builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
- builder.setAutoCancel(true);
-
- long timestamp = notifications.get(0).getTimestamp();
- if (timestamp != 0) builder.setWhen(timestamp);
-
- builder.addActions(notificationState.getMarkAsReadIntent(context, SUMMARY_NOTIFICATION_ID));
-
- ListIterator iterator = notifications.listIterator(notifications.size());
-
- while(iterator.hasPrevious()) {
- NotificationItem item = iterator.previous();
- builder.addMessageBody(item.getIndividualRecipient(), item.getRecipient(), item.getText());
- }
-
- if (signal) {
- builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate());
- builder.setTicker(notifications.get(0).getIndividualRecipient(),
- notifications.get(0).getText());
- }
-
- Notification notification = builder.build();
- NotificationManagerCompat.from(context).notify(SUMMARY_NOTIFICATION_ID, builder.build());
- Log.i(TAG, "Posted notification. " + notification.toString());
- }
-
- private static void sendInThreadNotification(Context context, Recipient recipient) {
- if (!TextSecurePreferences.isInThreadNotifications(context) ||
- ServiceUtil.getAudioManager(context).getRingerMode() != AudioManager.RINGER_MODE_NORMAL)
- {
- return;
- }
-
- Uri uri = null;
- if (recipient != null) {
- uri = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context, recipient) : recipient.getMessageRingtone();
- }
-
- if (uri == null) {
- uri = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context) : TextSecurePreferences.getNotificationRingtone(context);
- }
-
- if (uri.toString().isEmpty()) {
- Log.d(TAG, "ringtone uri is empty");
- return;
- }
-
- Ringtone ringtone = RingtoneManager.getRingtone(context, uri);
-
- if (ringtone == null) {
- Log.w(TAG, "ringtone is null");
- return;
- }
-
- if (Build.VERSION.SDK_INT >= 21) {
- ringtone.setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
- .build());
- } else {
- ringtone.setStreamType(AudioManager.STREAM_NOTIFICATION);
- }
-
- ringtone.play();
- }
-
- private static NotificationState constructNotificationState(@NonNull Context context,
- @NonNull Cursor cursor)
- {
- NotificationState notificationState = new NotificationState();
- MmsSmsDatabase.Reader reader = DatabaseFactory.getMmsSmsDatabase(context).readerFor(cursor);
-
- MessageRecord record;
-
- while ((record = reader.getNext()) != null) {
- long id = record.getId();
- boolean mms = record.isMms() || record.isMmsNotification();
- Recipient recipient = record.getIndividualRecipient();
- Recipient conversationRecipient = record.getRecipient();
- long threadId = record.getThreadId();
- CharSequence body = record.getDisplayBody(context);
- Recipient threadRecipients = null;
- SlideDeck slideDeck = null;
- long timestamp = record.getTimestamp();
-
-
- if (threadId != -1) {
- threadRecipients = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
- }
-
- if (KeyCachingService.isLocked(context)) {
- body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message));
- } else if (record.isMms() && !((MmsMessageRecord) record).getSharedContacts().isEmpty()) {
- Contact contact = ((MmsMessageRecord) record).getSharedContacts().get(0);
- body = ContactUtil.getStringSummary(context, contact);
- } else if (record.isMms() && ((MmsMessageRecord) record).getSlideDeck().getStickerSlide() != null) {
- body = SpanUtil.italic(context.getString(R.string.MessageNotifier_sticker));
- slideDeck = ((MmsMessageRecord) record).getSlideDeck();
- } else if (record.isMms() && TextUtils.isEmpty(body) && !((MmsMessageRecord) record).getSlideDeck().getSlides().isEmpty()) {
- body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message));
- slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
- } else if (record.isMms() && !record.isMmsNotification() && !((MmsMessageRecord) record).getSlideDeck().getSlides().isEmpty()) {
- String message = context.getString(R.string.MessageNotifier_media_message_with_text, body);
- int italicLength = message.length() - body.length();
- body = SpanUtil.italic(message, italicLength);
- slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
- }
-
- if (threadRecipients == null || !threadRecipients.isMuted()) {
- notificationState.addNotification(new NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck));
- }
- }
-
- reader.close();
- return notificationState;
- }
-
- private static void updateBadge(Context context, int count) {
- try {
- if (count == 0) ShortcutBadger.removeCount(context);
- else ShortcutBadger.applyCount(context, count);
- } catch (Throwable t) {
- // NOTE :: I don't totally trust this thing, so I'm catching
- // everything.
- Log.w("MessageNotifier", t);
- }
- }
-
- private static void scheduleReminder(Context context, int count) {
- if (count >= TextSecurePreferences.getRepeatAlertsCount(context)) {
- return;
- }
-
- AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION);
- alarmIntent.putExtra("reminder_count", count);
-
- PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
- long timeout = TimeUnit.MINUTES.toMillis(2);
-
- alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent);
- }
-
- public static void clearReminder(Context context) {
- Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
- AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- alarmManager.cancel(pendingIntent);
- }
-
- public static class ReminderReceiver extends BroadcastReceiver {
-
- public static final String REMINDER_ACTION = "network.loki.securesms.MessageNotifier.REMINDER_ACTION";
-
- @SuppressLint("StaticFieldLeak")
- @Override
- public void onReceive(final Context context, final Intent intent) {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... params) {
- int reminderCount = intent.getIntExtra("reminder_count", 0);
- MessageNotifier.updateNotification(context, true, reminderCount + 1);
-
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- }
-
- private static class DelayedNotification implements Runnable {
-
- private static final long DELAY = TimeUnit.SECONDS.toMillis(5);
-
- private final AtomicBoolean canceled = new AtomicBoolean(false);
-
- private final Context context;
- private final long threadId;
- private final long delayUntil;
-
- private DelayedNotification(Context context, long threadId) {
- this.context = context;
- this.threadId = threadId;
- this.delayUntil = System.currentTimeMillis() + DELAY;
- }
-
- @Override
- public void run() {
- long delayMillis = delayUntil - System.currentTimeMillis();
- Log.i(TAG, "Waiting to notify: " + delayMillis);
-
- if (delayMillis > 0) {
- Util.sleep(delayMillis);
- }
-
- if (!canceled.get()) {
- Log.i(TAG, "Not canceled, notifying...");
- MessageNotifier.updateNotification(context, threadId, true);
- MessageNotifier.cancelDelayedNotifications();
- } else {
- Log.w(TAG, "Canceled, not notifying...");
- }
- }
-
- public void cancel() {
- canceled.set(true);
- }
- }
-
- private static class CancelableExecutor {
-
- private final Executor executor = Executors.newSingleThreadExecutor();
- private final Set tasks = new HashSet<>();
-
- public void execute(final DelayedNotification runnable) {
- synchronized (tasks) {
- tasks.add(runnable);
- }
-
- Runnable wrapper = new Runnable() {
- @Override
- public void run() {
- runnable.run();
-
- synchronized (tasks) {
- tasks.remove(runnable);
- }
- }
- };
-
- executor.execute(wrapper);
- }
-
- public void cancel() {
- synchronized (tasks) {
- for (DelayedNotification task : tasks) {
- task.cancel();
- }
- }
- }
- }
+import androidx.annotation.NonNull;
+
+public interface MessageNotifier {
+ void setVisibleThread(long threadId);
+ void setLastDesktopActivityTimestamp(long timestamp);
+ void notifyMessageDeliveryFailed(Context context, Recipient recipient, long threadId);
+ void cancelDelayedNotifications();
+ void updateNotification(@NonNull Context context);
+ void updateNotification(@NonNull Context context, long threadId);
+ void updateNotification(@NonNull Context context, long threadId, boolean signal);
+ void updateNotification(@android.support.annotation.NonNull Context context, boolean signal, int reminderCount);
+ void clearReminder(@NonNull Context context);
}
diff --git a/src/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java
new file mode 100644
index 0000000000..ee2601e3ff
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java
@@ -0,0 +1,131 @@
+package org.thoughtcrime.securesms.notifications;
+
+import android.content.Context;
+import android.os.Looper;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+
+import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.loki.api.PublicChatManager;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.util.Debouncer;
+import org.whispersystems.signalservice.loki.api.Poller;
+
+import java.util.concurrent.TimeUnit;
+
+public class OptimizedMessageNotifier implements MessageNotifier {
+ private final MessageNotifier wrapped;
+ private final Debouncer debouncer;
+
+ @MainThread
+ public OptimizedMessageNotifier(@NonNull MessageNotifier wrapped) {
+ this.wrapped = wrapped;
+ this.debouncer = new Debouncer(TimeUnit.SECONDS.toMillis(1));
+ }
+
+ @Override
+ public void setVisibleThread(long threadId) { wrapped.setVisibleThread(threadId); }
+
+ @Override
+ public void setLastDesktopActivityTimestamp(long timestamp) { wrapped.setLastDesktopActivityTimestamp(timestamp);}
+
+ @Override
+ public void notifyMessageDeliveryFailed(Context context, Recipient recipient, long threadId) {
+ wrapped.notifyMessageDeliveryFailed(context, recipient, threadId);
+ }
+
+ @Override
+ public void cancelDelayedNotifications() { wrapped.cancelDelayedNotifications(); }
+
+ @Override
+ public void updateNotification(@NonNull Context context) {
+ Poller lokiPoller = ApplicationContext.getInstance(context).poller;
+ PublicChatManager publicChatManager = ApplicationContext.getInstance(context).publicChatManager;
+ boolean isCaughtUp = true;
+ if (lokiPoller != null) {
+ isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
+ }
+
+ if (publicChatManager != null) {
+ isCaughtUp = isCaughtUp && publicChatManager.areAllCaughtUp();
+ }
+
+ if (isCaughtUp) {
+ performOnBackgroundThreadIfNeeded(() -> wrapped.updateNotification(context));
+ } else {
+ debouncer.publish(() -> performOnBackgroundThreadIfNeeded(() -> wrapped.updateNotification(context)));
+ }
+ }
+
+ @Override
+ public void updateNotification(@NonNull Context context, long threadId) {
+ Poller lokiPoller = ApplicationContext.getInstance(context).poller;
+ PublicChatManager publicChatManager = ApplicationContext.getInstance(context).publicChatManager;
+ boolean isCaughtUp = true;
+ if (lokiPoller != null) {
+ isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
+ }
+
+ if (publicChatManager != null) {
+ isCaughtUp = isCaughtUp && publicChatManager.areAllCaughtUp();
+ }
+
+ if (isCaughtUp) {
+ performOnBackgroundThreadIfNeeded(() -> wrapped.updateNotification(context, threadId));
+ } else {
+ debouncer.publish(() -> performOnBackgroundThreadIfNeeded(() -> wrapped.updateNotification(context, threadId)));
+ }
+ }
+
+ @Override
+ public void updateNotification(@NonNull Context context, long threadId, boolean signal) {
+ Poller lokiPoller = ApplicationContext.getInstance(context).poller;
+ PublicChatManager publicChatManager = ApplicationContext.getInstance(context).publicChatManager;
+ boolean isCaughtUp = true;
+ if (lokiPoller != null) {
+ isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
+ }
+
+ if (publicChatManager != null) {
+ isCaughtUp = isCaughtUp && publicChatManager.areAllCaughtUp();
+ }
+
+ if (isCaughtUp) {
+ performOnBackgroundThreadIfNeeded(() -> wrapped.updateNotification(context, threadId, signal));
+ } else {
+ debouncer.publish(() -> performOnBackgroundThreadIfNeeded(() -> wrapped.updateNotification(context, threadId, signal)));
+ }
+ }
+
+ @Override
+ public void updateNotification(@android.support.annotation.NonNull Context context, boolean signal, int reminderCount) {
+ Poller lokiPoller = ApplicationContext.getInstance(context).poller;
+ PublicChatManager publicChatManager = ApplicationContext.getInstance(context).publicChatManager;
+ boolean isCaughtUp = true;
+ if (lokiPoller != null) {
+ isCaughtUp = isCaughtUp && lokiPoller.isCaughtUp();
+ }
+
+ if (publicChatManager != null) {
+ isCaughtUp = isCaughtUp && publicChatManager.areAllCaughtUp();
+ }
+
+ if (isCaughtUp) {
+ performOnBackgroundThreadIfNeeded(() -> wrapped.updateNotification(context, signal, reminderCount));
+ } else {
+ debouncer.publish(() -> performOnBackgroundThreadIfNeeded(() -> wrapped.updateNotification(context, signal, reminderCount)));
+ }
+ }
+
+ @Override
+ public void clearReminder(@NonNull Context context) { wrapped.clearReminder(context); }
+
+ private void performOnBackgroundThreadIfNeeded(Runnable r) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ new Thread(r).start();
+ } else {
+ r.run();
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java
index 7b42d10aca..84ff4c1664 100644
--- a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java
+++ b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java
@@ -25,6 +25,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.RemoteInput;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
@@ -59,7 +60,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
final Address address = intent.getParcelableExtra(ADDRESS_EXTRA);
final ReplyMethod replyMethod = (ReplyMethod) intent.getSerializableExtra(REPLY_METHOD);
- final CharSequence responseText = remoteInput.getCharSequence(MessageNotifier.EXTRA_REMOTE_REPLY);
+ final CharSequence responseText = remoteInput.getCharSequence(DefaultMessageNotifier.EXTRA_REMOTE_REPLY);
if (address == null) throw new AssertionError("No address specified");
if (replyMethod == null) throw new AssertionError("No reply method specified");
@@ -91,7 +92,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, true);
- MessageNotifier.updateNotification(context);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
MarkReadReceiver.process(context, messageIds);
return null;
diff --git a/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java
index 7f8bf3abc9..0476cdff2f 100644
--- a/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java
+++ b/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java
@@ -177,14 +177,14 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
replyAction = new Action.Builder(R.drawable.ic_reply_white_36dp,
actionName,
wearableReplyIntent)
- .addRemoteInput(new RemoteInput.Builder(MessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
+ .addRemoteInput(new RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
.build();
}
Action wearableReplyAction = new Action.Builder(R.drawable.ic_reply,
actionName,
wearableReplyIntent)
- .addRemoteInput(new RemoteInput.Builder(MessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
+ .addRemoteInput(new RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
.build();
diff --git a/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java
index 9b21b90dad..b3ddb9cd1b 100644
--- a/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java
+++ b/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java
@@ -220,7 +220,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
- MessageNotifier.updateNotification(getActivity());
+ ApplicationContext.getInstance(getActivity()).messageNotifier.updateNotification(getActivity());
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
diff --git a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java
index d27752fac3..4e1d198a15 100644
--- a/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java
+++ b/src/org/thoughtcrime/securesms/push/MessageSenderEventListener.java
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.push;
import android.content.Context;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
-import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
@@ -18,19 +17,4 @@ public class MessageSenderEventListener implements SignalServiceMessageSender.Ev
public void onSecurityEvent(SignalServiceAddress textSecureAddress) {
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);
- }
}
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java
index 9e45ef78d4..ca78ea9e84 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipient.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java
@@ -52,6 +52,7 @@ import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import java.util.Collections;
import java.util.HashSet;
@@ -554,7 +555,12 @@ public class Recipient implements RecipientModifiedListener {
}
public synchronized boolean isBlocked() {
- return blocked;
+ String masterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(this.address.serialize());
+ if (masterPublicKey != null) {
+ return Recipient.from(context, Address.fromSerialized(masterPublicKey), false).blocked;
+ } else {
+ return blocked;
+ }
}
public void setBlocked(boolean blocked) {
diff --git a/src/org/thoughtcrime/securesms/service/KeyCachingService.java b/src/org/thoughtcrime/securesms/service/KeyCachingService.java
index 796249b659..b07710e730 100644
--- a/src/org/thoughtcrime/securesms/service/KeyCachingService.java
+++ b/src/org/thoughtcrime/securesms/service/KeyCachingService.java
@@ -115,7 +115,7 @@ public class KeyCachingService extends Service {
@Override
protected Void doInBackground(Void... params) {
if (!DatabaseUpgradeActivity.isUpdate(KeyCachingService.this)) {
- MessageNotifier.updateNotification(KeyCachingService.this);
+ ApplicationContext.getInstance(KeyCachingService.this).messageNotifier.updateNotification(KeyCachingService.this);
}
return null;
}
@@ -188,7 +188,7 @@ public class KeyCachingService extends Service {
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
- MessageNotifier.updateNotification(KeyCachingService.this);
+ ApplicationContext.getInstance(KeyCachingService.this).messageNotifier.updateNotification(KeyCachingService.this);
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
diff --git a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java
index 0de3ac406f..855b327131 100644
--- a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java
+++ b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java
@@ -721,7 +721,7 @@ public class WebRtcCallService extends Service implements InjectableType,
private void insertMissedCall(@NonNull Recipient recipient, boolean signal) {
Pair messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(recipient.getAddress());
- MessageNotifier.updateNotification(this, messageAndThreadId.second, signal);
+ ApplicationContext.getInstance(this).messageNotifier.updateNotification(this, messageAndThreadId.second, signal);
}
private void handleAnswerCall(Intent intent) {
diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java
index 35f48d702c..9f16df5c7c 100644
--- a/src/org/thoughtcrime/securesms/sms/MessageSender.java
+++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java
@@ -36,10 +36,10 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
+import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
+import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.logging.Log;
-import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
-import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
@@ -76,12 +76,7 @@ public class MessageSender {
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
- if (FriendRequestProtocol.shouldUpdateFriendRequestStatusFromOutgoingTextMessage(context, message)) {
- FriendRequestProtocol.setFriendRequestStatusToSendingIfNeeded(context, messageId, allocatedThreadId);
- }
-
- sendTextMessage(context, recipient, forceSms, keyExchange, messageId, message.isEndSession());
+ sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
return allocatedThreadId;
}
@@ -107,11 +102,6 @@ public class MessageSender {
Recipient recipient = message.getRecipient();
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());
return allocatedThreadId;
} catch (MmsException e) {
@@ -135,7 +125,7 @@ public class MessageSender {
if (messageRecord.isMms()) {
sendMediaMessage(context, recipient, forceSms, messageId, expiresIn);
} else {
- sendTextMessage(context, recipient, forceSms, keyExchange, messageId, messageRecord.isEndSession());
+ sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
}
}
@@ -152,21 +142,25 @@ public class MessageSender {
private static void sendTextMessage(Context context, Recipient recipient,
boolean forceSms, boolean keyExchange,
- long messageId, boolean isEndSession)
+ long messageId)
{
if (isLocalSelfSend(context, recipient, forceSms)) {
sendLocalTextSelf(context, messageId);
} else {
- sendTextPush(context, recipient, messageId, isEndSession);
+ sendTextPush(context, recipient, messageId);
}
}
- private static void sendTextPush(Context context, Recipient recipient, long messageId, boolean isEndSession) {
- MultiDeviceProtocol.sendTextPush(context, recipient, messageId, isEndSession);
+ private static void sendTextPush(Context context, Recipient recipient, long messageId) {
+ JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
+ jobManager.add(new PushTextSendJob(messageId, recipient.getAddress()));
+// MultiDeviceProtocol.sendTextPush(context, recipient, messageId);
}
private static void sendMediaPush(Context context, Recipient recipient, long messageId) {
- MultiDeviceProtocol.sendMediaPush(context, recipient, messageId);
+ JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
+ PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress());
+// MultiDeviceProtocol.sendMediaPush(context, recipient, messageId);
}
private static void sendGroupPush(Context context, Recipient recipient, long messageId, Address filterAddress) {
diff --git a/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java b/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java
index 387fec8fa1..e74cb8ff99 100644
--- a/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java
+++ b/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java
@@ -9,7 +9,6 @@ public class OutgoingTextMessage {
private final String message;
private final int subscriptionId;
private final long expiresIn;
- public boolean isFriendRequest = false;
public OutgoingTextMessage(Recipient recipient, String message, int subscriptionId) {
this(recipient, message, 0, subscriptionId);
diff --git a/src/org/thoughtcrime/securesms/util/IdentityUtil.java b/src/org/thoughtcrime/securesms/util/IdentityUtil.java
index ae712c9f71..fe5542e9a5 100644
--- a/src/org/thoughtcrime/securesms/util/IdentityUtil.java
+++ b/src/org/thoughtcrime/securesms/util/IdentityUtil.java
@@ -6,6 +6,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address;
@@ -143,7 +144,7 @@ public class IdentityUtil {
Optional insertResult = smsDatabase.insertMessageInbox(individualUpdate);
if (insertResult.isPresent()) {
- MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
}
diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
index 86d0bb4f74..669c265c57 100644
--- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
+++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
@@ -214,14 +214,6 @@ public class TextSecurePreferences {
public static void setLastFCMUploadTime(Context context, long value) {
setLongPreference(context, LAST_FCM_TOKEN_UPLOAD_TIME, value);
}
-
- public static boolean hasSeenPNModeSheet(Context context) {
- return getBooleanPreference(context, HAS_SEEN_PN_MODE_SHEET, false);
- }
-
- public static void setHasSeenPNModeSheet(Context context, boolean value) {
- setBooleanPreference(context, HAS_SEEN_PN_MODE_SHEET, value);
- }
// endregion
public static boolean isScreenLockEnabled(@NonNull Context context) {
@@ -1316,5 +1308,13 @@ public class TextSecurePreferences {
public static void clearAll(Context context) {
PreferenceManager.getDefaultSharedPreferences(context).edit().clear().commit();
}
+
+ public static boolean getHasSeenMultiDeviceRemovalSheet(Context context) {
+ return getBooleanPreference(context, "has_seen_multi_device_removal_sheet", false);
+ }
+
+ public static void setHasSeenMultiDeviceRemovalSheet(Context context) {
+ setBooleanPreference(context, "has_seen_multi_device_removal_sheet", true);
+ }
// endregion
}