Add support for view-once messages.
@ -299,7 +299,7 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".mediasend.MediaSendActivity"
|
<activity android:name=".mediasend.MediaSendActivity"
|
||||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
android:theme="@style/TextSecure.FullScreenMedia"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@ -336,6 +336,13 @@
|
|||||||
android:windowSoftInputMode="stateUnchanged"
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".revealable.RevealableMessageActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/TextSecure.FullScreenMedia"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".stickers.StickerManagementActivity"
|
<activity android:name=".stickers.StickerManagementActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
@ -577,6 +584,8 @@
|
|||||||
|
|
||||||
<receiver android:name=".service.ExpirationListener" />
|
<receiver android:name=".service.ExpirationListener" />
|
||||||
|
|
||||||
|
<receiver android:name=".revealable.RevealableMessageManager$RevealAlarm" />
|
||||||
|
|
||||||
<provider android:name=".providers.PartProvider"
|
<provider android:name=".providers.PartProvider"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
BIN
res/drawable-hdpi/ic_view_infinite_32.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
res/drawable-hdpi/ic_view_once_32.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
res/drawable-mdpi/ic_view_infinite_32.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
res/drawable-mdpi/ic_view_once_32.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
res/drawable-xhdpi/ic_view_infinite_32.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
res/drawable-xhdpi/ic_view_once_32.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
res/drawable-xxhdpi/ic_view_infinite_32.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
res/drawable-xxhdpi/ic_view_once_32.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
res/drawable-xxxhdpi/ic_view_infinite_32.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
res/drawable-xxxhdpi/ic_view_once_32.png
Normal file
After Width: | Height: | Size: 13 KiB |
9
res/drawable/ic_arrow_down_circle_outline_24.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,1C5.9,1 1,5.9 1,12s4.9,11 11,11s11,-4.9 11,-11S18.1,1 12,1zM12,21.5c-5.2,0 -9.5,-4.3 -9.5,-9.5S6.8,2.5 12,2.5s9.5,4.3 9.5,9.5C21.5,17.2 17.2,21.5 12,21.5zM17,12.2l1.1,1.1l-5.5,5.5c-0.3,0.3 -0.8,0.3 -1.1,0l-5.5,-5.5L7,12.2l3.6,3.6c0,0 0.3,0.4 0.7,1V5h1.5v11.8c0.4,-0.6 0.7,-1 0.7,-1L17,12.2z"/>
|
||||||
|
</vector>
|
9
res/drawable/ic_play_outline_24.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M5.5,3.6 L20.006,12 5.5,20.4V3.6M4.72,1.575c-0.426,0 -0.72,0.339 -0.72,0.925v19c0,0.586 0.294,0.925 0.72,0.925a1.168,1.168 0,0 0,0.578 -0.177l16.4,-9.5a0.8,0.8 0,0 0,0 -1.5L5.3,1.752a1.168,1.168 0,0 0,-0.578 -0.177Z"/>
|
||||||
|
</vector>
|
9
res/drawable/ic_play_solid_24.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21.7,11.248a0.8,0.8 0,0 1,0 1.5l-16.4,9.5c-0.714,0.414 -1.3,0.077 -1.3,-0.748V2.5c0,-0.825 0.584,-1.162 1.3,-0.748Z"/>
|
||||||
|
</vector>
|
9
res/drawable/ic_timer_24.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M13,14.5c0,0.6 -0.4,1 -1,1s-1,-0.4 -1,-1L11.5,8h1L13,14.5zM21,14c0,5 -4,9 -9,9s-9,-4 -9,-9c0,-2 0.7,-4 1.9,-5.6C4.3,8.3 3.9,7.6 4,6.9S4.9,5.9 5.5,6C6,6.2 6.3,6.5 6.4,6.9c1.4,-1.1 3,-1.8 4.7,-1.9L10,1h4l-1.2,4c1.7,0.2 3.4,0.8 4.8,1.9c0.2,-0.7 0.8,-1.1 1.5,-0.9c0.7,0.2 1.1,0.8 0.9,1.5c-0.1,0.4 -0.5,0.8 -0.9,0.9C20.3,10 21,12 21,14zM19.5,14c0,-4.1 -3.4,-7.5 -7.5,-7.5S4.5,9.9 4.5,14s3.4,7.5 7.5,7.5S19.5,18.1 19.5,14z"/>
|
||||||
|
</vector>
|
9
res/drawable/ic_timer_disabled_24.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M7.9,20.3l-1.1,1.1c4.1,2.8 9.7,1.8 12.5,-2.3c2.1,-3.1 2.1,-7.2 0,-10.3l-1.1,1.1c2.3,3.5 1.3,8.1 -2.2,10.4C13.6,21.9 10.4,21.9 7.9,20.3zM3.5,22.5L21,5l-1,-1l-2.8,2.7c-1.3,-1 -2.8,-1.5 -4.4,-1.7L14,1h-4l1.2,4C9.5,5.2 7.8,5.8 6.4,6.9C6.3,6.3 5.6,5.9 4.9,6S3.9,6.9 4,7.5C4.2,8 4.5,8.3 4.9,8.4C3.7,10 3,12 3,14c0,1.9 0.6,3.7 1.7,5.2l-2.2,2.2L3.5,22.5zM5.8,18.2c-2.3,-3.4 -1.4,-8.1 2.1,-10.4c2.5,-1.7 5.8,-1.7 8.3,0l-3.4,3.4L12.5,8h-1l-0.4,4.8L5.8,18.2z"/>
|
||||||
|
</vector>
|
@ -153,6 +153,16 @@
|
|||||||
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||||
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding" />
|
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/revealable_view_stub"
|
||||||
|
android:layout="@layout/conversation_item_received_revealable"
|
||||||
|
android:layout_width="148dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||||
|
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||||
|
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
android:id="@+id/conversation_item_body"
|
android:id="@+id/conversation_item_body"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
12
res/layout/conversation_item_received_revealable.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.revealable.RevealableMessageView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/revealable_view"
|
||||||
|
android:layout_width="148dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:revealable_unopenedForegroundColor="?conversation_item_received_text_primary_color"
|
||||||
|
app:revealable_openedForegroundColor="?conversation_item_sent_text_primary_color"
|
||||||
|
tools:visibility="visible"/>
|
@ -94,6 +94,16 @@
|
|||||||
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||||
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding" />
|
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/revealable_view_stub"
|
||||||
|
android:layout="@layout/conversation_item_sent_revealable"
|
||||||
|
android:layout_width="148dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||||
|
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||||
|
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
android:id="@+id/conversation_item_body"
|
android:id="@+id/conversation_item_body"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
12
res/layout/conversation_item_sent_revealable.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.revealable.RevealableMessageView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/revealable_view"
|
||||||
|
android:layout_width="148dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:revealable_unopenedForegroundColor="?conversation_item_sent_text_primary_color"
|
||||||
|
app:revealable_openedForegroundColor="?conversation_item_sent_text_primary_color"
|
||||||
|
tools:visibility="visible"/>
|
@ -60,6 +60,15 @@
|
|||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="12dp"
|
android:layout_marginBottom="12dp"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/mediasend_reveal_toggle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
tools:src="@drawable/ic_view_infinite_32" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/mediasend_compose_container"
|
android:id="@+id/mediasend_compose_container"
|
||||||
|
24
res/layout/revealable_message_activity.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/core_black">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/reveal_image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="fitCenter"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/reveal_close_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="19dp"
|
||||||
|
android:layout_marginStart="19dp"
|
||||||
|
android:tint="@color/core_white"
|
||||||
|
app:srcCompat="@drawable/ic_x"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
50
res/layout/revealable_message_view.xml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
tools:parentTag="android.widget.LinearLayout">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:layout_marginBottom="2dp"
|
||||||
|
android:layout_gravity="center_vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/revealable_icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
tools:src="@drawable/ic_play_solid_24"/>
|
||||||
|
|
||||||
|
<com.pnikosis.materialishprogress.ProgressWheel
|
||||||
|
android:id="@+id/revealable_progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:matProg_barColor="@color/core_white"
|
||||||
|
app:matProg_rimColor="@color/transparent"
|
||||||
|
app:matProg_linearProgress="true"
|
||||||
|
app:matProg_spinSpeed="0.2"
|
||||||
|
app:matProg_barWidth="2dp"
|
||||||
|
app:matProg_rimWidth="2dp"
|
||||||
|
app:matProg_circleRadius="24dp"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/revealable_text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
style="@style/Signal.Text.Preview"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="@string/RevealableMessageView_view_photo" />
|
||||||
|
|
||||||
|
</merge>
|
@ -94,6 +94,7 @@
|
|||||||
<attr name="conversation_item_sticky_date_background" format="reference" />
|
<attr name="conversation_item_sticky_date_background" format="reference" />
|
||||||
<attr name="conversation_item_sticky_date_text_color" format="color" />
|
<attr name="conversation_item_sticky_date_text_color" format="color" />
|
||||||
<attr name="conversation_item_image_outline_color" format="color" />
|
<attr name="conversation_item_image_outline_color" format="color" />
|
||||||
|
<attr name="conversation_item_reveal_viewed_background_color" format="color" />
|
||||||
|
|
||||||
<attr name="dialog_info_icon" format="reference" />
|
<attr name="dialog_info_icon" format="reference" />
|
||||||
<attr name="dialog_alert_icon" format="reference" />
|
<attr name="dialog_alert_icon" format="reference" />
|
||||||
@ -347,4 +348,9 @@
|
|||||||
<attr name="labeledEditText_background" format="color" />
|
<attr name="labeledEditText_background" format="color" />
|
||||||
<attr name="labeledEditText_textLayout" format="reference" />
|
<attr name="labeledEditText_textLayout" format="reference" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
|
<declare-styleable name="RevealableMessageView">
|
||||||
|
<attr name="revealable_unopenedForegroundColor" format="color" />
|
||||||
|
<attr name="revealable_openedForegroundColor" format="color" />
|
||||||
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -639,6 +639,11 @@
|
|||||||
<string name="RegistrationActivity_enter_the_code_we_sent_to_s">Enter the code we sent to %s</string>
|
<string name="RegistrationActivity_enter_the_code_we_sent_to_s">Enter the code we sent to %s</string>
|
||||||
<string name="RegistrationActivity_call">Call</string>
|
<string name="RegistrationActivity_call">Call</string>
|
||||||
|
|
||||||
|
<!-- RevealableMessageView -->
|
||||||
|
<string name="RevealableMessageView_view_photo">View Photo</string>
|
||||||
|
<string name="RevealableMessageView_viewed">Viewed</string>
|
||||||
|
<string name="RevealableMessageView_photo">Photo</string>
|
||||||
|
|
||||||
<!-- ScribbleActivity -->
|
<!-- ScribbleActivity -->
|
||||||
<string name="ScribbleActivity_save_failure">Failed to save image changes</string>
|
<string name="ScribbleActivity_save_failure">Failed to save image changes</string>
|
||||||
|
|
||||||
@ -677,6 +682,7 @@
|
|||||||
<string name="SmsMessageRecord_secure_session_reset_s">%s reset the secure session.</string>
|
<string name="SmsMessageRecord_secure_session_reset_s">%s reset the secure session.</string>
|
||||||
<string name="SmsMessageRecord_duplicate_message">Duplicate message.</string>
|
<string name="SmsMessageRecord_duplicate_message">Duplicate message.</string>
|
||||||
<string name="SmsMessageRecord_this_message_could_not_be_processed_because_it_was_sent_from_a_newer_version">This message could not be processed because it was sent from a newer version of Signal. You can ask your contact to send this message again after you update.</string>
|
<string name="SmsMessageRecord_this_message_could_not_be_processed_because_it_was_sent_from_a_newer_version">This message could not be processed because it was sent from a newer version of Signal. You can ask your contact to send this message again after you update.</string>
|
||||||
|
<string name="SmsMessageRecord_error_handling_incoming_message">Error handling incoming message</string>
|
||||||
|
|
||||||
<!-- StickerManagementActivity -->
|
<!-- StickerManagementActivity -->
|
||||||
<string name="StickerManagementActivity_stickers">Stickers</string>
|
<string name="StickerManagementActivity_stickers">Stickers</string>
|
||||||
@ -707,6 +713,8 @@
|
|||||||
<string name="ThreadRecord_called_you">Called you</string>
|
<string name="ThreadRecord_called_you">Called you</string>
|
||||||
<string name="ThreadRecord_missed_call">Missed call</string>
|
<string name="ThreadRecord_missed_call">Missed call</string>
|
||||||
<string name="ThreadRecord_media_message">Media message</string>
|
<string name="ThreadRecord_media_message">Media message</string>
|
||||||
|
<string name="ThreadRecord_sticker">Sticker</string>
|
||||||
|
<string name="ThreadRecord_disappearing_photo">Disappearing photo</string>
|
||||||
<string name="ThreadRecord_s_is_on_signal">%s is on Signal!</string>
|
<string name="ThreadRecord_s_is_on_signal">%s is on Signal!</string>
|
||||||
<string name="ThreadRecord_disappearing_messages_disabled">Disappearing messages disabled</string>
|
<string name="ThreadRecord_disappearing_messages_disabled">Disappearing messages disabled</string>
|
||||||
<string name="ThreadRecord_disappearing_message_time_updated_to_s">Disappearing message time set to %s</string>
|
<string name="ThreadRecord_disappearing_message_time_updated_to_s">Disappearing message time set to %s</string>
|
||||||
@ -794,6 +802,7 @@
|
|||||||
<string name="MessageNotifier_mark_read">Mark read</string>
|
<string name="MessageNotifier_mark_read">Mark read</string>
|
||||||
<string name="MessageNotifier_media_message">Media message</string>
|
<string name="MessageNotifier_media_message">Media message</string>
|
||||||
<string name="MessageNotifier_sticker">Sticker</string>
|
<string name="MessageNotifier_sticker">Sticker</string>
|
||||||
|
<string name="MessageNotifier_disappearing_photo">Disappearing photo</string>
|
||||||
<string name="MessageNotifier_reply">Reply</string>
|
<string name="MessageNotifier_reply">Reply</string>
|
||||||
<string name="MessageNotifier_signal_message">Signal Message</string>
|
<string name="MessageNotifier_signal_message">Signal Message</string>
|
||||||
<string name="MessageNotifier_unsecured_sms">Unsecured SMS</string>
|
<string name="MessageNotifier_unsecured_sms">Unsecured SMS</string>
|
||||||
|
@ -219,6 +219,7 @@
|
|||||||
<item name="conversation_item_sticky_date_background">@drawable/sticky_date_header_background_light</item>
|
<item name="conversation_item_sticky_date_background">@drawable/sticky_date_header_background_light</item>
|
||||||
<item name="conversation_item_sticky_date_text_color">@color/core_grey_60</item>
|
<item name="conversation_item_sticky_date_text_color">@color/core_grey_60</item>
|
||||||
<item name="conversation_item_image_outline_color">@color/transparent_black_30</item>
|
<item name="conversation_item_image_outline_color">@color/transparent_black_30</item>
|
||||||
|
<item name="conversation_item_reveal_viewed_background_color">@color/core_white</item>
|
||||||
|
|
||||||
<item name="quick_camera_icon">@drawable/quick_camera_light</item>
|
<item name="quick_camera_icon">@drawable/quick_camera_light</item>
|
||||||
<item name="quick_mic_icon">@drawable/ic_mic_grey600_24dp</item>
|
<item name="quick_mic_icon">@drawable/ic_mic_grey600_24dp</item>
|
||||||
@ -355,6 +356,7 @@
|
|||||||
<item name="conversation_item_sticky_date_background">@drawable/sticky_date_header_background_dark</item>
|
<item name="conversation_item_sticky_date_background">@drawable/sticky_date_header_background_dark</item>
|
||||||
<item name="conversation_item_sticky_date_text_color">@color/core_grey_25</item>
|
<item name="conversation_item_sticky_date_text_color">@color/core_grey_25</item>
|
||||||
<item name="conversation_item_image_outline_color">@color/transparent_white_30</item>
|
<item name="conversation_item_image_outline_color">@color/transparent_white_30</item>
|
||||||
|
<item name="conversation_item_reveal_viewed_background_color">@color/core_black</item>
|
||||||
|
|
||||||
<item name="contact_list_divider">@drawable/contact_list_divider_dark</item>
|
<item name="contact_list_divider">@drawable/contact_list_divider_dark</item>
|
||||||
|
|
||||||
@ -495,4 +497,8 @@
|
|||||||
<style name="TextSecure.DarkRegistrationTheme" parent="TextSecure.DarkNoActionBar">
|
<style name="TextSecure.DarkRegistrationTheme" parent="TextSecure.DarkNoActionBar">
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="TextSecure.FullScreenMedia" parent="TextSecure.DarkNoActionBar">
|
||||||
|
<item name="android:windowFullscreen">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -62,6 +62,7 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
|||||||
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealableMessageManager;
|
||||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||||
@ -90,12 +91,13 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
|
|
||||||
private static final String TAG = ApplicationContext.class.getSimpleName();
|
private static final String TAG = ApplicationContext.class.getSimpleName();
|
||||||
|
|
||||||
private ExpiringMessageManager expiringMessageManager;
|
private ExpiringMessageManager expiringMessageManager;
|
||||||
private TypingStatusRepository typingStatusRepository;
|
private RevealableMessageManager revealableMessageManager;
|
||||||
private TypingStatusSender typingStatusSender;
|
private TypingStatusRepository typingStatusRepository;
|
||||||
private JobManager jobManager;
|
private TypingStatusSender typingStatusSender;
|
||||||
private IncomingMessageObserver incomingMessageObserver;
|
private JobManager jobManager;
|
||||||
private PersistentLogger persistentLogger;
|
private IncomingMessageObserver incomingMessageObserver;
|
||||||
|
private PersistentLogger persistentLogger;
|
||||||
|
|
||||||
private volatile boolean isAppVisible;
|
private volatile boolean isAppVisible;
|
||||||
|
|
||||||
@ -114,6 +116,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
initializeJobManager();
|
initializeJobManager();
|
||||||
initializeMessageRetrieval();
|
initializeMessageRetrieval();
|
||||||
initializeExpiringMessageManager();
|
initializeExpiringMessageManager();
|
||||||
|
initializeRevealableMessageManager();
|
||||||
initializeTypingStatusRepository();
|
initializeTypingStatusRepository();
|
||||||
initializeTypingStatusSender();
|
initializeTypingStatusSender();
|
||||||
initializeGcmCheck();
|
initializeGcmCheck();
|
||||||
@ -154,6 +157,10 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
return expiringMessageManager;
|
return expiringMessageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RevealableMessageManager getRevealableMessageManager() {
|
||||||
|
return revealableMessageManager;
|
||||||
|
}
|
||||||
|
|
||||||
public TypingStatusRepository getTypingStatusRepository() {
|
public TypingStatusRepository getTypingStatusRepository() {
|
||||||
return typingStatusRepository;
|
return typingStatusRepository;
|
||||||
}
|
}
|
||||||
@ -244,6 +251,10 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
this.expiringMessageManager = new ExpiringMessageManager(this);
|
this.expiringMessageManager = new ExpiringMessageManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeRevealableMessageManager() {
|
||||||
|
this.revealableMessageManager = new RevealableMessageManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeTypingStatusRepository() {
|
private void initializeTypingStatusRepository() {
|
||||||
this.typingStatusRepository = new TypingStatusRepository();
|
this.typingStatusRepository = new TypingStatusRepository();
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ public interface BindableConversationItem extends Unbindable {
|
|||||||
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
||||||
void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms);
|
void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms);
|
||||||
void onStickerClicked(@NonNull StickerLocator stickerLocator);
|
void onStickerClicked(@NonNull StickerLocator stickerLocator);
|
||||||
|
void onRevealableMessageClicked(@NonNull MmsMessageRecord messageRecord);
|
||||||
void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView);
|
void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView);
|
||||||
void onAddToContactsClicked(@NonNull Contact contact);
|
void onAddToContactsClicked(@NonNull Contact contact);
|
||||||
void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
|
void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
|
||||||
|
public class TombstoneAttachment extends Attachment {
|
||||||
|
|
||||||
|
public TombstoneAttachment(@NonNull String contentType, boolean quote) {
|
||||||
|
super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, null, null, null, null, null, false, 0, 0, quote, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Uri getDataUri() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Uri getThumbnailUri() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -75,8 +75,8 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
for (String table : tables) {
|
for (String table : tables) {
|
||||||
if (table.equals(SmsDatabase.TABLE_NAME) || table.equals(MmsDatabase.TABLE_NAME)) {
|
if (table.equals(MmsDatabase.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0, null, count);
|
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMessage, null, count);
|
||||||
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
|
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count);
|
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count);
|
||||||
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
|
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
|
||||||
@ -253,14 +253,20 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isNonExpiringMessage(@NonNull Cursor cursor) {
|
||||||
|
return cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0 &&
|
||||||
|
cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.REVEAL_DURATION)) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, long mmsId) {
|
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, long mmsId) {
|
||||||
String[] columns = new String[] { MmsDatabase.EXPIRES_IN };
|
String[] columns = new String[] { MmsDatabase.EXPIRES_IN, MmsDatabase.REVEAL_DURATION };
|
||||||
String where = MmsDatabase.ID + " = ?";
|
String where = MmsDatabase.ID + " = ?";
|
||||||
String[] args = new String[] { String.valueOf(mmsId) };
|
String[] args = new String[] { String.valueOf(mmsId) };
|
||||||
|
|
||||||
try (Cursor mmsCursor = db.query(MmsDatabase.TABLE_NAME, columns, where, args, null, null, null)) {
|
try (Cursor mmsCursor = db.query(MmsDatabase.TABLE_NAME, columns, where, args, null, null, null)) {
|
||||||
if (mmsCursor != null && mmsCursor.moveToFirst()) {
|
if (mmsCursor != null && mmsCursor.moveToFirst()) {
|
||||||
return mmsCursor.getLong(0) == 0;
|
return mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN)) == 0 &&
|
||||||
|
mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow(MmsDatabase.REVEAL_DURATION)) == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,12 +25,16 @@ public class Outliner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void draw(Canvas canvas) {
|
public void draw(Canvas canvas) {
|
||||||
|
draw(canvas, 0, canvas.getWidth(), canvas.getHeight(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(Canvas canvas, int top, int right, int bottom, int left) {
|
||||||
final float halfStrokeWidth = outlinePaint.getStrokeWidth() / 2;
|
final float halfStrokeWidth = outlinePaint.getStrokeWidth() / 2;
|
||||||
|
|
||||||
bounds.left = halfStrokeWidth;
|
bounds.left = left + halfStrokeWidth;
|
||||||
bounds.top = halfStrokeWidth;
|
bounds.top = top + halfStrokeWidth;
|
||||||
bounds.right = canvas.getWidth() - halfStrokeWidth;
|
bounds.right = right - halfStrokeWidth;
|
||||||
bounds.bottom = canvas.getHeight() - halfStrokeWidth;
|
bounds.bottom = bottom - halfStrokeWidth;
|
||||||
|
|
||||||
corners.reset();
|
corners.reset();
|
||||||
corners.addRoundRect(bounds, radii, Path.Direction.CW);
|
corners.addRoundRect(bounds, radii, Path.Direction.CW);
|
||||||
|
@ -92,6 +92,8 @@ import org.thoughtcrime.securesms.RegistrationActivity;
|
|||||||
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
|
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
|
||||||
import org.thoughtcrime.securesms.TransportOption;
|
import org.thoughtcrime.securesms.TransportOption;
|
||||||
import org.thoughtcrime.securesms.VerifyIdentityActivity;
|
import org.thoughtcrime.securesms.VerifyIdentityActivity;
|
||||||
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
|
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
|
||||||
import org.thoughtcrime.securesms.audio.AudioRecorder;
|
import org.thoughtcrime.securesms.audio.AudioRecorder;
|
||||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||||
@ -539,6 +541,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
boolean initiating = threadId == -1;
|
boolean initiating = threadId == -1;
|
||||||
TransportOption transport = data.getParcelableExtra(MediaSendActivity.EXTRA_TRANSPORT);
|
TransportOption transport = data.getParcelableExtra(MediaSendActivity.EXTRA_TRANSPORT);
|
||||||
String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE);
|
String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE);
|
||||||
|
long revealDuration = data.getLongExtra(MediaSendActivity.EXTRA_REVEAL_DURATION, 0);
|
||||||
|
QuoteModel quote = (revealDuration == 0) ? inputPanel.getQuote().orNull() : null;
|
||||||
SlideDeck slideDeck = new SlideDeck();
|
SlideDeck slideDeck = new SlideDeck();
|
||||||
|
|
||||||
if (transport == null) {
|
if (transport == null) {
|
||||||
@ -566,10 +570,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
sendMediaMessage(transport.isSms(),
|
sendMediaMessage(transport.isSms(),
|
||||||
message,
|
message,
|
||||||
slideDeck,
|
slideDeck,
|
||||||
inputPanel.getQuote().orNull(),
|
quote,
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
expiresIn,
|
expiresIn,
|
||||||
|
revealDuration,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
initiating,
|
initiating,
|
||||||
true).addListener(new AssertedSuccessListener<Void>() {
|
true).addListener(new AssertedSuccessListener<Void>() {
|
||||||
@ -1807,7 +1812,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
long expiresIn = recipient.getExpireMessages() * 1000L;
|
long expiresIn = recipient.getExpireMessages() * 1000L;
|
||||||
boolean initiating = threadId == -1;
|
boolean initiating = threadId == -1;
|
||||||
|
|
||||||
sendMediaMessage(isSmsForced(), "", attachmentManager.buildSlideDeck(), null, contacts, Collections.emptyList(), expiresIn, subscriptionId, initiating, false);
|
sendMediaMessage(isSmsForced(), "", attachmentManager.buildSlideDeck(), null, contacts, Collections.emptyList(), expiresIn, 0, subscriptionId, initiating, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectContactInfo(ContactData contactData) {
|
private void selectContactInfo(ContactData contactData) {
|
||||||
@ -2129,7 +2134,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
} else if (!forceSms && identityRecords.isUntrusted()) {
|
} else if (!forceSms && identityRecords.isUntrusted()) {
|
||||||
handleUntrustedRecipients();
|
handleUntrustedRecipients();
|
||||||
} else if (isMediaMessage) {
|
} else if (isMediaMessage) {
|
||||||
sendMediaMessage(forceSms, expiresIn, subscriptionId, initiating);
|
sendMediaMessage(forceSms, expiresIn, 0, subscriptionId, initiating);
|
||||||
} else {
|
} else {
|
||||||
sendTextMessage(forceSms, expiresIn, subscriptionId, initiating);
|
sendTextMessage(forceSms, expiresIn, subscriptionId, initiating);
|
||||||
}
|
}
|
||||||
@ -2145,11 +2150,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMediaMessage(final boolean forceSms, final long expiresIn, final int subscriptionId, boolean initiating)
|
private void sendMediaMessage(final boolean forceSms, final long expiresIn, final long revealDuration, final int subscriptionId, boolean initiating)
|
||||||
throws InvalidMessageException
|
throws InvalidMessageException
|
||||||
{
|
{
|
||||||
Log.i(TAG, "Sending media message...");
|
Log.i(TAG, "Sending media message...");
|
||||||
sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), Collections.emptyList(), linkPreviewViewModel.getActiveLinkPreviews(), expiresIn, subscriptionId, initiating, true);
|
sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), Collections.emptyList(), linkPreviewViewModel.getActiveLinkPreviews(), expiresIn, revealDuration, subscriptionId, initiating, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFuture<Void> sendMediaMessage(final boolean forceSms,
|
private ListenableFuture<Void> sendMediaMessage(final boolean forceSms,
|
||||||
@ -2159,6 +2164,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
List<Contact> contacts,
|
List<Contact> contacts,
|
||||||
List<LinkPreview> previews,
|
List<LinkPreview> previews,
|
||||||
final long expiresIn,
|
final long expiresIn,
|
||||||
|
final long revealDuration,
|
||||||
final int subscriptionId,
|
final int subscriptionId,
|
||||||
final boolean initiating,
|
final boolean initiating,
|
||||||
final boolean clearComposeBox)
|
final boolean clearComposeBox)
|
||||||
@ -2177,7 +2183,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, distributionType, quote, contacts, previews);
|
OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, revealDuration, distributionType, quote, contacts, previews);
|
||||||
|
|
||||||
final SettableFuture<Void> future = new SettableFuture<>();
|
final SettableFuture<Void> future = new SettableFuture<>();
|
||||||
final Context context = getApplicationContext();
|
final Context context = getApplicationContext();
|
||||||
@ -2378,7 +2384,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
SlideDeck slideDeck = new SlideDeck();
|
SlideDeck slideDeck = new SlideDeck();
|
||||||
slideDeck.addSlide(audioSlide);
|
slideDeck.addSlide(audioSlide);
|
||||||
|
|
||||||
sendMediaMessage(forceSms, "", slideDeck, inputPanel.getQuote().orNull(), Collections.emptyList(), Collections.emptyList(), expiresIn, subscriptionId, initiating, true).addListener(new AssertedSuccessListener<Void>() {
|
sendMediaMessage(forceSms, "", slideDeck, inputPanel.getQuote().orNull(), Collections.emptyList(), Collections.emptyList(), expiresIn, 0, subscriptionId, initiating, true).addListener(new AssertedSuccessListener<Void>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Void nothing) {
|
public void onSuccess(Void nothing) {
|
||||||
new AsyncTask<Void, Void, Void>() {
|
new AsyncTask<Void, Void, Void>() {
|
||||||
@ -2506,7 +2512,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
slideDeck.addSlide(stickerSlide);
|
slideDeck.addSlide(stickerSlide);
|
||||||
|
|
||||||
sendMediaMessage(transport.isSms(), "", slideDeck, null, Collections.emptyList(), Collections.emptyList(), expiresIn, subscriptionId, initiating, clearCompose);
|
sendMediaMessage(transport.isSms(), "", slideDeck, null, Collections.emptyList(), Collections.emptyList(), expiresIn, 0, subscriptionId, initiating, clearCompose);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2687,11 +2693,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
messageRecord.getBody(),
|
messageRecord.getBody(),
|
||||||
slideDeck);
|
slideDeck);
|
||||||
} else {
|
} else {
|
||||||
|
SlideDeck slideDeck = messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck();
|
||||||
|
|
||||||
|
if (messageRecord.isMms() && ((MmsMessageRecord) messageRecord).getRevealDuration() > 0 && slideDeck.getSlides().size() > 0) {
|
||||||
|
Attachment attachment = new TombstoneAttachment(slideDeck.getSlides().get(0).getContentType(), true);
|
||||||
|
slideDeck = new SlideDeck();
|
||||||
|
slideDeck.addSlide(MediaUtil.getSlideForAttachment(this, attachment));
|
||||||
|
}
|
||||||
|
|
||||||
inputPanel.setQuote(GlideApp.with(this),
|
inputPanel.setQuote(GlideApp.with(this),
|
||||||
messageRecord.getDateSent(),
|
messageRecord.getDateSent(),
|
||||||
author,
|
author,
|
||||||
messageRecord.getBody(),
|
messageRecord.getBody(),
|
||||||
messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck());
|
slideDeck);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,7 @@ import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHol
|
|||||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
|
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.MessagingDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
|
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
|
||||||
@ -78,6 +79,7 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
|||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.MultiDeviceRevealUpdateJob;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
|
import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
|
||||||
@ -88,6 +90,8 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
|
|||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.profiles.UnknownSenderView;
|
import org.thoughtcrime.securesms.profiles.UnknownSenderView;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealableMessageActivity;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealableUtil;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
@ -958,6 +962,38 @@ public class ConversationFragment extends Fragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRevealableMessageClicked(@NonNull MmsMessageRecord messageRecord) {
|
||||||
|
if (messageRecord.getRevealDuration() == 0) {
|
||||||
|
throw new AssertionError("Non-revealable message clicked.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageRecord.getRevealStartTime() == 0) {
|
||||||
|
SimpleTask.run(getLifecycle(), () -> {
|
||||||
|
if (!messageRecord.isOutgoing()) {
|
||||||
|
Log.i(TAG, "Marking revealable message as opened.");
|
||||||
|
|
||||||
|
DatabaseFactory.getMmsDatabase(requireContext()).markRevealStarted(messageRecord.getId());
|
||||||
|
|
||||||
|
ApplicationContext.getInstance(requireContext())
|
||||||
|
.getRevealableMessageManager()
|
||||||
|
.scheduleIfNecessary();
|
||||||
|
|
||||||
|
ApplicationContext.getInstance(requireContext())
|
||||||
|
.getJobManager()
|
||||||
|
.add(new MultiDeviceRevealUpdateJob(new MessagingDatabase.SyncMessageId(messageRecord.getIndividualRecipient().getAddress(), messageRecord.getDateSent())));
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Opening your own revealable message. It will automatically be marked as opened when it is sent.");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, (nothing) -> {
|
||||||
|
startActivity(RevealableMessageActivity.getIntent(requireContext(), messageRecord.getId()));
|
||||||
|
});
|
||||||
|
} else if (RevealableUtil.isViewable(messageRecord)) {
|
||||||
|
startActivity(RevealableMessageActivity.getIntent(requireContext(), messageRecord.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView) {
|
public void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView) {
|
||||||
if (getContext() != null && getActivity() != null) {
|
if (getContext() != null && getActivity() != null) {
|
||||||
|
@ -21,6 +21,7 @@ import android.content.ActivityNotFoundException;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
@ -65,7 +66,9 @@ import org.thoughtcrime.securesms.components.ConversationItemFooter;
|
|||||||
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
|
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
|
||||||
import org.thoughtcrime.securesms.components.DocumentView;
|
import org.thoughtcrime.securesms.components.DocumentView;
|
||||||
import org.thoughtcrime.securesms.components.LinkPreviewView;
|
import org.thoughtcrime.securesms.components.LinkPreviewView;
|
||||||
|
import org.thoughtcrime.securesms.components.Outliner;
|
||||||
import org.thoughtcrime.securesms.components.QuoteView;
|
import org.thoughtcrime.securesms.components.QuoteView;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealableMessageView;
|
||||||
import org.thoughtcrime.securesms.components.SharedContactView;
|
import org.thoughtcrime.securesms.components.SharedContactView;
|
||||||
import org.thoughtcrime.securesms.components.StickerView;
|
import org.thoughtcrime.securesms.components.StickerView;
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||||
@ -96,6 +99,7 @@ import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
|||||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealableUtil;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
@ -151,6 +155,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
private ViewGroup container;
|
private ViewGroup container;
|
||||||
|
|
||||||
private @NonNull Set<MessageRecord> batchSelected = new HashSet<>();
|
private @NonNull Set<MessageRecord> batchSelected = new HashSet<>();
|
||||||
|
private @NonNull Outliner outliner = new Outliner();
|
||||||
private Recipient conversationRecipient;
|
private Recipient conversationRecipient;
|
||||||
private Stub<ConversationItemThumbnail> mediaThumbnailStub;
|
private Stub<ConversationItemThumbnail> mediaThumbnailStub;
|
||||||
private Stub<AudioView> audioViewStub;
|
private Stub<AudioView> audioViewStub;
|
||||||
@ -158,6 +163,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
private Stub<SharedContactView> sharedContactStub;
|
private Stub<SharedContactView> sharedContactStub;
|
||||||
private Stub<LinkPreviewView> linkPreviewStub;
|
private Stub<LinkPreviewView> linkPreviewStub;
|
||||||
private Stub<StickerView> stickerStub;
|
private Stub<StickerView> stickerStub;
|
||||||
|
private Stub<RevealableMessageView> revealableStub;
|
||||||
private @Nullable EventListener eventListener;
|
private @Nullable EventListener eventListener;
|
||||||
|
|
||||||
private int defaultBubbleColor;
|
private int defaultBubbleColor;
|
||||||
@ -169,6 +175,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
private final SharedContactEventListener sharedContactEventListener = new SharedContactEventListener();
|
private final SharedContactEventListener sharedContactEventListener = new SharedContactEventListener();
|
||||||
private final SharedContactClickListener sharedContactClickListener = new SharedContactClickListener();
|
private final SharedContactClickListener sharedContactClickListener = new SharedContactClickListener();
|
||||||
private final LinkPreviewClickListener linkPreviewClickListener = new LinkPreviewClickListener();
|
private final LinkPreviewClickListener linkPreviewClickListener = new LinkPreviewClickListener();
|
||||||
|
private final RevealableMessageClickListener revealableClickListener = new RevealableMessageClickListener();
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
@ -207,6 +214,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
this.sharedContactStub = new Stub<>(findViewById(R.id.shared_contact_view_stub));
|
this.sharedContactStub = new Stub<>(findViewById(R.id.shared_contact_view_stub));
|
||||||
this.linkPreviewStub = new Stub<>(findViewById(R.id.link_preview_stub));
|
this.linkPreviewStub = new Stub<>(findViewById(R.id.link_preview_stub));
|
||||||
this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub));
|
this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub));
|
||||||
|
this.revealableStub = new Stub<>(findViewById(R.id.revealable_view_stub));
|
||||||
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
||||||
this.quoteView = findViewById(R.id.quote_view);
|
this.quoteView = findViewById(R.id.quote_view);
|
||||||
this.container = findViewById(R.id.container);
|
this.container = findViewById(R.id.container);
|
||||||
@ -302,11 +310,21 @@ public class ConversationItem extends LinearLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
|
super.dispatchDraw(canvas);
|
||||||
|
|
||||||
|
if (!messageRecord.isOutgoing() && hasRevealableMessage(messageRecord) && RevealableUtil.isRevealExpired((MmsMessageRecord) messageRecord)) {
|
||||||
|
outliner.setColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_text_secondary_color));
|
||||||
|
outliner.draw(canvas, bodyBubble.getTop() + getPaddingTop(), bodyBubble.getRight(), bodyBubble.getBottom() + getPaddingTop(), bodyBubble.getLeft());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int getAvailableMessageBubbleWidth(@NonNull View forView) {
|
private int getAvailableMessageBubbleWidth(@NonNull View forView) {
|
||||||
int availableWidth;
|
int availableWidth;
|
||||||
if (hasAudio(messageRecord)) {
|
if (hasAudio(messageRecord)) {
|
||||||
availableWidth = audioViewStub.get().getMeasuredWidth() + ViewUtil.getLeftMargin(audioViewStub.get()) + ViewUtil.getRightMargin(audioViewStub.get());
|
availableWidth = audioViewStub.get().getMeasuredWidth() + ViewUtil.getLeftMargin(audioViewStub.get()) + ViewUtil.getRightMargin(audioViewStub.get());
|
||||||
} else if (hasThumbnail(messageRecord) || hasBigImageLinkPreview(messageRecord)) {
|
} else if (!hasRevealableMessage(messageRecord) && (hasThumbnail(messageRecord) || hasBigImageLinkPreview(messageRecord))) {
|
||||||
availableWidth = mediaThumbnailStub.get().getMeasuredWidth();
|
availableWidth = mediaThumbnailStub.get().getMeasuredWidth();
|
||||||
} else {
|
} else {
|
||||||
availableWidth = bodyBubble.getMeasuredWidth() - bodyBubble.getPaddingLeft() - bodyBubble.getPaddingRight();
|
availableWidth = bodyBubble.getMeasuredWidth() - bodyBubble.getPaddingLeft() - bodyBubble.getPaddingRight();
|
||||||
@ -341,8 +359,16 @@ public class ConversationItem extends LinearLayout
|
|||||||
private void setBubbleState(MessageRecord messageRecord) {
|
private void setBubbleState(MessageRecord messageRecord) {
|
||||||
if (messageRecord.isOutgoing()) {
|
if (messageRecord.isOutgoing()) {
|
||||||
bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY);
|
bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||||
|
footer.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_text_secondary_color));
|
||||||
|
footer.setIconColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_icon_color));
|
||||||
|
} else if (hasRevealableMessage(messageRecord) && RevealableUtil.isRevealExpired((MmsMessageRecord) messageRecord)) {
|
||||||
|
bodyBubble.getBackground().setColorFilter(ThemeUtil.getThemedColor(context, R.attr.conversation_item_reveal_viewed_background_color), PorterDuff.Mode.MULTIPLY);
|
||||||
|
footer.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_text_secondary_color));
|
||||||
|
footer.setIconColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_icon_color));
|
||||||
} else {
|
} else {
|
||||||
bodyBubble.getBackground().setColorFilter(messageRecord.getRecipient().getColor().toConversationColor(context), PorterDuff.Mode.MULTIPLY);
|
bodyBubble.getBackground().setColorFilter(messageRecord.getRecipient().getColor().toConversationColor(context), PorterDuff.Mode.MULTIPLY);
|
||||||
|
footer.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_secondary_color));
|
||||||
|
footer.setIconColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_secondary_color));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioViewStub.resolved()) {
|
if (audioViewStub.resolved()) {
|
||||||
@ -413,7 +439,8 @@ public class ConversationItem extends LinearLayout
|
|||||||
!hasAudio(messageRecord) &&
|
!hasAudio(messageRecord) &&
|
||||||
!hasDocument(messageRecord) &&
|
!hasDocument(messageRecord) &&
|
||||||
!hasSharedContact(messageRecord) &&
|
!hasSharedContact(messageRecord) &&
|
||||||
!hasSticker(messageRecord);
|
!hasSticker(messageRecord) &&
|
||||||
|
!hasRevealableMessage(messageRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasDocument(MessageRecord messageRecord) {
|
private boolean hasDocument(MessageRecord messageRecord) {
|
||||||
@ -450,6 +477,10 @@ public class ConversationItem extends LinearLayout
|
|||||||
!StickerUrl.isValidShareLink(linkPreview.getUrl());
|
!StickerUrl.isValidShareLink(linkPreview.getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasRevealableMessage(MessageRecord messageRecord) {
|
||||||
|
return messageRecord.isMms() && ((MmsMessageRecord) messageRecord).getRevealDuration() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery) {
|
private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery) {
|
||||||
bodyText.setClickable(false);
|
bodyText.setClickable(false);
|
||||||
bodyText.setFocusable(false);
|
bodyText.setFocusable(false);
|
||||||
@ -481,13 +512,28 @@ public class ConversationItem extends LinearLayout
|
|||||||
{
|
{
|
||||||
boolean showControls = !messageRecord.isFailed();
|
boolean showControls = !messageRecord.isFailed();
|
||||||
|
|
||||||
if (hasSharedContact(messageRecord)) {
|
if (hasRevealableMessage(messageRecord)) {
|
||||||
|
revealableStub.get().setVisibility(VISIBLE);
|
||||||
|
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||||
|
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||||
|
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
|
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||||
|
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
||||||
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
|
revealableStub.get().setMessage((MmsMessageRecord) messageRecord);
|
||||||
|
revealableStub.get().setOnClickListener(revealableClickListener);
|
||||||
|
revealableStub.get().setOnLongClickListener(passthroughClickListener);
|
||||||
|
|
||||||
|
footer.setVisibility(VISIBLE);
|
||||||
|
} else if (hasSharedContact(messageRecord)) {
|
||||||
sharedContactStub.get().setVisibility(VISIBLE);
|
sharedContactStub.get().setVisibility(VISIBLE);
|
||||||
if (audioViewStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
if (audioViewStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
sharedContactStub.get().setContact(((MediaMmsMessageRecord) messageRecord).getSharedContacts().get(0), glideRequests, locale);
|
sharedContactStub.get().setContact(((MediaMmsMessageRecord) messageRecord).getSharedContacts().get(0), glideRequests, locale);
|
||||||
sharedContactStub.get().setEventListener(sharedContactEventListener);
|
sharedContactStub.get().setEventListener(sharedContactEventListener);
|
||||||
@ -506,6 +552,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0);
|
LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0);
|
||||||
@ -544,6 +591,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||||
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
audioViewStub.get().setAudio(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls);
|
audioViewStub.get().setAudio(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls);
|
||||||
@ -561,6 +609,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||||
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
documentViewStub.get().setDocument(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getDocumentSlide(), showControls);
|
documentViewStub.get().setDocument(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getDocumentSlide(), showControls);
|
||||||
@ -581,6 +630,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||||
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
||||||
|
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
stickerStub.get().setSticker(glideRequests, ((MmsMessageRecord) messageRecord).getSlideDeck().getStickerSlide());
|
stickerStub.get().setSticker(glideRequests, ((MmsMessageRecord) messageRecord).getSlideDeck().getStickerSlide());
|
||||||
@ -600,6 +650,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||||
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
List<Slide> thumbnailSlides = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlides();
|
List<Slide> thumbnailSlides = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlides();
|
||||||
@ -629,6 +680,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||||
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
@ -876,7 +928,10 @@ public class ConversationItem extends LinearLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setGroupAuthorColor(@NonNull MessageRecord messageRecord) {
|
private void setGroupAuthorColor(@NonNull MessageRecord messageRecord) {
|
||||||
if (hasSticker(messageRecord)) {
|
if (!messageRecord.isOutgoing() && hasRevealableMessage(messageRecord) && RevealableUtil.isRevealExpired((MmsMessageRecord) messageRecord)) {
|
||||||
|
groupSender.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color));
|
||||||
|
groupSenderProfileName.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color));
|
||||||
|
} else if (hasSticker(messageRecord)) {
|
||||||
groupSender.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color));
|
groupSender.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color));
|
||||||
groupSenderProfileName.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color));
|
groupSenderProfileName.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color));
|
||||||
} else {
|
} else {
|
||||||
@ -912,19 +967,43 @@ public class ConversationItem extends LinearLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setMessageShape(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
private void setMessageShape(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||||
|
int bigRadius = readDimen(R.dimen.message_corner_radius);
|
||||||
|
int smallRadius = readDimen(R.dimen.message_corner_collapse_radius);
|
||||||
|
|
||||||
int background;
|
int background;
|
||||||
|
|
||||||
if (isSingularMessage(current, previous, next, isGroupThread)) {
|
if (isSingularMessage(current, previous, next, isGroupThread)) {
|
||||||
background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_alone
|
if (current.isOutgoing()) {
|
||||||
: R.drawable.message_bubble_background_received_alone;
|
background = R.drawable.message_bubble_background_sent_alone;
|
||||||
|
outliner.setRadius(bigRadius);
|
||||||
|
} else {
|
||||||
|
background = R.drawable.message_bubble_background_received_alone;
|
||||||
|
outliner.setRadius(bigRadius);
|
||||||
|
}
|
||||||
} else if (isStartOfMessageCluster(current, previous, isGroupThread)) {
|
} else if (isStartOfMessageCluster(current, previous, isGroupThread)) {
|
||||||
background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_start
|
if (current.isOutgoing()) {
|
||||||
: R.drawable.message_bubble_background_received_start;
|
background = R.drawable.message_bubble_background_sent_start;
|
||||||
|
outliner.setRadii(bigRadius, bigRadius, smallRadius, bigRadius);
|
||||||
|
} else {
|
||||||
|
background = R.drawable.message_bubble_background_received_start;
|
||||||
|
outliner.setRadii(bigRadius, bigRadius, bigRadius, smallRadius);
|
||||||
|
}
|
||||||
} else if (isEndOfMessageCluster(current, next, isGroupThread)) {
|
} else if (isEndOfMessageCluster(current, next, isGroupThread)) {
|
||||||
background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_end
|
if (current.isOutgoing()) {
|
||||||
: R.drawable.message_bubble_background_received_end;
|
background = R.drawable.message_bubble_background_sent_end;
|
||||||
|
outliner.setRadii(bigRadius, smallRadius, bigRadius, bigRadius);
|
||||||
|
} else {
|
||||||
|
background = R.drawable.message_bubble_background_received_end;
|
||||||
|
outliner.setRadii(smallRadius, bigRadius, bigRadius, bigRadius);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_middle
|
if (current.isOutgoing()) {
|
||||||
: R.drawable.message_bubble_background_received_middle;
|
background = R.drawable.message_bubble_background_sent_middle;
|
||||||
|
outliner.setRadii(bigRadius, smallRadius, smallRadius, bigRadius);
|
||||||
|
} else {
|
||||||
|
background = R.drawable.message_bubble_background_received_middle;
|
||||||
|
outliner.setRadii(smallRadius, bigRadius, bigRadius, smallRadius);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyBubble.setBackgroundResource(background);
|
bodyBubble.setBackgroundResource(background);
|
||||||
@ -1090,6 +1169,21 @@ public class ConversationItem extends LinearLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class RevealableMessageClickListener implements View.OnClickListener {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
RevealableMessageView revealView = (RevealableMessageView) view;
|
||||||
|
|
||||||
|
if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && RevealableUtil.isViewable((MmsMessageRecord) messageRecord)) {
|
||||||
|
eventListener.onRevealableMessageClicked((MmsMessageRecord) messageRecord);
|
||||||
|
} else if (batchSelected.isEmpty() && messageRecord.isMms() && revealView.requiresTapToDownload((MmsMessageRecord) messageRecord)) {
|
||||||
|
singleDownloadClickListener.onClick(view, ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlide());
|
||||||
|
} else {
|
||||||
|
passthroughClickListener.onClick(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class LinkPreviewThumbnailClickListener implements SlideClickListener {
|
private class LinkPreviewThumbnailClickListener implements SlideClickListener {
|
||||||
public void onClick(final View v, final Slide slide) {
|
public void onClick(final View v, final Slide slide) {
|
||||||
if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) {
|
if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) {
|
||||||
|
@ -245,6 +245,15 @@ public class AttachmentDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasAttachmentFilesForMessage(long mmsId) {
|
||||||
|
String selection = MMS_ID + " = ? AND (" + DATA + " NOT NULL OR " + TRANSFER_STATE + " != ?)";
|
||||||
|
String[] args = new String[] { String.valueOf(mmsId), String.valueOf(TRANSFER_PROGRESS_DONE) };
|
||||||
|
|
||||||
|
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, "1")) {
|
||||||
|
return cursor != null && cursor.moveToFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public @NonNull List<DatabaseAttachment> getPendingAttachments() {
|
public @NonNull List<DatabaseAttachment> getPendingAttachments() {
|
||||||
final SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
final SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||||
final List<DatabaseAttachment> attachments = new LinkedList<>();
|
final List<DatabaseAttachment> attachments = new LinkedList<>();
|
||||||
@ -263,7 +272,7 @@ public class AttachmentDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
void deleteAttachmentsForMessage(long mmsId) {
|
public void deleteAttachmentsForMessage(long mmsId) {
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
|
|
||||||
@ -283,6 +292,44 @@ public class AttachmentDatabase extends Database {
|
|||||||
notifyAttachmentListeners();
|
notifyAttachmentListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteAttachmentFilesForMessage(long mmsId) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
|
Cursor cursor = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE}, MMS_ID + " = ?",
|
||||||
|
new String[] {mmsId+""}, null, null, null);
|
||||||
|
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
deleteAttachmentOnDisk(cursor.getString(0), cursor.getString(1), cursor.getString(2));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null)
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(DATA, (String) null);
|
||||||
|
values.put(DATA_RANDOM, (byte[]) null);
|
||||||
|
values.put(THUMBNAIL, (String) null);
|
||||||
|
values.put(THUMBNAIL_RANDOM, (byte[]) null);
|
||||||
|
values.put(FILE_NAME, (String) null);
|
||||||
|
values.put(CAPTION, (String) null);
|
||||||
|
values.put(SIZE, 0);
|
||||||
|
values.put(WIDTH, 0);
|
||||||
|
values.put(HEIGHT, 0);
|
||||||
|
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
|
||||||
|
|
||||||
|
database.update(TABLE_NAME, values, MMS_ID + " = ?", new String[] {mmsId + ""});
|
||||||
|
notifyAttachmentListeners();
|
||||||
|
|
||||||
|
long threadId = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId);
|
||||||
|
if (threadId > 0) {
|
||||||
|
notifyConversationListeners(threadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void deleteAttachment(@NonNull AttachmentId id) {
|
public void deleteAttachment(@NonNull AttachmentId id) {
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ public class MediaDatabase extends Database {
|
|||||||
+ "WHERE " + AttachmentDatabase.MMS_ID + " IN (SELECT " + MmsSmsColumns.ID
|
+ "WHERE " + AttachmentDatabase.MMS_ID + " IN (SELECT " + MmsSmsColumns.ID
|
||||||
+ " FROM " + MmsDatabase.TABLE_NAME
|
+ " FROM " + MmsDatabase.TABLE_NAME
|
||||||
+ " WHERE " + MmsDatabase.THREAD_ID + " = ?) AND (%s) AND "
|
+ " WHERE " + MmsDatabase.THREAD_ID + " = ?) AND (%s) AND "
|
||||||
|
+ MmsDatabase.REVEAL_DURATION + " = 0 AND "
|
||||||
+ AttachmentDatabase.DATA + " IS NOT NULL AND "
|
+ AttachmentDatabase.DATA + " IS NOT NULL AND "
|
||||||
+ AttachmentDatabase.QUOTE + " = 0 AND "
|
+ AttachmentDatabase.QUOTE + " = 0 AND "
|
||||||
+ AttachmentDatabase.STICKER_PACK_ID + " IS NULL "
|
+ AttachmentDatabase.STICKER_PACK_ID + " IS NULL "
|
||||||
|
@ -5,15 +5,19 @@ import android.content.Context;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import net.sqlcipher.database.SQLiteDatabase;
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.documents.Document;
|
import org.thoughtcrime.securesms.database.documents.Document;
|
||||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
|
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -63,6 +63,8 @@ import org.thoughtcrime.securesms.mms.QuoteModel;
|
|||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealExpirationInfo;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealableUtil;
|
||||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
@ -107,6 +109,9 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
static final String SHARED_CONTACTS = "shared_contacts";
|
static final String SHARED_CONTACTS = "shared_contacts";
|
||||||
static final String LINK_PREVIEWS = "previews";
|
static final String LINK_PREVIEWS = "previews";
|
||||||
|
|
||||||
|
public static final String REVEAL_DURATION = "reveal_duration";
|
||||||
|
public static final String REVEAL_START_TIME = "reveal_start_time";
|
||||||
|
|
||||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||||
THREAD_ID + " INTEGER, " + DATE_SENT + " INTEGER, " + DATE_RECEIVED + " INTEGER, " + MESSAGE_BOX + " INTEGER, " +
|
THREAD_ID + " INTEGER, " + DATE_SENT + " INTEGER, " + DATE_RECEIVED + " INTEGER, " + MESSAGE_BOX + " INTEGER, " +
|
||||||
READ + " INTEGER DEFAULT 0, " + "m_id" + " TEXT, " + "sub" + " TEXT, " +
|
READ + " INTEGER DEFAULT 0, " + "m_id" + " TEXT, " + "sub" + " TEXT, " +
|
||||||
@ -126,7 +131,7 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + QUOTE_ID + " INTEGER DEFAULT 0, " +
|
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + QUOTE_ID + " INTEGER DEFAULT 0, " +
|
||||||
QUOTE_AUTHOR + " TEXT, " + QUOTE_BODY + " TEXT, " + QUOTE_ATTACHMENT + " INTEGER DEFAULT -1, " +
|
QUOTE_AUTHOR + " TEXT, " + QUOTE_BODY + " TEXT, " + QUOTE_ATTACHMENT + " INTEGER DEFAULT -1, " +
|
||||||
QUOTE_MISSING + " INTEGER DEFAULT 0, " + SHARED_CONTACTS + " TEXT, " + UNIDENTIFIED + " INTEGER DEFAULT 0, " +
|
QUOTE_MISSING + " INTEGER DEFAULT 0, " + SHARED_CONTACTS + " TEXT, " + UNIDENTIFIED + " INTEGER DEFAULT 0, " +
|
||||||
LINK_PREVIEWS + " TEXT);";
|
LINK_PREVIEWS + " TEXT, " + REVEAL_DURATION + " INTEGER DEFAULT 0, " + REVEAL_START_TIME + " INTEGER DEFAULT 0);";
|
||||||
|
|
||||||
public static final String[] CREATE_INDEXS = {
|
public static final String[] CREATE_INDEXS = {
|
||||||
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||||
@ -147,7 +152,7 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
||||||
DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID,
|
DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID,
|
||||||
EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING,
|
EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING,
|
||||||
SHARED_CONTACTS, LINK_PREVIEWS, UNIDENTIFIED,
|
SHARED_CONTACTS, LINK_PREVIEWS, UNIDENTIFIED, REVEAL_DURATION, REVEAL_START_TIME,
|
||||||
"json_group_array(json_object(" +
|
"json_group_array(json_object(" +
|
||||||
"'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " +
|
"'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " +
|
||||||
"'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " +
|
"'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " +
|
||||||
@ -432,6 +437,60 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
notifyConversationListeners(threadId);
|
notifyConversationListeners(threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void markRevealStarted(long messageId) {
|
||||||
|
markRevealStarted(messageId, System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markRevealStarted(long messageId, long startTime) {
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
contentValues.put(REVEAL_START_TIME, startTime);
|
||||||
|
|
||||||
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
|
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
|
||||||
|
|
||||||
|
long threadId = getThreadIdForMessage(messageId);
|
||||||
|
notifyConversationListeners(threadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RevealExpirationInfo> markRevealStarted(@NonNull SyncMessageId messageId, long proposedStartTime) {
|
||||||
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
|
List<RevealExpirationInfo> expirationInfos = new LinkedList<>();
|
||||||
|
|
||||||
|
String[] projection = new String[] { ID, ADDRESS, THREAD_ID, DATE_SENT, DATE_RECEIVED, REVEAL_DURATION, REVEAL_START_TIME };
|
||||||
|
String selection = DATE_SENT + " = ?";
|
||||||
|
String[] args = new String[] { String.valueOf(messageId.getTimetamp()) };
|
||||||
|
|
||||||
|
try (Cursor cursor = db.query(TABLE_NAME, projection, selection, args, null, null, null)) {
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
Address theirAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
|
||||||
|
Address ourAddress = messageId.getAddress();
|
||||||
|
|
||||||
|
if (ourAddress.equals(theirAddress) || theirAddress.isGroup()) {
|
||||||
|
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||||
|
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
||||||
|
long receiveTime = cursor.getLong(cursor.getColumnIndexOrThrow(DATE_RECEIVED));
|
||||||
|
long revealDuration = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_DURATION));
|
||||||
|
long revealStartTime = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_START_TIME));
|
||||||
|
|
||||||
|
revealStartTime = revealStartTime > 0 ? Math.min(proposedStartTime, revealStartTime) : proposedStartTime;
|
||||||
|
revealStartTime = Math.min(revealStartTime, System.currentTimeMillis());
|
||||||
|
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
|
||||||
|
values.put(REVEAL_START_TIME, revealStartTime);
|
||||||
|
expirationInfos.add(new RevealExpirationInfo(id, receiveTime, revealStartTime, revealDuration));
|
||||||
|
|
||||||
|
db.update(TABLE_NAME, values, ID_WHERE, new String[] { String.valueOf(id) });
|
||||||
|
|
||||||
|
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
|
||||||
|
notifyConversationListeners(threadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expirationInfos;
|
||||||
|
}
|
||||||
|
|
||||||
public void markAsNotified(long id) {
|
public void markAsNotified(long id) {
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
@ -609,6 +668,7 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
|
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
|
||||||
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID));
|
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID));
|
||||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
|
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
|
||||||
|
long revealDuration = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_DURATION));
|
||||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
|
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
|
||||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
||||||
int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId);
|
int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId);
|
||||||
@ -655,12 +715,12 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
|
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
|
||||||
return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, quote, contacts, previews);
|
return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, 0, quote, contacts, previews);
|
||||||
} else if (Types.isExpirationTimerUpdate(outboxType)) {
|
} else if (Types.isExpirationTimerUpdate(outboxType)) {
|
||||||
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
|
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts, previews, networkFailures, mismatches);
|
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, revealDuration, distributionType, quote, contacts, previews, networkFailures, mismatches);
|
||||||
|
|
||||||
if (Types.isSecureType(outboxType)) {
|
if (Types.isSecureType(outboxType)) {
|
||||||
return new OutgoingSecureMediaMessage(message);
|
return new OutgoingSecureMediaMessage(message);
|
||||||
@ -764,6 +824,7 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
contentValues.put(READ, 1);
|
contentValues.put(READ, 1);
|
||||||
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
|
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
|
||||||
contentValues.put(EXPIRES_IN, request.getExpiresIn());
|
contentValues.put(EXPIRES_IN, request.getExpiresIn());
|
||||||
|
contentValues.put(REVEAL_DURATION, request.getRevealDuration());
|
||||||
|
|
||||||
List<Attachment> attachments = new LinkedList<>();
|
List<Attachment> attachments = new LinkedList<>();
|
||||||
|
|
||||||
@ -831,6 +892,7 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
contentValues.put(PART_COUNT, retrieved.getAttachments().size());
|
contentValues.put(PART_COUNT, retrieved.getAttachments().size());
|
||||||
contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId());
|
contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId());
|
||||||
contentValues.put(EXPIRES_IN, retrieved.getExpiresIn());
|
contentValues.put(EXPIRES_IN, retrieved.getExpiresIn());
|
||||||
|
contentValues.put(REVEAL_DURATION, retrieved.getRevealDuration());
|
||||||
contentValues.put(READ, retrieved.isExpirationUpdate() ? 1 : 0);
|
contentValues.put(READ, retrieved.isExpirationUpdate() ? 1 : 0);
|
||||||
contentValues.put(UNIDENTIFIED, retrieved.isUnidentified());
|
contentValues.put(UNIDENTIFIED, retrieved.isUnidentified());
|
||||||
|
|
||||||
@ -986,6 +1048,7 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
|
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
|
||||||
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
|
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
|
||||||
contentValues.put(EXPIRES_IN, message.getExpiresIn());
|
contentValues.put(EXPIRES_IN, message.getExpiresIn());
|
||||||
|
contentValues.put(REVEAL_DURATION, message.getRevealDuration());
|
||||||
contentValues.put(ADDRESS, message.getRecipient().getAddress().serialize());
|
contentValues.put(ADDRESS, message.getRecipient().getAddress().serialize());
|
||||||
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
|
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
|
||||||
contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum());
|
contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum());
|
||||||
@ -1244,6 +1307,42 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
database.delete(TABLE_NAME, null, null);
|
database.delete(TABLE_NAME, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable RevealExpirationInfo getNearestExpiringRevealableMessage() {
|
||||||
|
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||||
|
RevealExpirationInfo info = null;
|
||||||
|
long nearestExpiration = Long.MAX_VALUE;
|
||||||
|
|
||||||
|
String query = "SELECT " +
|
||||||
|
TABLE_NAME + "." + ID + ", " +
|
||||||
|
REVEAL_DURATION + ", " +
|
||||||
|
REVEAL_START_TIME + ", " +
|
||||||
|
DATE_RECEIVED + " " +
|
||||||
|
"FROM " + TABLE_NAME + " INNER JOIN " + AttachmentDatabase.TABLE_NAME + " " +
|
||||||
|
"ON " + TABLE_NAME + "." + ID + " = " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " " +
|
||||||
|
"WHERE " +
|
||||||
|
REVEAL_DURATION + " > 0 AND " +
|
||||||
|
"(" + AttachmentDatabase.DATA + " NOT NULL OR " + AttachmentDatabase.TRANSFER_STATE + " != ?)";
|
||||||
|
String[] args = new String[] { String.valueOf(AttachmentDatabase.TRANSFER_PROGRESS_DONE) };
|
||||||
|
|
||||||
|
try (Cursor cursor = db.rawQuery(query, args)) {
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||||
|
long revealDuration = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_DURATION));
|
||||||
|
long revealStartTime = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_START_TIME));
|
||||||
|
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(DATE_RECEIVED));
|
||||||
|
long expiresAt = revealStartTime > 0 ? revealStartTime + revealDuration
|
||||||
|
: dateReceived + RevealableUtil.MAX_LIFESPAN;
|
||||||
|
|
||||||
|
if (info == null || expiresAt < nearestExpiration) {
|
||||||
|
info = new RevealExpirationInfo(id, dateReceived, revealStartTime, revealDuration);
|
||||||
|
nearestExpiration = expiresAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
public Cursor getCarrierMmsInformation(String apn) {
|
public Cursor getCarrierMmsInformation(String apn) {
|
||||||
Uri uri = Uri.withAppendedPath(Uri.parse("content://telephony/carriers"), "current");
|
Uri uri = Uri.withAppendedPath(Uri.parse("content://telephony/carriers"), "current");
|
||||||
String selection = TextUtils.isEmpty(apn) ? null : "apn = ?";
|
String selection = TextUtils.isEmpty(apn) ? null : "apn = ?";
|
||||||
@ -1342,7 +1441,10 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
new LinkedList<NetworkFailure>(),
|
new LinkedList<NetworkFailure>(),
|
||||||
message.getSubscriptionId(),
|
message.getSubscriptionId(),
|
||||||
message.getExpiresIn(),
|
message.getExpiresIn(),
|
||||||
System.currentTimeMillis(), 0,
|
System.currentTimeMillis(),
|
||||||
|
message.getRevealDuration(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
message.getOutgoingQuote() != null ?
|
message.getOutgoingQuote() != null ?
|
||||||
new Quote(message.getOutgoingQuote().getId(),
|
new Quote(message.getOutgoingQuote().getId(),
|
||||||
message.getOutgoingQuote().getAuthor(),
|
message.getOutgoingQuote().getAuthor(),
|
||||||
@ -1439,6 +1541,8 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN));
|
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN));
|
||||||
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED));
|
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED));
|
||||||
boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.UNIDENTIFIED)) == 1;
|
boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.UNIDENTIFIED)) == 1;
|
||||||
|
long revealDuration = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.REVEAL_DURATION));
|
||||||
|
long revealStartTime = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.REVEAL_START_TIME));
|
||||||
|
|
||||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||||
readReceiptCount = 0;
|
readReceiptCount = 0;
|
||||||
@ -1459,6 +1563,7 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
addressDeviceId, dateSent, dateReceived, deliveryReceiptCount,
|
addressDeviceId, dateSent, dateReceived, deliveryReceiptCount,
|
||||||
threadId, body, slideDeck, partCount, box, mismatches,
|
threadId, body, slideDeck, partCount, box, mismatches,
|
||||||
networkFailures, subscriptionId, expiresIn, expireStarted,
|
networkFailures, subscriptionId, expiresIn, expireStarted,
|
||||||
|
revealDuration, revealStartTime,
|
||||||
readReceiptCount, quote, contacts, previews, unidentified);
|
readReceiptCount, quote, contacts, previews, unidentified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ public interface MmsSmsColumns {
|
|||||||
protected static final long MISSED_CALL_TYPE = 3;
|
protected static final long MISSED_CALL_TYPE = 3;
|
||||||
protected static final long JOINED_TYPE = 4;
|
protected static final long JOINED_TYPE = 4;
|
||||||
protected static final long UNSUPPORTED_MESSAGE_TYPE = 5;
|
protected static final long UNSUPPORTED_MESSAGE_TYPE = 5;
|
||||||
|
protected static final long INVALID_MESSAGE_TYPE = 6;
|
||||||
|
|
||||||
protected static final long BASE_INBOX_TYPE = 20;
|
protected static final long BASE_INBOX_TYPE = 20;
|
||||||
protected static final long BASE_OUTBOX_TYPE = 21;
|
protected static final long BASE_OUTBOX_TYPE = 21;
|
||||||
@ -147,6 +148,10 @@ public interface MmsSmsColumns {
|
|||||||
return (type & BASE_TYPE_MASK) == UNSUPPORTED_MESSAGE_TYPE;
|
return (type & BASE_TYPE_MASK) == UNSUPPORTED_MESSAGE_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isInvalidMessageType(long type) {
|
||||||
|
return (type & BASE_TYPE_MASK) == INVALID_MESSAGE_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isSecureType(long type) {
|
public static boolean isSecureType(long type) {
|
||||||
return (type & SECURE_MESSAGE_BIT) != 0;
|
return (type & SECURE_MESSAGE_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,9 @@ public class MmsSmsDatabase extends Database {
|
|||||||
MmsDatabase.QUOTE_MISSING,
|
MmsDatabase.QUOTE_MISSING,
|
||||||
MmsDatabase.QUOTE_ATTACHMENT,
|
MmsDatabase.QUOTE_ATTACHMENT,
|
||||||
MmsDatabase.SHARED_CONTACTS,
|
MmsDatabase.SHARED_CONTACTS,
|
||||||
MmsDatabase.LINK_PREVIEWS};
|
MmsDatabase.LINK_PREVIEWS,
|
||||||
|
MmsDatabase.REVEAL_DURATION,
|
||||||
|
MmsDatabase.REVEAL_START_TIME};
|
||||||
|
|
||||||
public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||||
super(context, databaseHelper);
|
super(context, databaseHelper);
|
||||||
@ -270,7 +272,9 @@ public class MmsSmsDatabase extends Database {
|
|||||||
MmsDatabase.QUOTE_MISSING,
|
MmsDatabase.QUOTE_MISSING,
|
||||||
MmsDatabase.QUOTE_ATTACHMENT,
|
MmsDatabase.QUOTE_ATTACHMENT,
|
||||||
MmsDatabase.SHARED_CONTACTS,
|
MmsDatabase.SHARED_CONTACTS,
|
||||||
MmsDatabase.LINK_PREVIEWS};
|
MmsDatabase.LINK_PREVIEWS,
|
||||||
|
MmsDatabase.REVEAL_DURATION,
|
||||||
|
MmsDatabase.REVEAL_START_TIME};
|
||||||
|
|
||||||
String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||||
SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||||
@ -296,7 +300,9 @@ public class MmsSmsDatabase extends Database {
|
|||||||
MmsDatabase.QUOTE_MISSING,
|
MmsDatabase.QUOTE_MISSING,
|
||||||
MmsDatabase.QUOTE_ATTACHMENT,
|
MmsDatabase.QUOTE_ATTACHMENT,
|
||||||
MmsDatabase.SHARED_CONTACTS,
|
MmsDatabase.SHARED_CONTACTS,
|
||||||
MmsDatabase.LINK_PREVIEWS};
|
MmsDatabase.LINK_PREVIEWS,
|
||||||
|
MmsDatabase.REVEAL_DURATION,
|
||||||
|
MmsDatabase.REVEAL_START_TIME};
|
||||||
|
|
||||||
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
||||||
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
|
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
|
||||||
@ -367,6 +373,8 @@ public class MmsSmsDatabase extends Database {
|
|||||||
mmsColumnsPresent.add(MmsDatabase.QUOTE_ATTACHMENT);
|
mmsColumnsPresent.add(MmsDatabase.QUOTE_ATTACHMENT);
|
||||||
mmsColumnsPresent.add(MmsDatabase.SHARED_CONTACTS);
|
mmsColumnsPresent.add(MmsDatabase.SHARED_CONTACTS);
|
||||||
mmsColumnsPresent.add(MmsDatabase.LINK_PREVIEWS);
|
mmsColumnsPresent.add(MmsDatabase.LINK_PREVIEWS);
|
||||||
|
mmsColumnsPresent.add(MmsDatabase.REVEAL_DURATION);
|
||||||
|
mmsColumnsPresent.add(MmsDatabase.REVEAL_START_TIME);
|
||||||
|
|
||||||
Set<String> smsColumnsPresent = new HashSet<>();
|
Set<String> smsColumnsPresent = new HashSet<>();
|
||||||
smsColumnsPresent.add(MmsSmsColumns.ID);
|
smsColumnsPresent.add(MmsSmsColumns.ID);
|
||||||
|
@ -221,6 +221,10 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.UNSUPPORTED_MESSAGE_TYPE);
|
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.UNSUPPORTED_MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void markAsInvalidMessage(long id) {
|
||||||
|
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.INVALID_MESSAGE_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
public void markAsLegacyVersion(long id) {
|
public void markAsLegacyVersion(long id) {
|
||||||
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT);
|
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT);
|
||||||
}
|
}
|
||||||
@ -883,7 +887,8 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
addressDeviceId,
|
addressDeviceId,
|
||||||
dateSent, dateReceived, deliveryReceiptCount, type,
|
dateSent, dateReceived, deliveryReceiptCount, type,
|
||||||
threadId, status, mismatches, subscriptionId,
|
threadId, status, mismatches, subscriptionId,
|
||||||
expiresIn, expireStarted, readReceiptCount, unidentified);
|
expiresIn, expireStarted,
|
||||||
|
readReceiptCount, unidentified);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IdentityKeyMismatch> getMismatches(String document) {
|
private List<IdentityKeyMismatch> getMismatches(String document) {
|
||||||
|
@ -26,6 +26,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import net.sqlcipher.database.SQLiteDatabase;
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
|
||||||
@ -44,12 +45,15 @@ import org.thoughtcrime.securesms.mms.Slide;
|
|||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.DelimiterUtil;
|
import org.thoughtcrime.securesms.util.DelimiterUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.Pair;
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -71,6 +75,8 @@ public class ThreadDatabase extends Database {
|
|||||||
private static final String ERROR = "error";
|
private static final String ERROR = "error";
|
||||||
public static final String SNIPPET_TYPE = "snippet_type";
|
public static final String SNIPPET_TYPE = "snippet_type";
|
||||||
public static final String SNIPPET_URI = "snippet_uri";
|
public static final String SNIPPET_URI = "snippet_uri";
|
||||||
|
public static final String SNIPPET_CONTENT_TYPE = "snippet_content_type";
|
||||||
|
public static final String SNIPPET_EXTRAS = "snippet_extras";
|
||||||
public static final String ARCHIVED = "archived";
|
public static final String ARCHIVED = "archived";
|
||||||
public static final String STATUS = "status";
|
public static final String STATUS = "status";
|
||||||
public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count";
|
public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count";
|
||||||
@ -85,6 +91,7 @@ public class ThreadDatabase extends Database {
|
|||||||
SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " +
|
SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " +
|
||||||
TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
|
TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
|
||||||
SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " +
|
SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " +
|
||||||
|
SNIPPET_CONTENT_TYPE + " TEXT DEFAULT NULL, " + SNIPPET_EXTRAS + " TEXT DEFAULT NULL, " +
|
||||||
ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " +
|
ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " +
|
||||||
DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
|
DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
|
||||||
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " +
|
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " +
|
||||||
@ -97,7 +104,7 @@ public class ThreadDatabase extends Database {
|
|||||||
|
|
||||||
private static final String[] THREAD_PROJECTION = {
|
private static final String[] THREAD_PROJECTION = {
|
||||||
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE,
|
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE,
|
||||||
SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT
|
SNIPPET_URI, SNIPPET_CONTENT_TYPE, SNIPPET_EXTRAS, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final List<String> TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION)
|
private static final List<String> TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION)
|
||||||
@ -130,15 +137,28 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateThread(long threadId, long count, String body, @Nullable Uri attachment,
|
private void updateThread(long threadId, long count, String body, @Nullable Uri attachment,
|
||||||
|
@Nullable String contentType, @Nullable Extra extra,
|
||||||
long date, int status, int deliveryReceiptCount, long type, boolean unarchive,
|
long date, int status, int deliveryReceiptCount, long type, boolean unarchive,
|
||||||
long expiresIn, int readReceiptCount)
|
long expiresIn, int readReceiptCount)
|
||||||
{
|
{
|
||||||
|
String extraSerialized = null;
|
||||||
|
|
||||||
|
if (extra != null) {
|
||||||
|
try {
|
||||||
|
extraSerialized = JsonUtils.toJson(extra);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues(7);
|
ContentValues contentValues = new ContentValues(7);
|
||||||
contentValues.put(DATE, date - date % 1000);
|
contentValues.put(DATE, date - date % 1000);
|
||||||
contentValues.put(MESSAGE_COUNT, count);
|
contentValues.put(MESSAGE_COUNT, count);
|
||||||
contentValues.put(SNIPPET, body);
|
contentValues.put(SNIPPET, body);
|
||||||
contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString());
|
contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString());
|
||||||
contentValues.put(SNIPPET_TYPE, type);
|
contentValues.put(SNIPPET_TYPE, type);
|
||||||
|
contentValues.put(SNIPPET_CONTENT_TYPE, contentType);
|
||||||
|
contentValues.put(SNIPPET_EXTRAS, extraSerialized);
|
||||||
contentValues.put(STATUS, status);
|
contentValues.put(STATUS, status);
|
||||||
contentValues.put(DELIVERY_RECEIPT_COUNT, deliveryReceiptCount);
|
contentValues.put(DELIVERY_RECEIPT_COUNT, deliveryReceiptCount);
|
||||||
contentValues.put(READ_RECEIPT_COUNT, readReceiptCount);
|
contentValues.put(READ_RECEIPT_COUNT, readReceiptCount);
|
||||||
@ -571,6 +591,7 @@ public class ThreadDatabase extends Database {
|
|||||||
|
|
||||||
if (reader != null && (record = reader.getNext()) != null) {
|
if (reader != null && (record = reader.getNext()) != null) {
|
||||||
updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record),
|
updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record),
|
||||||
|
getContentTypeFor(record), getExtrasFor(record),
|
||||||
record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(),
|
record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(),
|
||||||
record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount());
|
record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount());
|
||||||
notifyConversationListListeners();
|
notifyConversationListListeners();
|
||||||
@ -601,13 +622,36 @@ public class ThreadDatabase extends Database {
|
|||||||
SlideDeck slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
|
SlideDeck slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
|
||||||
Slide thumbnail = slideDeck.getThumbnailSlide();
|
Slide thumbnail = slideDeck.getThumbnailSlide();
|
||||||
|
|
||||||
if (thumbnail != null) {
|
if (thumbnail != null && ((MmsMessageRecord) record).getRevealDuration() == 0) {
|
||||||
return thumbnail.getThumbnailUri();
|
return thumbnail.getThumbnailUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @Nullable String getContentTypeFor(MessageRecord record) {
|
||||||
|
if (record.isMms()) {
|
||||||
|
SlideDeck slideDeck = ((MmsMessageRecord) record).getSlideDeck();
|
||||||
|
|
||||||
|
if (slideDeck.getSlides().size() > 0) {
|
||||||
|
return slideDeck.getSlides().get(0).getContentType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable Extra getExtrasFor(MessageRecord record) {
|
||||||
|
if (record.isMms() && ((MmsMessageRecord) record).getRevealDuration() > 0) {
|
||||||
|
return Extra.forRevealableMessage();
|
||||||
|
} else if (record.isMms() && ((MmsMessageRecord) record).getSlideDeck().getStickerSlide() != null) {
|
||||||
|
return Extra.forSticker();
|
||||||
|
} else if (record.isMms() && ((MmsMessageRecord) record).getSlideDeck().getSlides().size() > 1) {
|
||||||
|
return Extra.forAlbum();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private @NonNull String createQuery(@NonNull String where, int limit) {
|
private @NonNull String createQuery(@NonNull String where, int limit) {
|
||||||
String projection = Util.join(COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION, ",");
|
String projection = Util.join(COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION, ",");
|
||||||
String query =
|
String query =
|
||||||
@ -686,12 +730,24 @@ public class ThreadDatabase extends Database {
|
|||||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN));
|
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN));
|
||||||
long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN));
|
long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN));
|
||||||
Uri snippetUri = getSnippetUri(cursor);
|
Uri snippetUri = getSnippetUri(cursor);
|
||||||
|
String contentType = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_CONTENT_TYPE));
|
||||||
|
String extraString = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_EXTRAS));
|
||||||
|
|
||||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||||
readReceiptCount = 0;
|
readReceiptCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ThreadRecord(body, snippetUri, recipient, date, count,
|
Extra extra = null;
|
||||||
|
|
||||||
|
if (extraString != null) {
|
||||||
|
try {
|
||||||
|
extra = JsonUtils.fromJson(extraString, Extra.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed to decode extras!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ThreadRecord(body, snippetUri, contentType, extra, recipient, date, count,
|
||||||
unreadCount, threadId, deliveryReceiptCount, status, type,
|
unreadCount, threadId, deliveryReceiptCount, status, type,
|
||||||
distributionType, archived, expiresIn, lastSeen, readReceiptCount);
|
distributionType, archived, expiresIn, lastSeen, readReceiptCount);
|
||||||
}
|
}
|
||||||
@ -716,4 +772,45 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class Extra {
|
||||||
|
|
||||||
|
@JsonProperty private final boolean isRevealable;
|
||||||
|
@JsonProperty private final boolean isSticker;
|
||||||
|
@JsonProperty private final boolean isAlbum;
|
||||||
|
|
||||||
|
public Extra(@JsonProperty("isRevealable") boolean isRevealable,
|
||||||
|
@JsonProperty("isSticker") boolean isSticker,
|
||||||
|
@JsonProperty("isAlbum") boolean isAlbum)
|
||||||
|
{
|
||||||
|
this.isRevealable = isRevealable;
|
||||||
|
this.isSticker = isSticker;
|
||||||
|
this.isAlbum = isAlbum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull Extra forRevealableMessage() {
|
||||||
|
return new Extra(true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull Extra forSticker() {
|
||||||
|
return new Extra(false, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull Extra forAlbum() {
|
||||||
|
return new Extra(false, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isRevealable() {
|
||||||
|
return isRevealable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSticker() {
|
||||||
|
return isSticker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlbum() {
|
||||||
|
return isAlbum;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,8 +66,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
private static final int RECIPIENT_FORCE_SMS_SELECTION = 19;
|
private static final int RECIPIENT_FORCE_SMS_SELECTION = 19;
|
||||||
private static final int JOBMANAGER_STRIKES_BACK = 20;
|
private static final int JOBMANAGER_STRIKES_BACK = 20;
|
||||||
private static final int STICKERS = 21;
|
private static final int STICKERS = 21;
|
||||||
|
private static final int REVEALABLE_MESSAGES = 22;
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = 21;
|
private static final int DATABASE_VERSION = 22;
|
||||||
private static final String DATABASE_NAME = "signal.db";
|
private static final String DATABASE_NAME = "signal.db";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@ -462,6 +463,14 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL("CREATE INDEX IF NOT EXISTS part_sticker_pack_id_index ON part (sticker_pack_id)");
|
db.execSQL("CREATE INDEX IF NOT EXISTS part_sticker_pack_id_index ON part (sticker_pack_id)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < REVEALABLE_MESSAGES) {
|
||||||
|
db.execSQL("ALTER TABLE mms ADD COLUMN reveal_duration INTEGER DEFAULT 0");
|
||||||
|
db.execSQL("ALTER TABLE mms ADD COLUMN reveal_start_time INTEGER DEFAULT 0");
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE thread ADD COLUMN snippet_content_type TEXT DEFAULT NULL");
|
||||||
|
db.execSQL("ALTER TABLE thread ADD COLUMN snippet_extras TEXT DEFAULT NULL");
|
||||||
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
|
@ -44,6 +44,7 @@ public class ConversationListLoader extends AbstractCursorLoader {
|
|||||||
ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT,
|
ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT,
|
||||||
ThreadDatabase.ADDRESS, ThreadDatabase.SNIPPET, ThreadDatabase.READ, ThreadDatabase.UNREAD_COUNT,
|
ThreadDatabase.ADDRESS, ThreadDatabase.SNIPPET, ThreadDatabase.READ, ThreadDatabase.UNREAD_COUNT,
|
||||||
ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI,
|
ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI,
|
||||||
|
ThreadDatabase.SNIPPET_CONTENT_TYPE, ThreadDatabase.SNIPPET_EXTRAS,
|
||||||
ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.DELIVERY_RECEIPT_COUNT,
|
ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.DELIVERY_RECEIPT_COUNT,
|
||||||
ThreadDatabase.EXPIRES_IN, ThreadDatabase.LAST_SEEN, ThreadDatabase.READ_RECEIPT_COUNT}, 1);
|
ThreadDatabase.EXPIRES_IN, ThreadDatabase.LAST_SEEN, ThreadDatabase.READ_RECEIPT_COUNT}, 1);
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ public class ConversationListLoader extends AbstractCursorLoader {
|
|||||||
|
|
||||||
switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount,
|
switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount,
|
||||||
"-1", null, 1, 0, ThreadDatabase.DistributionTypes.ARCHIVE,
|
"-1", null, 1, 0, ThreadDatabase.DistributionTypes.ARCHIVE,
|
||||||
0, null, 0, -1, 0, 0, 0, -1});
|
0, null, null, null, 0, -1, 0, 0, 0, -1});
|
||||||
|
|
||||||
cursorList.add(switchToArchiveCursor);
|
cursorList.add(switchToArchiveCursor);
|
||||||
}
|
}
|
||||||
|
@ -54,14 +54,15 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
|||||||
int partCount, long mailbox,
|
int partCount, long mailbox,
|
||||||
List<IdentityKeyMismatch> mismatches,
|
List<IdentityKeyMismatch> mismatches,
|
||||||
List<NetworkFailure> failures, int subscriptionId,
|
List<NetworkFailure> failures, int subscriptionId,
|
||||||
long expiresIn, long expireStarted, int readReceiptCount,
|
long expiresIn, long expireStarted,
|
||||||
|
long revealDuration, long revealStartTime, int readReceiptCount,
|
||||||
@Nullable Quote quote, @NonNull List<Contact> contacts,
|
@Nullable Quote quote, @NonNull List<Contact> contacts,
|
||||||
@NonNull List<LinkPreview> linkPreviews, boolean unidentified)
|
@NonNull List<LinkPreview> linkPreviews, boolean unidentified)
|
||||||
{
|
{
|
||||||
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
|
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
|
||||||
dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
|
dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
|
||||||
subscriptionId, expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts,
|
subscriptionId, expiresIn, expireStarted, revealDuration, revealStartTime, slideDeck,
|
||||||
linkPreviews, unidentified);
|
readReceiptCount, quote, contacts, linkPreviews, unidentified);
|
||||||
this.partCount = partCount;
|
this.partCount = partCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,19 +22,25 @@ public abstract class MmsMessageRecord extends MessageRecord {
|
|||||||
private final @NonNull List<Contact> contacts = new LinkedList<>();
|
private final @NonNull List<Contact> contacts = new LinkedList<>();
|
||||||
private final @NonNull List<LinkPreview> linkPreviews = new LinkedList<>();
|
private final @NonNull List<LinkPreview> linkPreviews = new LinkedList<>();
|
||||||
|
|
||||||
|
private final long revealDuration;
|
||||||
|
private final long revealStartTime;
|
||||||
|
|
||||||
MmsMessageRecord(long id, String body, Recipient conversationRecipient,
|
MmsMessageRecord(long id, String body, Recipient conversationRecipient,
|
||||||
Recipient individualRecipient, int recipientDeviceId, long dateSent,
|
Recipient individualRecipient, int recipientDeviceId, long dateSent,
|
||||||
long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount,
|
long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount,
|
||||||
long type, List<IdentityKeyMismatch> mismatches,
|
long type, List<IdentityKeyMismatch> mismatches,
|
||||||
List<NetworkFailure> networkFailures, int subscriptionId, long expiresIn,
|
List<NetworkFailure> networkFailures, int subscriptionId, long expiresIn,
|
||||||
long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount,
|
long expireStarted, long revealDuration, long revealStartTime,
|
||||||
|
@NonNull SlideDeck slideDeck, int readReceiptCount,
|
||||||
@Nullable Quote quote, @NonNull List<Contact> contacts,
|
@Nullable Quote quote, @NonNull List<Contact> contacts,
|
||||||
@NonNull List<LinkPreview> linkPreviews, boolean unidentified)
|
@NonNull List<LinkPreview> linkPreviews, boolean unidentified)
|
||||||
{
|
{
|
||||||
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount, unidentified);
|
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount, unidentified);
|
||||||
|
|
||||||
this.slideDeck = slideDeck;
|
this.slideDeck = slideDeck;
|
||||||
this.quote = quote;
|
this.quote = quote;
|
||||||
|
this.revealDuration = revealDuration;
|
||||||
|
this.revealStartTime = revealStartTime;
|
||||||
|
|
||||||
this.contacts.addAll(contacts);
|
this.contacts.addAll(contacts);
|
||||||
this.linkPreviews.addAll(linkPreviews);
|
this.linkPreviews.addAll(linkPreviews);
|
||||||
@ -76,4 +82,12 @@ public abstract class MmsMessageRecord extends MessageRecord {
|
|||||||
public @NonNull List<LinkPreview> getLinkPreviews() {
|
public @NonNull List<LinkPreview> getLinkPreviews() {
|
||||||
return linkPreviews;
|
return linkPreviews;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getRevealDuration() {
|
||||||
|
return revealDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRevealStartTime() {
|
||||||
|
return revealStartTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
|
|||||||
super(id, "", conversationRecipient, individualRecipient, recipientDeviceId,
|
super(id, "", conversationRecipient, individualRecipient, recipientDeviceId,
|
||||||
dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
|
dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
|
||||||
new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId,
|
new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId,
|
||||||
0, 0, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false);
|
0, 0, 0, 0, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false);
|
||||||
|
|
||||||
this.contentLocation = contentLocation;
|
this.contentLocation = contentLocation;
|
||||||
this.messageSize = messageSize;
|
this.messageSize = messageSize;
|
||||||
|
@ -86,6 +86,8 @@ public class SmsMessageRecord extends MessageRecord {
|
|||||||
return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_reset_s, getIndividualRecipient().toShortString()));
|
return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_reset_s, getIndividualRecipient().toShortString()));
|
||||||
} else if (SmsDatabase.Types.isUnsupportedMessageType(type)) {
|
} else if (SmsDatabase.Types.isUnsupportedMessageType(type)) {
|
||||||
return emphasisAdded(context.getString(R.string.SmsMessageRecord_this_message_could_not_be_processed_because_it_was_sent_from_a_newer_version));
|
return emphasisAdded(context.getString(R.string.SmsMessageRecord_this_message_could_not_be_processed_because_it_was_sent_from_a_newer_version));
|
||||||
|
} else if (SmsDatabase.Types.isInvalidMessageType(type)) {
|
||||||
|
return emphasisAdded(context.getString(R.string.SmsMessageRecord_error_handling_incoming_message));
|
||||||
} else {
|
} else {
|
||||||
return super.getDisplayBody(context);
|
return super.getDisplayBody(context);
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,11 @@ import android.text.style.StyleSpan;
|
|||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadDatabase.Extra;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message record model which represents thread heading messages.
|
* The message record model which represents thread heading messages.
|
||||||
@ -41,6 +44,8 @@ import org.thoughtcrime.securesms.util.ExpirationUtil;
|
|||||||
public class ThreadRecord extends DisplayRecord {
|
public class ThreadRecord extends DisplayRecord {
|
||||||
|
|
||||||
private @Nullable final Uri snippetUri;
|
private @Nullable final Uri snippetUri;
|
||||||
|
private @Nullable final String contentType;
|
||||||
|
private @Nullable final Extra extra;
|
||||||
private final long count;
|
private final long count;
|
||||||
private final int unreadCount;
|
private final int unreadCount;
|
||||||
private final int distributionType;
|
private final int distributionType;
|
||||||
@ -49,6 +54,7 @@ public class ThreadRecord extends DisplayRecord {
|
|||||||
private final long lastSeen;
|
private final long lastSeen;
|
||||||
|
|
||||||
public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri,
|
public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri,
|
||||||
|
@Nullable String contentType, @Nullable Extra extra,
|
||||||
@NonNull Recipient recipient, long date, long count, int unreadCount,
|
@NonNull Recipient recipient, long date, long count, int unreadCount,
|
||||||
long threadId, int deliveryReceiptCount, int status, long snippetType,
|
long threadId, int deliveryReceiptCount, int status, long snippetType,
|
||||||
int distributionType, boolean archived, long expiresIn, long lastSeen,
|
int distributionType, boolean archived, long expiresIn, long lastSeen,
|
||||||
@ -56,6 +62,8 @@ public class ThreadRecord extends DisplayRecord {
|
|||||||
{
|
{
|
||||||
super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount);
|
super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount);
|
||||||
this.snippetUri = snippetUri;
|
this.snippetUri = snippetUri;
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.extra = extra;
|
||||||
this.count = count;
|
this.count = count;
|
||||||
this.unreadCount = unreadCount;
|
this.unreadCount = unreadCount;
|
||||||
this.distributionType = distributionType;
|
this.distributionType = distributionType;
|
||||||
@ -113,7 +121,13 @@ public class ThreadRecord extends DisplayRecord {
|
|||||||
return emphasisAdded(context.getString(R.string.ThreadRecord_message_could_not_be_processed));
|
return emphasisAdded(context.getString(R.string.ThreadRecord_message_could_not_be_processed));
|
||||||
} else {
|
} else {
|
||||||
if (TextUtils.isEmpty(getBody())) {
|
if (TextUtils.isEmpty(getBody())) {
|
||||||
return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_media_message)));
|
if (extra != null && extra.isSticker()) {
|
||||||
|
return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_sticker)));
|
||||||
|
} else if (extra != null && extra.isRevealable() && MediaUtil.isImageType(contentType)) {
|
||||||
|
return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_photo)));
|
||||||
|
} else {
|
||||||
|
return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_media_message)));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return new SpannableString(getBody());
|
return new SpannableString(getBody());
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ public class GroupManager {
|
|||||||
avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null);
|
avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, null, Collections.emptyList(), Collections.emptyList());
|
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, 0, null, Collections.emptyList(), Collections.emptyList());
|
||||||
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
|
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
|
||||||
|
|
||||||
return new GroupActionResult(groupRecipient, threadId);
|
return new GroupActionResult(groupRecipient, threadId);
|
||||||
|
@ -212,7 +212,7 @@ public class GroupMessageProcessor {
|
|||||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||||
Address addres = Address.fromExternal(context, GroupUtil.getEncodedId(group.getGroupId(), false));
|
Address addres = Address.fromExternal(context, GroupUtil.getEncodedId(group.getGroupId(), false));
|
||||||
Recipient recipient = Recipient.from(context, addres, false);
|
Recipient recipient = Recipient.from(context, addres, false);
|
||||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, null, Collections.emptyList(), Collections.emptyList());
|
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, 0, null, Collections.emptyList(), Collections.emptyList());
|
||||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||||
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
|
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
|
||||||
|
|
||||||
@ -222,7 +222,7 @@ public class GroupMessageProcessor {
|
|||||||
} else {
|
} else {
|
||||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||||
String body = Base64.encodeBytes(storage.toByteArray());
|
String body = Base64.encodeBytes(storage.toByteArray());
|
||||||
IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, content.isNeedsReceipt());
|
IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, 0, content.isNeedsReceipt());
|
||||||
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
|
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
|
||||||
|
|
||||||
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(groupMessage);
|
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(groupMessage);
|
||||||
|
@ -40,6 +40,7 @@ public final class JobManagerFactories {
|
|||||||
put(MultiDeviceGroupUpdateJob.KEY, new MultiDeviceGroupUpdateJob.Factory());
|
put(MultiDeviceGroupUpdateJob.KEY, new MultiDeviceGroupUpdateJob.Factory());
|
||||||
put(MultiDeviceProfileKeyUpdateJob.KEY, new MultiDeviceProfileKeyUpdateJob.Factory());
|
put(MultiDeviceProfileKeyUpdateJob.KEY, new MultiDeviceProfileKeyUpdateJob.Factory());
|
||||||
put(MultiDeviceReadUpdateJob.KEY, new MultiDeviceReadUpdateJob.Factory());
|
put(MultiDeviceReadUpdateJob.KEY, new MultiDeviceReadUpdateJob.Factory());
|
||||||
|
put(MultiDeviceRevealUpdateJob.KEY, new MultiDeviceRevealUpdateJob.Factory());
|
||||||
put(MultiDeviceStickerPackOperationJob.KEY, new MultiDeviceStickerPackOperationJob.Factory());
|
put(MultiDeviceStickerPackOperationJob.KEY, new MultiDeviceStickerPackOperationJob.Factory());
|
||||||
put(MultiDeviceStickerPackSyncJob.KEY, new MultiDeviceStickerPackSyncJob.Factory());
|
put(MultiDeviceStickerPackSyncJob.KEY, new MultiDeviceStickerPackSyncJob.Factory());
|
||||||
put(MultiDeviceVerifiedUpdateJob.KEY, new MultiDeviceVerifiedUpdateJob.Factory());
|
put(MultiDeviceVerifiedUpdateJob.KEY, new MultiDeviceVerifiedUpdateJob.Factory());
|
||||||
|
@ -247,7 +247,7 @@ public class MmsDownloadJob extends BaseJob {
|
|||||||
group = Optional.of(Address.fromSerialized(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(new LinkedList<>(members), true)));
|
group = Optional.of(Address.fromSerialized(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(new LinkedList<>(members), true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false, false);
|
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false, 0, false);
|
||||||
Optional<InsertResult> insertResult = database.insertMessageInbox(message, contentLocation, threadId);
|
Optional<InsertResult> insertResult = database.insertMessageInbox(message, contentLocation, threadId);
|
||||||
|
|
||||||
if (insertResult.isPresent()) {
|
if (insertResult.isPresent()) {
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||||
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
|
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
|
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||||
|
import org.whispersystems.signalservice.api.messages.multidevice.MessageTimerReadMessage;
|
||||||
|
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class MultiDeviceRevealUpdateJob extends BaseJob {
|
||||||
|
|
||||||
|
public static final String KEY = "MultiDeviceRevealUpdateJob";
|
||||||
|
|
||||||
|
private static final String TAG = MultiDeviceRevealUpdateJob.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final String KEY_MESSAGE_ID = "message_id";
|
||||||
|
|
||||||
|
private SerializableSyncMessageId messageId;
|
||||||
|
|
||||||
|
public MultiDeviceRevealUpdateJob(SyncMessageId messageId) {
|
||||||
|
this(new Parameters.Builder()
|
||||||
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
|
.build(),
|
||||||
|
messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultiDeviceRevealUpdateJob(@NonNull Parameters parameters, @NonNull SyncMessageId syncMessageId) {
|
||||||
|
super(parameters);
|
||||||
|
this.messageId = new SerializableSyncMessageId(syncMessageId.getAddress().toPhoneString(), syncMessageId.getTimetamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Data serialize() {
|
||||||
|
String serialized;
|
||||||
|
|
||||||
|
try {
|
||||||
|
serialized = JsonUtils.toJson(messageId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Data.Builder().putString(KEY_MESSAGE_ID, serialized).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String getFactoryKey() {
|
||||||
|
return KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRun() throws IOException, UntrustedIdentityException {
|
||||||
|
if (!TextSecurePreferences.isMultiDevice(context)) {
|
||||||
|
Log.i(TAG, "Not multi device...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||||
|
MessageTimerReadMessage timerMessage = new MessageTimerReadMessage(messageId.sender, messageId.timestamp);
|
||||||
|
|
||||||
|
messageSender.sendMessage(SignalServiceSyncMessage.forMessageTimerRead(timerMessage), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onShouldRetry(@NonNull Exception exception) {
|
||||||
|
return exception instanceof PushNetworkException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCanceled() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SerializableSyncMessageId implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final String sender;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
|
private SerializableSyncMessageId(@JsonProperty("sender") String sender, @JsonProperty("timestamp") long timestamp) {
|
||||||
|
this.sender = sender;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Factory implements Job.Factory<MultiDeviceRevealUpdateJob> {
|
||||||
|
@Override
|
||||||
|
public @NonNull MultiDeviceRevealUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||||
|
SerializableSyncMessageId messageId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
messageId = JsonUtils.fromJson(data.getString(KEY_MESSAGE_ID), SerializableSyncMessageId.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncMessageId syncMessageId = new SyncMessageId(Address.fromSerialized(messageId.sender), messageId.timestamp);
|
||||||
|
|
||||||
|
return new MultiDeviceRevealUpdateJob(parameters, syncMessageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.R;
|
|||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||||
import org.thoughtcrime.securesms.attachments.PointerAttachment;
|
import org.thoughtcrime.securesms.attachments.PointerAttachment;
|
||||||
|
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
|
||||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||||
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
|
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
|
||||||
@ -79,6 +80,8 @@ import org.thoughtcrime.securesms.mms.StickerSlide;
|
|||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealExpirationInfo;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealableMessageManager;
|
||||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
|
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
|
||||||
@ -109,6 +112,7 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
|||||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||||
|
import org.whispersystems.signalservice.api.messages.multidevice.MessageTimerReadMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||||
@ -253,7 +257,8 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
SignalServiceDataMessage message = content.getDataMessage().get();
|
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||||
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
||||||
|
|
||||||
if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
if (isInvalidMessage(message)) handleInvalidMessage(content.getSender(), content.getSenderDevice(), message.getGroupInfo(), content.getTimestamp(), smsMessageId);
|
||||||
|
else if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
||||||
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
||||||
else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId);
|
else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId);
|
||||||
else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId);
|
else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId);
|
||||||
@ -278,6 +283,7 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get());
|
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get());
|
||||||
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get());
|
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get());
|
||||||
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp());
|
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp());
|
||||||
|
else if (syncMessage.getMessageTimerRead().isPresent()) handleSynchronizeMessageTimerReadMessage(syncMessage.getMessageTimerRead().get(), content.getTimestamp());
|
||||||
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
|
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
|
||||||
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
|
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
|
||||||
else Log.w(TAG, "Contains no known sync types...");
|
else Log.w(TAG, "Contains no known sync types...");
|
||||||
@ -425,7 +431,7 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, content.getSender()),
|
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, content.getSender()),
|
||||||
content.getSenderDevice(),
|
content.getSenderDevice(),
|
||||||
content.getTimestamp(),
|
content.getTimestamp(),
|
||||||
"", Optional.absent(), 0,
|
"", Optional.absent(), 0, 0,
|
||||||
content.isNeedsReceipt());
|
content.isNeedsReceipt());
|
||||||
|
|
||||||
Long threadId;
|
Long threadId;
|
||||||
@ -509,6 +515,7 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()),
|
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()),
|
||||||
message.getTimestamp(), -1,
|
message.getTimestamp(), -1,
|
||||||
message.getExpiresInSeconds() * 1000L, true,
|
message.getExpiresInSeconds() * 1000L, true,
|
||||||
|
0,
|
||||||
content.isNeedsReceipt(),
|
content.isNeedsReceipt(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
message.getGroupInfo(),
|
message.getGroupInfo(),
|
||||||
@ -669,6 +676,17 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
MessageNotifier.updateNotification(context);
|
MessageNotifier.updateNotification(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleSynchronizeMessageTimerReadMessage(@NonNull MessageTimerReadMessage timerMessage, long envelopeTimestamp) {
|
||||||
|
SyncMessageId messageId = new SyncMessageId(Address.fromExternal(context, timerMessage.getSender()), timerMessage.getTimestamp());
|
||||||
|
|
||||||
|
DatabaseFactory.getMmsDatabase(context).markRevealStarted(messageId, envelopeTimestamp);
|
||||||
|
ApplicationContext.getInstance(context).getRevealableMessageManager().scheduleIfNecessary();
|
||||||
|
|
||||||
|
MessageNotifier.setLastDesktopActivityTimestamp(envelopeTimestamp);
|
||||||
|
MessageNotifier.cancelDelayedNotifications();
|
||||||
|
MessageNotifier.updateNotification(context);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleMediaMessage(@NonNull SignalServiceContent content,
|
private void handleMediaMessage(@NonNull SignalServiceContent content,
|
||||||
@NonNull SignalServiceDataMessage message,
|
@NonNull SignalServiceDataMessage message,
|
||||||
@NonNull Optional<Long> smsMessageId)
|
@NonNull Optional<Long> smsMessageId)
|
||||||
@ -689,6 +707,7 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()),
|
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()),
|
||||||
message.getTimestamp(), -1,
|
message.getTimestamp(), -1,
|
||||||
message.getExpiresInSeconds() * 1000L, false,
|
message.getExpiresInSeconds() * 1000L, false,
|
||||||
|
message.getMessageTimerInSeconds() * 1000,
|
||||||
content.isNeedsReceipt(),
|
content.isNeedsReceipt(),
|
||||||
message.getBody(),
|
message.getBody(),
|
||||||
message.getGroupInfo(),
|
message.getGroupInfo(),
|
||||||
@ -698,7 +717,6 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
linkPreviews,
|
linkPreviews,
|
||||||
sticker);
|
sticker);
|
||||||
|
|
||||||
|
|
||||||
insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||||
|
|
||||||
if (insertResult.isPresent()) {
|
if (insertResult.isPresent()) {
|
||||||
@ -728,6 +746,10 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
|
|
||||||
if (insertResult.isPresent()) {
|
if (insertResult.isPresent()) {
|
||||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||||
|
|
||||||
|
if (message.getMessageTimerInSeconds() > 0) {
|
||||||
|
ApplicationContext.getInstance(context).getRevealableMessageManager().scheduleIfNecessary();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -758,7 +780,8 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
Optional<Attachment> sticker = getStickerAttachment(message.getMessage().getSticker());
|
Optional<Attachment> sticker = getStickerAttachment(message.getMessage().getSticker());
|
||||||
Optional<List<Contact>> sharedContacts = getContacts(message.getMessage().getSharedContacts());
|
Optional<List<Contact>> sharedContacts = getContacts(message.getMessage().getSharedContacts());
|
||||||
Optional<List<LinkPreview>> previews = getLinkPreviews(message.getMessage().getPreviews(), message.getMessage().getBody().or(""));
|
Optional<List<LinkPreview>> previews = getLinkPreviews(message.getMessage().getPreviews(), message.getMessage().getBody().or(""));
|
||||||
List<Attachment> syncAttachments = PointerAttachment.forPointers(message.getMessage().getAttachments());
|
long messageTimer = message.getMessage().getMessageTimerInSeconds() * 1000;
|
||||||
|
List<Attachment> syncAttachments = messageTimer == 0 ? PointerAttachment.forPointers(message.getMessage().getAttachments()) : Collections.emptyList();
|
||||||
|
|
||||||
if (sticker.isPresent()) {
|
if (sticker.isPresent()) {
|
||||||
syncAttachments.add(sticker.get());
|
syncAttachments.add(sticker.get());
|
||||||
@ -768,6 +791,7 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
syncAttachments,
|
syncAttachments,
|
||||||
message.getTimestamp(), -1,
|
message.getTimestamp(), -1,
|
||||||
message.getMessage().getExpiresInSeconds() * 1000,
|
message.getMessage().getExpiresInSeconds() * 1000,
|
||||||
|
messageTimer,
|
||||||
ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(),
|
ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(),
|
||||||
sharedContacts.or(Collections.emptyList()),
|
sharedContacts.or(Collections.emptyList()),
|
||||||
previews.or(Collections.emptyList()),
|
previews.or(Collections.emptyList()),
|
||||||
@ -897,6 +921,7 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
message.getTimestamp(), body,
|
message.getTimestamp(), body,
|
||||||
message.getGroupInfo(),
|
message.getGroupInfo(),
|
||||||
message.getExpiresInSeconds() * 1000L,
|
message.getExpiresInSeconds() * 1000L,
|
||||||
|
message.getMessageTimerInSeconds() * 1000L,
|
||||||
content.isNeedsReceipt());
|
content.isNeedsReceipt());
|
||||||
|
|
||||||
textMessage = new IncomingEncryptedMessage(textMessage, body);
|
textMessage = new IncomingEncryptedMessage(textMessage, body);
|
||||||
@ -931,7 +956,7 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
long messageId;
|
long messageId;
|
||||||
|
|
||||||
if (isGroup) {
|
if (isGroup) {
|
||||||
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), Collections.emptyList());
|
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, 0, ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), Collections.emptyList());
|
||||||
outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage);
|
outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage);
|
||||||
|
|
||||||
messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null);
|
messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null);
|
||||||
@ -1035,6 +1060,26 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleInvalidMessage(@NonNull String sender,
|
||||||
|
int senderDevice,
|
||||||
|
@NonNull Optional<SignalServiceGroup> group,
|
||||||
|
long timestamp,
|
||||||
|
@NonNull Optional<Long> smsMessageId)
|
||||||
|
{
|
||||||
|
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||||
|
|
||||||
|
if (!smsMessageId.isPresent()) {
|
||||||
|
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp, group);
|
||||||
|
|
||||||
|
if (insertResult.isPresent()) {
|
||||||
|
smsDatabase.markAsInvalidMessage(insertResult.get().getMessageId());
|
||||||
|
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
smsDatabase.markAsNoSession(smsMessageId.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp,
|
private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp,
|
||||||
@NonNull Optional<Long> smsMessageId)
|
@NonNull Optional<Long> smsMessageId)
|
||||||
{
|
{
|
||||||
@ -1149,6 +1194,17 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isInvalidMessage(@NonNull SignalServiceDataMessage message) {
|
||||||
|
if (message.getMessageTimerInSeconds() > 0) {
|
||||||
|
return !message.getAttachments().isPresent() ||
|
||||||
|
message.getAttachments().get().size() != 1 ||
|
||||||
|
!MediaUtil.isImageType(message.getAttachments().get().get(0).getContentType().toLowerCase());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<QuoteModel> getValidatedQuote(Optional<SignalServiceDataMessage.Quote> quote) {
|
private Optional<QuoteModel> getValidatedQuote(Optional<SignalServiceDataMessage.Quote> quote) {
|
||||||
if (!quote.isPresent()) return Optional.absent();
|
if (!quote.isPresent()) return Optional.absent();
|
||||||
|
|
||||||
@ -1172,12 +1228,18 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
|
|
||||||
if (message.isMms()) {
|
if (message.isMms()) {
|
||||||
MmsMessageRecord mmsMessage = (MmsMessageRecord) message;
|
MmsMessageRecord mmsMessage = (MmsMessageRecord) message;
|
||||||
attachments = mmsMessage.getSlideDeck().asAttachments();
|
|
||||||
if (attachments.isEmpty()) {
|
if (mmsMessage.getRevealDuration() == 0) {
|
||||||
attachments.addAll(Stream.of(mmsMessage.getLinkPreviews())
|
attachments = mmsMessage.getSlideDeck().asAttachments();
|
||||||
.filter(lp -> lp.getThumbnail().isPresent())
|
|
||||||
.map(lp -> lp.getThumbnail().get())
|
if (attachments.isEmpty()) {
|
||||||
.toList());
|
attachments.addAll(Stream.of(mmsMessage.getLinkPreviews())
|
||||||
|
.filter(lp -> lp.getThumbnail().isPresent())
|
||||||
|
.map(lp -> lp.getThumbnail().get())
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
} else if (quote.get().getAttachments().size() > 0) {
|
||||||
|
attachments.add(new TombstoneAttachment(quote.get().getAttachments().get(0).getContentType(), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1185,6 +1247,7 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log.w(TAG, "Didn't find matching message record...");
|
Log.w(TAG, "Didn't find matching message record...");
|
||||||
|
|
||||||
return Optional.of(new QuoteModel(quote.get().getId(),
|
return Optional.of(new QuoteModel(quote.get().getId(),
|
||||||
author,
|
author,
|
||||||
quote.get().getText(),
|
quote.get().getText(),
|
||||||
@ -1272,7 +1335,7 @@ public class PushDecryptJob extends BaseJob {
|
|||||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||||
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, sender),
|
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, sender),
|
||||||
senderDevice, timestamp, "",
|
senderDevice, timestamp, "",
|
||||||
group, 0, false);
|
group, 0, 0, false);
|
||||||
|
|
||||||
textMessage = new IncomingEncryptedMessage(textMessage, "");
|
textMessage = new IncomingEncryptedMessage(textMessage, "");
|
||||||
return database.insertMessageInbox(textMessage);
|
return database.insertMessageInbox(textMessage);
|
||||||
|
@ -194,6 +194,13 @@ public class PushGroupSendJob extends PushSendJob {
|
|||||||
.getExpiringMessageManager()
|
.getExpiringMessageManager()
|
||||||
.scheduleDeletion(messageId, true, message.getExpiresIn());
|
.scheduleDeletion(messageId, true, message.getExpiresIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.getRevealDuration() > 0) {
|
||||||
|
database.markRevealStarted(messageId);
|
||||||
|
ApplicationContext.getInstance(context)
|
||||||
|
.getRevealableMessageManager()
|
||||||
|
.scheduleIfNecessary();
|
||||||
|
}
|
||||||
} else if (!networkFailures.isEmpty()) {
|
} else if (!networkFailures.isEmpty()) {
|
||||||
throw new RetryLaterException();
|
throw new RetryLaterException();
|
||||||
} else if (!identityMismatches.isEmpty()) {
|
} else if (!identityMismatches.isEmpty()) {
|
||||||
@ -262,6 +269,7 @@ public class PushGroupSendJob extends PushSendJob {
|
|||||||
.withAttachments(attachmentPointers)
|
.withAttachments(attachmentPointers)
|
||||||
.withBody(message.getBody())
|
.withBody(message.getBody())
|
||||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||||
|
.withMessageTimer((int)(message.getRevealDuration() / 1000))
|
||||||
.asExpirationUpdate(message.isExpirationUpdate())
|
.asExpirationUpdate(message.isExpirationUpdate())
|
||||||
.withProfileKey(profileKey.orNull())
|
.withProfileKey(profileKey.orNull())
|
||||||
.withQuote(quote.orNull())
|
.withQuote(quote.orNull())
|
||||||
|
@ -159,6 +159,13 @@ public class PushMediaSendJob extends PushSendJob {
|
|||||||
expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn());
|
expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.getRevealDuration() > 0) {
|
||||||
|
database.markRevealStarted(messageId);
|
||||||
|
ApplicationContext.getInstance(context)
|
||||||
|
.getRevealableMessageManager()
|
||||||
|
.scheduleIfNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
log(TAG, "Sent message: " + messageId);
|
log(TAG, "Sent message: " + messageId);
|
||||||
|
|
||||||
} catch (InsecureFallbackApprovalException ifae) {
|
} catch (InsecureFallbackApprovalException ifae) {
|
||||||
@ -210,6 +217,7 @@ public class PushMediaSendJob extends PushSendJob {
|
|||||||
.withAttachments(serviceAttachments)
|
.withAttachments(serviceAttachments)
|
||||||
.withTimestamp(message.getSentTimeMillis())
|
.withTimestamp(message.getSentTimeMillis())
|
||||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||||
|
.withMessageTimer((int) message.getRevealDuration() / 1000)
|
||||||
.withProfileKey(profileKey.orNull())
|
.withProfileKey(profileKey.orNull())
|
||||||
.withQuote(quote.orNull())
|
.withQuote(quote.orNull())
|
||||||
.withSticker(sticker.orNull())
|
.withSticker(sticker.orNull())
|
||||||
|
@ -47,7 +47,7 @@ import org.thoughtcrime.securesms.database.Address;
|
|||||||
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
|
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
|
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
|
||||||
import org.thoughtcrime.securesms.mediasend.MediaSendViewModel.TimerState;
|
import org.thoughtcrime.securesms.mediasend.MediaSendViewModel.RevealState;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
@ -122,6 +122,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
|||||||
private ViewGroup composeContainer;
|
private ViewGroup composeContainer;
|
||||||
private ViewGroup countButton;
|
private ViewGroup countButton;
|
||||||
private TextView countButtonText;
|
private TextView countButtonText;
|
||||||
|
private ImageView revealButton;
|
||||||
private EmojiEditText captionText;
|
private EmojiEditText captionText;
|
||||||
private EmojiToggle emojiToggle;
|
private EmojiToggle emojiToggle;
|
||||||
private Stub<MediaKeyboard> emojiDrawer;
|
private Stub<MediaKeyboard> emojiDrawer;
|
||||||
@ -191,6 +192,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
|||||||
composeContainer = findViewById(R.id.mediasend_compose_container);
|
composeContainer = findViewById(R.id.mediasend_compose_container);
|
||||||
countButton = findViewById(R.id.mediasend_count_button);
|
countButton = findViewById(R.id.mediasend_count_button);
|
||||||
countButtonText = findViewById(R.id.mediasend_count_button_text);
|
countButtonText = findViewById(R.id.mediasend_count_button_text);
|
||||||
|
revealButton = findViewById(R.id.mediasend_reveal_toggle);
|
||||||
captionText = findViewById(R.id.mediasend_caption);
|
captionText = findViewById(R.id.mediasend_caption);
|
||||||
emojiToggle = findViewById(R.id.mediasend_emoji_toggle);
|
emojiToggle = findViewById(R.id.mediasend_emoji_toggle);
|
||||||
charactersLeft = findViewById(R.id.mediasend_characters_left);
|
charactersLeft = findViewById(R.id.mediasend_characters_left);
|
||||||
@ -289,6 +291,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
|||||||
.or(recipient.getAddress().serialize()));
|
.or(recipient.getAddress().serialize()));
|
||||||
composeText.setHint(getString(R.string.MediaSendActivity_message_to_s, displayName), null);
|
composeText.setHint(getString(R.string.MediaSendActivity_message_to_s, displayName), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
composeText.setOnEditorActionListener((v, actionId, event) -> {
|
composeText.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
boolean isSend = actionId == EditorInfo.IME_ACTION_SEND;
|
boolean isSend = actionId == EditorInfo.IME_ACTION_SEND;
|
||||||
if (isSend) sendButton.performClick();
|
if (isSend) sendButton.performClick();
|
||||||
@ -302,6 +305,8 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
|||||||
}
|
}
|
||||||
|
|
||||||
initViewModel();
|
initViewModel();
|
||||||
|
|
||||||
|
revealButton.setOnClickListener(v -> viewModel.onRevealButtonToggled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -512,14 +517,14 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
|||||||
if (state == null) return;
|
if (state == null) return;
|
||||||
|
|
||||||
hud.setVisibility(state.isHudVisible() ? View.VISIBLE : View.GONE);
|
hud.setVisibility(state.isHudVisible() ? View.VISIBLE : View.GONE);
|
||||||
composeContainer.setVisibility(state.isComposeVisible() ? View.VISIBLE : (state.getTimerState() == TimerState.GONE ? View.GONE : View.INVISIBLE));
|
composeContainer.setVisibility(state.isComposeVisible() ? View.VISIBLE : (state.getRevealState() == RevealState.GONE ? View.GONE : View.INVISIBLE));
|
||||||
captionText.setVisibility(state.isCaptionVisible() ? View.VISIBLE : View.GONE);
|
captionText.setVisibility(state.isCaptionVisible() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
int captionBackground;
|
int captionBackground;
|
||||||
|
|
||||||
if (state.getRailState() == MediaSendViewModel.RailState.VIEWABLE) {
|
if (state.getRailState() == MediaSendViewModel.RailState.VIEWABLE) {
|
||||||
captionBackground = R.color.core_grey_90;
|
captionBackground = R.color.core_grey_90;
|
||||||
} else if (state.getTimerState() == TimerState.ENABLED) {
|
} else if (state.getRevealState() == RevealState.ENABLED) {
|
||||||
captionBackground = 0;
|
captionBackground = 0;
|
||||||
} else {
|
} else {
|
||||||
captionBackground = R.color.transparent_black_70;
|
captionBackground = R.color.transparent_black_70;
|
||||||
@ -543,6 +548,20 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (state.getRevealState()) {
|
||||||
|
case ENABLED:
|
||||||
|
revealButton.setVisibility(View.VISIBLE);
|
||||||
|
revealButton.setImageResource(R.drawable.ic_view_once_32);
|
||||||
|
break;
|
||||||
|
case DISABLED:
|
||||||
|
revealButton.setVisibility(View.VISIBLE);
|
||||||
|
revealButton.setImageResource(R.drawable.ic_view_infinite_32);
|
||||||
|
break;
|
||||||
|
case GONE:
|
||||||
|
revealButton.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
switch (state.getRailState()) {
|
switch (state.getRailState()) {
|
||||||
case INTERACTIVE:
|
case INTERACTIVE:
|
||||||
mediaRail.setVisibility(View.VISIBLE);
|
mediaRail.setVisibility(View.VISIBLE);
|
||||||
|
@ -17,8 +17,10 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.revealable.RevealableUtil;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
@ -64,27 +66,27 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
private boolean captionVisible;
|
private boolean captionVisible;
|
||||||
private ButtonState buttonState;
|
private ButtonState buttonState;
|
||||||
private RailState railState;
|
private RailState railState;
|
||||||
private TimerState timerState;
|
private RevealState revealState;
|
||||||
|
|
||||||
|
|
||||||
private MediaSendViewModel(@NonNull Application application, @NonNull MediaRepository repository) {
|
private MediaSendViewModel(@NonNull Application application, @NonNull MediaRepository repository) {
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.selectedMedia = new MutableLiveData<>();
|
this.selectedMedia = new MutableLiveData<>();
|
||||||
this.bucketMedia = new MutableLiveData<>();
|
this.bucketMedia = new MutableLiveData<>();
|
||||||
this.mostRecentMedia = new MutableLiveData<>();
|
this.mostRecentMedia = new MutableLiveData<>();
|
||||||
this.position = new MutableLiveData<>();
|
this.position = new MutableLiveData<>();
|
||||||
this.bucketId = new MutableLiveData<>();
|
this.bucketId = new MutableLiveData<>();
|
||||||
this.folders = new MutableLiveData<>();
|
this.folders = new MutableLiveData<>();
|
||||||
this.hudState = new MutableLiveData<>();
|
this.hudState = new MutableLiveData<>();
|
||||||
this.error = new SingleLiveEvent<>();
|
this.error = new SingleLiveEvent<>();
|
||||||
this.savedDrawState = new HashMap<>();
|
this.savedDrawState = new HashMap<>();
|
||||||
this.lastCameraCapture = Optional.absent();
|
this.lastCameraCapture = Optional.absent();
|
||||||
this.body = "";
|
this.body = "";
|
||||||
this.buttonState = ButtonState.GONE;
|
this.buttonState = ButtonState.GONE;
|
||||||
this.railState = RailState.GONE;
|
this.railState = RailState.GONE;
|
||||||
this.timerState = TimerState.GONE;
|
this.revealState = RevealState.GONE;
|
||||||
this.page = Page.UNKNOWN;
|
this.page = Page.UNKNOWN;
|
||||||
|
|
||||||
position.setValue(-1);
|
position.setValue(-1);
|
||||||
}
|
}
|
||||||
@ -171,7 +173,7 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
captionVisible = false;
|
captionVisible = false;
|
||||||
buttonState = ButtonState.COUNT;
|
buttonState = ButtonState.COUNT;
|
||||||
railState = RailState.VIEWABLE;
|
railState = RailState.VIEWABLE;
|
||||||
timerState = TimerState.GONE;
|
revealState = RevealState.GONE;
|
||||||
|
|
||||||
hudState.setValue(buildHudState());
|
hudState.setValue(buildHudState());
|
||||||
}
|
}
|
||||||
@ -179,20 +181,28 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
void onImageEditorStarted() {
|
void onImageEditorStarted() {
|
||||||
page = Page.EDITOR;
|
page = Page.EDITOR;
|
||||||
hudVisible = true;
|
hudVisible = true;
|
||||||
composeVisible = timerState != TimerState.ENABLED;
|
composeVisible = revealState != RevealState.ENABLED;
|
||||||
captionVisible = getSelectedMediaOrDefault().size() > 1 || (getSelectedMediaOrDefault().size() > 0 && getSelectedMediaOrDefault().get(0).getCaption().isPresent());
|
captionVisible = getSelectedMediaOrDefault().size() > 1 || (getSelectedMediaOrDefault().size() > 0 && getSelectedMediaOrDefault().get(0).getCaption().isPresent());
|
||||||
buttonState = ButtonState.SEND;
|
buttonState = ButtonState.SEND;
|
||||||
railState = !isSms ? RailState.INTERACTIVE : RailState.GONE;
|
|
||||||
|
if (revealState == RevealState.GONE && revealSupported()) {
|
||||||
|
revealState = TextSecurePreferences.isRevealableMessageEnabled(application) ? RevealState.ENABLED : RevealState.DISABLED;
|
||||||
|
} else if (!revealSupported()) {
|
||||||
|
revealState = RevealState.GONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
railState = !isSms && revealState != RevealState.ENABLED ? RailState.INTERACTIVE : RailState.GONE;
|
||||||
|
|
||||||
hudState.setValue(buildHudState());
|
hudState.setValue(buildHudState());
|
||||||
}
|
}
|
||||||
|
|
||||||
void onCameraStarted() {
|
void onCameraStarted() {
|
||||||
|
// TODO: Don't need this?
|
||||||
Page previous = page;
|
Page previous = page;
|
||||||
|
|
||||||
page = Page.CAMERA;
|
page = Page.CAMERA;
|
||||||
hudVisible = false;
|
hudVisible = false;
|
||||||
timerState = TimerState.GONE;
|
revealState = RevealState.GONE;
|
||||||
buttonState = ButtonState.COUNT;
|
buttonState = ButtonState.COUNT;
|
||||||
|
|
||||||
List<Media> selected = getSelectedMediaOrDefault();
|
List<Media> selected = getSelectedMediaOrDefault();
|
||||||
@ -212,7 +222,7 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
composeVisible = false;
|
composeVisible = false;
|
||||||
captionVisible = false;
|
captionVisible = false;
|
||||||
buttonState = ButtonState.COUNT;
|
buttonState = ButtonState.COUNT;
|
||||||
timerState = TimerState.GONE;
|
revealState = RevealState.GONE;
|
||||||
railState = getSelectedMediaOrDefault().isEmpty() ? RailState.GONE : RailState.VIEWABLE;
|
railState = getSelectedMediaOrDefault().isEmpty() ? RailState.GONE : RailState.VIEWABLE;
|
||||||
|
|
||||||
lastCameraCapture = Optional.absent();
|
lastCameraCapture = Optional.absent();
|
||||||
@ -226,7 +236,7 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
composeVisible = false;
|
composeVisible = false;
|
||||||
captionVisible = false;
|
captionVisible = false;
|
||||||
buttonState = ButtonState.COUNT;
|
buttonState = ButtonState.COUNT;
|
||||||
timerState = TimerState.GONE;
|
revealState = RevealState.GONE;
|
||||||
railState = getSelectedMediaOrDefault().isEmpty() ? RailState.GONE : RailState.VIEWABLE;
|
railState = getSelectedMediaOrDefault().isEmpty() ? RailState.GONE : RailState.VIEWABLE;
|
||||||
|
|
||||||
lastCameraCapture = Optional.absent();
|
lastCameraCapture = Optional.absent();
|
||||||
@ -234,10 +244,20 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
hudState.setValue(buildHudState());
|
hudState.setValue(buildHudState());
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTimerButtonToggled() {
|
void onRevealButtonToggled() {
|
||||||
hudVisible = true;
|
hudVisible = true;
|
||||||
timerState = (timerState == TimerState.ENABLED) ? TimerState.DISABLED : TimerState.ENABLED;
|
revealState = revealState == RevealState.ENABLED ? RevealState.DISABLED : RevealState.ENABLED;
|
||||||
composeVisible = (timerState != TimerState.ENABLED);
|
composeVisible = revealState != RevealState.ENABLED;
|
||||||
|
railState = revealState == RevealState.ENABLED || isSms ? RailState.GONE : RailState.INTERACTIVE;
|
||||||
|
captionVisible = false;
|
||||||
|
|
||||||
|
List<Media> uncaptioned = Stream.of(getSelectedMediaOrDefault())
|
||||||
|
.map(m -> new Media(m.getUri(), m.getMimeType(), m.getDate(), m.getWidth(), m.getHeight(), m.getSize(), m.getBucketId(), Optional.absent()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
selectedMedia.setValue(uncaptioned);
|
||||||
|
|
||||||
|
TextSecurePreferences.setIsRevealableMessageEnabled(application, revealState == RevealState.ENABLED);
|
||||||
|
|
||||||
hudState.setValue(buildHudState());
|
hudState.setValue(buildHudState());
|
||||||
}
|
}
|
||||||
@ -245,14 +265,14 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
void onKeyboardHidden(boolean isSms) {
|
void onKeyboardHidden(boolean isSms) {
|
||||||
if (page != Page.EDITOR) return;
|
if (page != Page.EDITOR) return;
|
||||||
|
|
||||||
composeVisible = (timerState != TimerState.ENABLED);
|
composeVisible = (revealState != RevealState.ENABLED);
|
||||||
buttonState = ButtonState.SEND;
|
buttonState = ButtonState.SEND;
|
||||||
|
|
||||||
if (isSms) {
|
if (isSms) {
|
||||||
railState = RailState.GONE;
|
railState = RailState.GONE;
|
||||||
captionVisible = false;
|
captionVisible = false;
|
||||||
} else {
|
} else {
|
||||||
railState = RailState.INTERACTIVE;
|
railState = revealState != RevealState.ENABLED ? RailState.INTERACTIVE : RailState.GONE;
|
||||||
|
|
||||||
if (getSelectedMediaOrDefault().size() > 1 || (getSelectedMediaOrDefault().size() > 0 && getSelectedMediaOrDefault().get(0).getCaption().isPresent())) {
|
if (getSelectedMediaOrDefault().size() > 1 || (getSelectedMediaOrDefault().size() > 0 && getSelectedMediaOrDefault().get(0).getCaption().isPresent())) {
|
||||||
captionVisible = true;
|
captionVisible = true;
|
||||||
@ -267,18 +287,18 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
|
|
||||||
if (isSms) {
|
if (isSms) {
|
||||||
railState = RailState.GONE;
|
railState = RailState.GONE;
|
||||||
composeVisible = (timerState == TimerState.GONE);
|
composeVisible = (revealState == RevealState.GONE);
|
||||||
captionVisible = false;
|
captionVisible = false;
|
||||||
buttonState = ButtonState.SEND;
|
buttonState = ButtonState.SEND;
|
||||||
} else {
|
} else {
|
||||||
if (isCaptionFocused) {
|
if (isCaptionFocused) {
|
||||||
railState = RailState.INTERACTIVE;
|
railState = revealState != RevealState.ENABLED ? RailState.INTERACTIVE : RailState.GONE;
|
||||||
composeVisible = false;
|
composeVisible = false;
|
||||||
captionVisible = true;
|
captionVisible = true;
|
||||||
buttonState = ButtonState.GONE;
|
buttonState = ButtonState.GONE;
|
||||||
} else if (isComposeFocused) {
|
} else if (isComposeFocused) {
|
||||||
railState = RailState.INTERACTIVE;
|
railState = revealState != RevealState.ENABLED ? RailState.INTERACTIVE : RailState.GONE;
|
||||||
composeVisible = (timerState != TimerState.ENABLED);
|
composeVisible = (revealState != RevealState.ENABLED);
|
||||||
captionVisible = false;
|
captionVisible = false;
|
||||||
buttonState = ButtonState.SEND;
|
buttonState = ButtonState.SEND;
|
||||||
}
|
}
|
||||||
@ -327,6 +347,10 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
this.position.setValue(Math.min(position, getSelectedMediaOrDefault().size() - 1));
|
this.position.setValue(Math.min(position, getSelectedMediaOrDefault().size() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getSelectedMediaOrDefault().size() == 1) {
|
||||||
|
revealState = revealSupported() ? RevealState.DISABLED : RevealState.GONE;
|
||||||
|
}
|
||||||
|
|
||||||
hudState.setValue(buildHudState());
|
hudState.setValue(buildHudState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,16 +374,6 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
bucketId.setValue(Media.ALL_MEDIA_BUCKET_ID);
|
bucketId.setValue(Media.ALL_MEDIA_BUCKET_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onImageCaptureUndo(@NonNull Context context) {
|
|
||||||
List<Media> selected = getSelectedMediaOrDefault();
|
|
||||||
|
|
||||||
if (lastCameraCapture.isPresent() && selected.contains(lastCameraCapture.get()) && selected.size() == 1) {
|
|
||||||
selected.remove(lastCameraCapture.get());
|
|
||||||
selectedMedia.setValue(selected);
|
|
||||||
BlobProvider.getInstance().delete(context, lastCameraCapture.get().getUri());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onCaptionChanged(@NonNull String newCaption) {
|
void onCaptionChanged(@NonNull String newCaption) {
|
||||||
if (position.getValue() >= 0 && !Util.isEmpty(selectedMedia.getValue())) {
|
if (position.getValue() >= 0 && !Util.isEmpty(selectedMedia.getValue())) {
|
||||||
selectedMedia.getValue().get(position.getValue()).setCaption(TextUtils.isEmpty(newCaption) ? null : newCaption);
|
selectedMedia.getValue().get(position.getValue()).setCaption(TextUtils.isEmpty(newCaption) ? null : newCaption);
|
||||||
@ -426,6 +440,8 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long getRevealDuration() {
|
long getRevealDuration() {
|
||||||
|
// TODO[reveal]
|
||||||
|
// return revealState == RevealState.ENABLED ? RevealableUtil.DURATION : 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,12 +463,14 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private HudState buildHudState() {
|
private HudState buildHudState() {
|
||||||
List<Media> selectedMedia = getSelectedMediaOrDefault();
|
// TODO[reveal]
|
||||||
int selectionCount = selectedMedia.size();
|
RevealState updatedRevealState = RevealState.GONE;
|
||||||
ButtonState updatedButtonState = buttonState == ButtonState.COUNT && selectionCount == 0 ? ButtonState.GONE : buttonState;
|
List<Media> selectedMedia = getSelectedMediaOrDefault();
|
||||||
boolean updatdCaptionVisible = captionVisible && (selectedMedia.size() > 1 || (selectedMedia.size() > 0 && selectedMedia.get(0).getCaption().isPresent()));
|
int selectionCount = selectedMedia.size();
|
||||||
|
ButtonState updatedButtonState = buttonState == ButtonState.COUNT && selectionCount == 0 ? ButtonState.GONE : buttonState;
|
||||||
|
boolean updatedCaptionVisible = captionVisible && (selectedMedia.size() > 1 || (selectedMedia.size() > 0 && selectedMedia.get(0).getCaption().isPresent()));
|
||||||
|
|
||||||
return new HudState(hudVisible, composeVisible, updatdCaptionVisible, selectionCount, updatedButtonState, railState, timerState);
|
return new HudState(hudVisible, composeVisible, updatedCaptionVisible, selectionCount, updatedButtonState, railState, updatedRevealState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearPersistedMedia() {
|
private void clearPersistedMedia() {
|
||||||
@ -462,6 +480,14 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
.forEach(uri -> BlobProvider.getInstance().delete(application.getApplicationContext(), uri));
|
.forEach(uri -> BlobProvider.getInstance().delete(application.getApplicationContext(), uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean revealSupported() {
|
||||||
|
return !isSms && mediaSupportsRevealableMessage(getSelectedMediaOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean mediaSupportsRevealableMessage(@NonNull List<Media> media) {
|
||||||
|
return media.size() == 1 && MediaUtil.isImageType(media.get(0).getMimeType());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCleared() {
|
protected void onCleared() {
|
||||||
if (!sentMedia) {
|
if (!sentMedia) {
|
||||||
@ -485,7 +511,7 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
INTERACTIVE, VIEWABLE, GONE
|
INTERACTIVE, VIEWABLE, GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TimerState {
|
enum RevealState {
|
||||||
ENABLED, DISABLED, GONE
|
ENABLED, DISABLED, GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,7 +523,7 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
private final int selectionCount;
|
private final int selectionCount;
|
||||||
private final ButtonState buttonState;
|
private final ButtonState buttonState;
|
||||||
private final RailState railState;
|
private final RailState railState;
|
||||||
private final TimerState timerState;
|
private final RevealState revealState;
|
||||||
|
|
||||||
HudState(boolean hudVisible,
|
HudState(boolean hudVisible,
|
||||||
boolean composeVisible,
|
boolean composeVisible,
|
||||||
@ -505,7 +531,7 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
int selectionCount,
|
int selectionCount,
|
||||||
@NonNull ButtonState buttonState,
|
@NonNull ButtonState buttonState,
|
||||||
@NonNull RailState railState,
|
@NonNull RailState railState,
|
||||||
@NonNull TimerState timerState)
|
@NonNull RevealState revealState)
|
||||||
{
|
{
|
||||||
this.hudVisible = hudVisible;
|
this.hudVisible = hudVisible;
|
||||||
this.composeVisible = composeVisible;
|
this.composeVisible = composeVisible;
|
||||||
@ -513,7 +539,7 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
this.selectionCount = selectionCount;
|
this.selectionCount = selectionCount;
|
||||||
this.buttonState = buttonState;
|
this.buttonState = buttonState;
|
||||||
this.railState = railState;
|
this.railState = railState;
|
||||||
this.timerState = timerState;
|
this.revealState = revealState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHudVisible() {
|
public boolean isHudVisible() {
|
||||||
@ -540,8 +566,9 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
return hudVisible ? railState : RailState.GONE;
|
return hudVisible ? railState : RailState.GONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull TimerState getTimerState() {
|
public @NonNull
|
||||||
return hudVisible ? timerState : TimerState.GONE;
|
RevealState getRevealState() {
|
||||||
|
return hudVisible ? revealState : RevealState.GONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ public class IncomingMediaMessage {
|
|||||||
private final int subscriptionId;
|
private final int subscriptionId;
|
||||||
private final long expiresIn;
|
private final long expiresIn;
|
||||||
private final boolean expirationUpdate;
|
private final boolean expirationUpdate;
|
||||||
|
private final long revealDuration;
|
||||||
private final QuoteModel quote;
|
private final QuoteModel quote;
|
||||||
private final boolean unidentified;
|
private final boolean unidentified;
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ public class IncomingMediaMessage {
|
|||||||
int subscriptionId,
|
int subscriptionId,
|
||||||
long expiresIn,
|
long expiresIn,
|
||||||
boolean expirationUpdate,
|
boolean expirationUpdate,
|
||||||
|
long revealDuration,
|
||||||
boolean unidentified)
|
boolean unidentified)
|
||||||
{
|
{
|
||||||
this.from = from;
|
this.from = from;
|
||||||
@ -49,6 +51,7 @@ public class IncomingMediaMessage {
|
|||||||
this.subscriptionId = subscriptionId;
|
this.subscriptionId = subscriptionId;
|
||||||
this.expiresIn = expiresIn;
|
this.expiresIn = expiresIn;
|
||||||
this.expirationUpdate = expirationUpdate;
|
this.expirationUpdate = expirationUpdate;
|
||||||
|
this.revealDuration = revealDuration;
|
||||||
this.quote = null;
|
this.quote = null;
|
||||||
this.unidentified = unidentified;
|
this.unidentified = unidentified;
|
||||||
|
|
||||||
@ -60,6 +63,7 @@ public class IncomingMediaMessage {
|
|||||||
int subscriptionId,
|
int subscriptionId,
|
||||||
long expiresIn,
|
long expiresIn,
|
||||||
boolean expirationUpdate,
|
boolean expirationUpdate,
|
||||||
|
long revealDuration,
|
||||||
boolean unidentified,
|
boolean unidentified,
|
||||||
Optional<String> body,
|
Optional<String> body,
|
||||||
Optional<SignalServiceGroup> group,
|
Optional<SignalServiceGroup> group,
|
||||||
@ -76,6 +80,7 @@ public class IncomingMediaMessage {
|
|||||||
this.subscriptionId = subscriptionId;
|
this.subscriptionId = subscriptionId;
|
||||||
this.expiresIn = expiresIn;
|
this.expiresIn = expiresIn;
|
||||||
this.expirationUpdate = expirationUpdate;
|
this.expirationUpdate = expirationUpdate;
|
||||||
|
this.revealDuration = revealDuration;
|
||||||
this.quote = quote.orNull();
|
this.quote = quote.orNull();
|
||||||
this.unidentified = unidentified;
|
this.unidentified = unidentified;
|
||||||
|
|
||||||
@ -127,6 +132,10 @@ public class IncomingMediaMessage {
|
|||||||
return expiresIn;
|
return expiresIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getRevealDuration() {
|
||||||
|
return revealDuration;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isGroupMessage() {
|
public boolean isGroupMessage() {
|
||||||
return groupId != null;
|
return groupId != null;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage
|
|||||||
|
|
||||||
public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) {
|
public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) {
|
||||||
super(recipient, "", new LinkedList<Attachment>(), sentTimeMillis,
|
super(recipient, "", new LinkedList<Attachment>(), sentTimeMillis,
|
||||||
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, null, Collections.emptyList(),
|
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, 0, null, Collections.emptyList(),
|
||||||
Collections.emptyList());
|
Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,13 +24,14 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
|||||||
@NonNull List<Attachment> avatar,
|
@NonNull List<Attachment> avatar,
|
||||||
long sentTimeMillis,
|
long sentTimeMillis,
|
||||||
long expiresIn,
|
long expiresIn,
|
||||||
|
long revealDuration,
|
||||||
@Nullable QuoteModel quote,
|
@Nullable QuoteModel quote,
|
||||||
@NonNull List<Contact> contacts,
|
@NonNull List<Contact> contacts,
|
||||||
@NonNull List<LinkPreview> previews)
|
@NonNull List<LinkPreview> previews)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
super(recipient, encodedGroupContext, avatar, sentTimeMillis,
|
super(recipient, encodedGroupContext, avatar, sentTimeMillis,
|
||||||
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, quote, contacts, previews);
|
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, revealDuration, quote, contacts, previews);
|
||||||
|
|
||||||
this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext));
|
this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext));
|
||||||
}
|
}
|
||||||
@ -40,6 +41,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
|||||||
@Nullable final Attachment avatar,
|
@Nullable final Attachment avatar,
|
||||||
long sentTimeMillis,
|
long sentTimeMillis,
|
||||||
long expireIn,
|
long expireIn,
|
||||||
|
long revealDuration,
|
||||||
@Nullable QuoteModel quote,
|
@Nullable QuoteModel quote,
|
||||||
@NonNull List<Contact> contacts,
|
@NonNull List<Contact> contacts,
|
||||||
@NonNull List<LinkPreview> previews)
|
@NonNull List<LinkPreview> previews)
|
||||||
@ -47,7 +49,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
|||||||
super(recipient, Base64.encodeBytes(group.toByteArray()),
|
super(recipient, Base64.encodeBytes(group.toByteArray()),
|
||||||
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
|
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
ThreadDatabase.DistributionTypes.CONVERSATION, expireIn, quote, contacts, previews);
|
ThreadDatabase.DistributionTypes.CONVERSATION, expireIn, revealDuration, quote, contacts, previews);
|
||||||
|
|
||||||
this.group = group;
|
this.group = group;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ public class OutgoingMediaMessage {
|
|||||||
private final int distributionType;
|
private final int distributionType;
|
||||||
private final int subscriptionId;
|
private final int subscriptionId;
|
||||||
private final long expiresIn;
|
private final long expiresIn;
|
||||||
|
private final long revealDuration;
|
||||||
private final QuoteModel outgoingQuote;
|
private final QuoteModel outgoingQuote;
|
||||||
|
|
||||||
private final List<NetworkFailure> networkFailures = new LinkedList<>();
|
private final List<NetworkFailure> networkFailures = new LinkedList<>();
|
||||||
@ -32,7 +33,7 @@ public class OutgoingMediaMessage {
|
|||||||
|
|
||||||
public OutgoingMediaMessage(Recipient recipient, String message,
|
public OutgoingMediaMessage(Recipient recipient, String message,
|
||||||
List<Attachment> attachments, long sentTimeMillis,
|
List<Attachment> attachments, long sentTimeMillis,
|
||||||
int subscriptionId, long expiresIn,
|
int subscriptionId, long expiresIn, long revealDuration,
|
||||||
int distributionType,
|
int distributionType,
|
||||||
@Nullable QuoteModel outgoingQuote,
|
@Nullable QuoteModel outgoingQuote,
|
||||||
@NonNull List<Contact> contacts,
|
@NonNull List<Contact> contacts,
|
||||||
@ -47,6 +48,7 @@ public class OutgoingMediaMessage {
|
|||||||
this.attachments = attachments;
|
this.attachments = attachments;
|
||||||
this.subscriptionId = subscriptionId;
|
this.subscriptionId = subscriptionId;
|
||||||
this.expiresIn = expiresIn;
|
this.expiresIn = expiresIn;
|
||||||
|
this.revealDuration = revealDuration;
|
||||||
this.outgoingQuote = outgoingQuote;
|
this.outgoingQuote = outgoingQuote;
|
||||||
|
|
||||||
this.contacts.addAll(contacts);
|
this.contacts.addAll(contacts);
|
||||||
@ -57,7 +59,8 @@ public class OutgoingMediaMessage {
|
|||||||
|
|
||||||
public OutgoingMediaMessage(Recipient recipient, SlideDeck slideDeck, String message,
|
public OutgoingMediaMessage(Recipient recipient, SlideDeck slideDeck, String message,
|
||||||
long sentTimeMillis, int subscriptionId, long expiresIn,
|
long sentTimeMillis, int subscriptionId, long expiresIn,
|
||||||
int distributionType, @Nullable QuoteModel outgoingQuote,
|
long revealDuration, int distributionType,
|
||||||
|
@Nullable QuoteModel outgoingQuote,
|
||||||
@NonNull List<Contact> contacts,
|
@NonNull List<Contact> contacts,
|
||||||
@NonNull List<LinkPreview> linkPreviews)
|
@NonNull List<LinkPreview> linkPreviews)
|
||||||
{
|
{
|
||||||
@ -65,7 +68,7 @@ public class OutgoingMediaMessage {
|
|||||||
buildMessage(slideDeck, message),
|
buildMessage(slideDeck, message),
|
||||||
slideDeck.asAttachments(),
|
slideDeck.asAttachments(),
|
||||||
sentTimeMillis, subscriptionId,
|
sentTimeMillis, subscriptionId,
|
||||||
expiresIn, distributionType, outgoingQuote,
|
expiresIn, revealDuration, distributionType, outgoingQuote,
|
||||||
contacts, linkPreviews, new LinkedList<>(), new LinkedList<>());
|
contacts, linkPreviews, new LinkedList<>(), new LinkedList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +80,7 @@ public class OutgoingMediaMessage {
|
|||||||
this.sentTimeMillis = that.sentTimeMillis;
|
this.sentTimeMillis = that.sentTimeMillis;
|
||||||
this.subscriptionId = that.subscriptionId;
|
this.subscriptionId = that.subscriptionId;
|
||||||
this.expiresIn = that.expiresIn;
|
this.expiresIn = that.expiresIn;
|
||||||
|
this.revealDuration = that.revealDuration;
|
||||||
this.outgoingQuote = that.outgoingQuote;
|
this.outgoingQuote = that.outgoingQuote;
|
||||||
|
|
||||||
this.identityKeyMismatches.addAll(that.identityKeyMismatches);
|
this.identityKeyMismatches.addAll(that.identityKeyMismatches);
|
||||||
@ -125,6 +129,10 @@ public class OutgoingMediaMessage {
|
|||||||
return expiresIn;
|
return expiresIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getRevealDuration() {
|
||||||
|
return revealDuration;
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable QuoteModel getOutgoingQuote() {
|
public @Nullable QuoteModel getOutgoingQuote() {
|
||||||
return outgoingQuote;
|
return outgoingQuote;
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,12 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
|
|||||||
long sentTimeMillis,
|
long sentTimeMillis,
|
||||||
int distributionType,
|
int distributionType,
|
||||||
long expiresIn,
|
long expiresIn,
|
||||||
|
long revealDuration,
|
||||||
@Nullable QuoteModel quote,
|
@Nullable QuoteModel quote,
|
||||||
@NonNull List<Contact> contacts,
|
@NonNull List<Contact> contacts,
|
||||||
@NonNull List<LinkPreview> previews)
|
@NonNull List<LinkPreview> previews)
|
||||||
{
|
{
|
||||||
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList());
|
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, revealDuration, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {
|
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {
|
||||||
|
@ -76,7 +76,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver {
|
|||||||
|
|
||||||
if (recipient.isGroupRecipient()) {
|
if (recipient.isGroupRecipient()) {
|
||||||
Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message");
|
Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message");
|
||||||
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
|
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
|
||||||
replyThreadId = MessageSender.send(context, reply, threadId, false, null);
|
replyThreadId = MessageSender.send(context, reply, threadId, false, null);
|
||||||
} else {
|
} else {
|
||||||
Log.w("AndroidAutoReplyReceiver", "Sending regular message ");
|
Log.w("AndroidAutoReplyReceiver", "Sending regular message ");
|
||||||
|
@ -465,6 +465,9 @@ public class MessageNotifier {
|
|||||||
} else if (record.isMms() && ((MmsMessageRecord) record).getSlideDeck().getStickerSlide() != null) {
|
} else if (record.isMms() && ((MmsMessageRecord) record).getSlideDeck().getStickerSlide() != null) {
|
||||||
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_sticker));
|
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_sticker));
|
||||||
slideDeck = ((MmsMessageRecord) record).getSlideDeck();
|
slideDeck = ((MmsMessageRecord) record).getSlideDeck();
|
||||||
|
} else if (record.isMms() && ((MmsMessageRecord) record).getRevealDuration() > 0) {
|
||||||
|
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_disappearing_photo));
|
||||||
|
slideDeck = ((MmsMessageRecord) record).getSlideDeck();
|
||||||
} else if (record.isMms() && TextUtils.isEmpty(body) && !((MmsMessageRecord) record).getSlideDeck().getSlides().isEmpty()) {
|
} else if (record.isMms() && TextUtils.isEmpty(body) && !((MmsMessageRecord) record).getSlideDeck().getSlides().isEmpty()) {
|
||||||
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message));
|
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message));
|
||||||
slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
|
slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
|
||||||
|
@ -76,7 +76,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
|
|||||||
|
|
||||||
switch (replyMethod) {
|
switch (replyMethod) {
|
||||||
case GroupMessage: {
|
case GroupMessage: {
|
||||||
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
|
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
|
||||||
threadId = MessageSender.send(context, reply, -1, false, null);
|
threadId = MessageSender.send(context, reply, -1, false, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package org.thoughtcrime.securesms.revealable;
|
||||||
|
|
||||||
|
public class RevealExpirationInfo {
|
||||||
|
|
||||||
|
private final long messageId;
|
||||||
|
private final long receiveTime;
|
||||||
|
private final long revealStartTime;
|
||||||
|
private final long revealDuration;
|
||||||
|
|
||||||
|
public RevealExpirationInfo(long messageId, long receiveTime, long revealStartTime, long revealDuration) {
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.receiveTime = receiveTime;
|
||||||
|
this.revealStartTime = revealStartTime;
|
||||||
|
this.revealDuration = revealDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMessageId() {
|
||||||
|
return messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getReceiveTime() {
|
||||||
|
return receiveTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRevealStartTime() {
|
||||||
|
return revealStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRevealDuration() {
|
||||||
|
return revealDuration;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package org.thoughtcrime.securesms.revealable;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
|
||||||
|
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
public class RevealableMessageActivity extends PassphraseRequiredActionBarActivity {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(RevealableMessageActivity.class);
|
||||||
|
|
||||||
|
private static final String KEY_MESSAGE_ID = "message_id";
|
||||||
|
|
||||||
|
private ImageView image;
|
||||||
|
private View closeButton;
|
||||||
|
private RevealableMessageViewModel viewModel;
|
||||||
|
|
||||||
|
public static Intent getIntent(@NonNull Context context, long messageId) {
|
||||||
|
Intent intent = new Intent(context, RevealableMessageActivity.class);
|
||||||
|
intent.putExtra(KEY_MESSAGE_ID, messageId);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
|
super.onCreate(savedInstanceState, ready);
|
||||||
|
setContentView(R.layout.revealable_message_activity);
|
||||||
|
|
||||||
|
this.image = findViewById(R.id.reveal_image);
|
||||||
|
this.closeButton = findViewById(R.id.reveal_close_button);
|
||||||
|
|
||||||
|
image.setOnClickListener(v -> finish());
|
||||||
|
closeButton.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
|
initViewModel(getIntent().getLongExtra(KEY_MESSAGE_ID, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViewModel(long messageId) {
|
||||||
|
RevealableMessageRepository repository = new RevealableMessageRepository(this);
|
||||||
|
viewModel = ViewModelProviders.of(this, new RevealableMessageViewModel.Factory(getApplication(), messageId, repository))
|
||||||
|
.get(RevealableMessageViewModel.class);
|
||||||
|
|
||||||
|
viewModel.getMessage().observe(this, (message) -> {
|
||||||
|
if (message == null) return;
|
||||||
|
|
||||||
|
if (message.isPresent()) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
GlideApp.with(this)
|
||||||
|
.load(new DecryptableUri(message.get().getSlideDeck().getThumbnailSlide().getUri()))
|
||||||
|
.into(image);
|
||||||
|
} else {
|
||||||
|
image.setImageDrawable(null);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package org.thoughtcrime.securesms.revealable;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import androidx.annotation.AnyThread;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.service.TimedEventManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages clearing removable message content after they're opened.
|
||||||
|
*/
|
||||||
|
public class RevealableMessageManager extends TimedEventManager<RevealExpirationInfo> {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(RevealableMessageManager.class);
|
||||||
|
|
||||||
|
private final MmsDatabase mmsDatabase;
|
||||||
|
private final AttachmentDatabase attachmentDatabase;
|
||||||
|
|
||||||
|
public RevealableMessageManager(@NonNull Application application) {
|
||||||
|
super(application, "RevealableMessageManager");
|
||||||
|
|
||||||
|
this.mmsDatabase = DatabaseFactory.getMmsDatabase(application);
|
||||||
|
this.attachmentDatabase = DatabaseFactory.getAttachmentDatabase(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
@Override
|
||||||
|
protected @Nullable RevealExpirationInfo getNextClosestEvent() {
|
||||||
|
RevealExpirationInfo expirationInfo = mmsDatabase.getNearestExpiringRevealableMessage();
|
||||||
|
|
||||||
|
if (expirationInfo != null) {
|
||||||
|
Log.i(TAG, "Next closest expiration is in " + getDelayForEvent(expirationInfo) + " ms for messsage " + expirationInfo.getMessageId() + ".");
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "No messages to schedule.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expirationInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
@Override
|
||||||
|
protected void executeEvent(@NonNull RevealExpirationInfo event) {
|
||||||
|
Log.i(TAG, "Deleting attachments for message " + event.getMessageId());
|
||||||
|
attachmentDatabase.deleteAttachmentFilesForMessage(event.getMessageId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
@Override
|
||||||
|
protected long getDelayForEvent(@NonNull RevealExpirationInfo event) {
|
||||||
|
if (event.getRevealStartTime() == 0) {
|
||||||
|
return event.getReceiveTime() + RevealableUtil.MAX_LIFESPAN;
|
||||||
|
} else {
|
||||||
|
long timeSinceStart = System.currentTimeMillis() - event.getRevealStartTime();
|
||||||
|
long timeLeft = event.getRevealDuration() - timeSinceStart;
|
||||||
|
|
||||||
|
return Math.max(0, timeLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
@Override
|
||||||
|
protected void scheduleAlarm(@NonNull Application application, long delay) {
|
||||||
|
setAlarm(application, delay, RevealAlarm.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RevealAlarm extends BroadcastReceiver {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(RevealAlarm.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Log.d(TAG, "onReceive()");
|
||||||
|
ApplicationContext.getInstance(context).getRevealableMessageManager().scheduleIfNecessary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package org.thoughtcrime.securesms.revealable;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
class RevealableMessageRepository {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(RevealableMessageRepository.class);
|
||||||
|
|
||||||
|
private final MmsDatabase mmsDatabase;
|
||||||
|
|
||||||
|
RevealableMessageRepository(@NonNull Context context) {
|
||||||
|
this.mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getMessage(long messageId, @NonNull Callback<Optional<MmsMessageRecord>> callback) {
|
||||||
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
|
try (MmsDatabase.Reader reader = mmsDatabase.readerFor(mmsDatabase.getMessage(messageId))) {
|
||||||
|
MmsMessageRecord record = (MmsMessageRecord) reader.getNext();
|
||||||
|
callback.onComplete(Optional.fromNullable(record));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback<T> {
|
||||||
|
void onComplete(T result);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,174 @@
|
|||||||
|
package org.thoughtcrime.securesms.revealable;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
public class RevealableMessageView extends LinearLayout {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(RevealableMessageView.class);
|
||||||
|
|
||||||
|
private ImageView icon;
|
||||||
|
private ProgressWheel progress;
|
||||||
|
private TextView text;
|
||||||
|
private Handler handler;
|
||||||
|
private Runnable updateRunnable;
|
||||||
|
private Attachment attachment;
|
||||||
|
private int unopenedForegroundColor;
|
||||||
|
private int openedForegroundColor;
|
||||||
|
private int foregroundColor;
|
||||||
|
|
||||||
|
public RevealableMessageView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RevealableMessageView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(@Nullable AttributeSet attrs) {
|
||||||
|
inflate(getContext(), R.layout.revealable_message_view, this);
|
||||||
|
setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
|
||||||
|
if (attrs != null) {
|
||||||
|
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.RevealableMessageView, 0, 0);
|
||||||
|
|
||||||
|
unopenedForegroundColor = typedArray.getColor(R.styleable.RevealableMessageView_revealable_unopenedForegroundColor, Color.BLACK);
|
||||||
|
openedForegroundColor = typedArray.getColor(R.styleable.RevealableMessageView_revealable_openedForegroundColor, Color.BLACK);
|
||||||
|
|
||||||
|
typedArray.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.icon = findViewById(R.id.revealable_icon);
|
||||||
|
this.progress = findViewById(R.id.revealable_progress);
|
||||||
|
this.text = findViewById(R.id.revealable_text);
|
||||||
|
this.handler = new Handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
if (!EventBus.getDefault().isRegistered(this)) {
|
||||||
|
EventBus.getDefault().register(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
EventBus.getDefault().unregister(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requiresTapToDownload(@NonNull MmsMessageRecord messageRecord) {
|
||||||
|
if (messageRecord.isOutgoing() || messageRecord.getSlideDeck().getThumbnailSlide() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Attachment attachment = messageRecord.getSlideDeck().getThumbnailSlide().asAttachment();
|
||||||
|
return attachment.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_FAILED ||
|
||||||
|
attachment.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(@NonNull MmsMessageRecord message) {
|
||||||
|
this.attachment = message.getSlideDeck().getThumbnailSlide() != null ? message.getSlideDeck().getThumbnailSlide().asAttachment() : null;
|
||||||
|
|
||||||
|
clearUpdateRunnable();
|
||||||
|
presentMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void presentMessage(@NonNull MmsMessageRecord message) {
|
||||||
|
presentText(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentText(@NonNull MmsMessageRecord messageRecord) {
|
||||||
|
if (downloadInProgress(messageRecord) && messageRecord.isOutgoing()) {
|
||||||
|
foregroundColor = unopenedForegroundColor;
|
||||||
|
text.setText(R.string.RevealableMessageView_view_photo);
|
||||||
|
icon.setImageResource(0);
|
||||||
|
progress.setVisibility(VISIBLE);
|
||||||
|
} else if (downloadInProgress(messageRecord)) {
|
||||||
|
foregroundColor = unopenedForegroundColor;
|
||||||
|
text.setText("");
|
||||||
|
icon.setImageResource(0);
|
||||||
|
progress.setVisibility(VISIBLE);
|
||||||
|
} else if (requiresTapToDownload(messageRecord)) {
|
||||||
|
foregroundColor = unopenedForegroundColor;
|
||||||
|
text.setText(formatFileSize(messageRecord));
|
||||||
|
icon.setImageResource(R.drawable.ic_arrow_down_circle_outline_24);
|
||||||
|
progress.setVisibility(GONE);
|
||||||
|
} else if (RevealableUtil.isViewable(messageRecord)) {
|
||||||
|
foregroundColor = unopenedForegroundColor;
|
||||||
|
text.setText(R.string.RevealableMessageView_view_photo);
|
||||||
|
icon.setImageResource(R.drawable.ic_play_solid_24);
|
||||||
|
progress.setVisibility(GONE);
|
||||||
|
} else if (messageRecord.isOutgoing()) {
|
||||||
|
foregroundColor = openedForegroundColor;
|
||||||
|
text.setText(R.string.RevealableMessageView_photo);
|
||||||
|
icon.setImageResource(R.drawable.ic_play_outline_24);
|
||||||
|
progress.setVisibility(GONE);
|
||||||
|
} else {
|
||||||
|
foregroundColor = openedForegroundColor;
|
||||||
|
text.setText(R.string.RevealableMessageView_viewed);
|
||||||
|
icon.setImageResource(R.drawable.ic_play_outline_24);
|
||||||
|
progress.setVisibility(GONE);
|
||||||
|
clearUpdateRunnable();
|
||||||
|
}
|
||||||
|
|
||||||
|
text.setTextColor(foregroundColor);
|
||||||
|
icon.setColorFilter(foregroundColor);
|
||||||
|
progress.setBarColor(foregroundColor);
|
||||||
|
progress.setRimColor(Color.TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean downloadInProgress(@NonNull MmsMessageRecord messageRecord) {
|
||||||
|
if (messageRecord.getSlideDeck().getThumbnailSlide() == null) return false;
|
||||||
|
|
||||||
|
Attachment attachment = messageRecord.getSlideDeck().getThumbnailSlide().asAttachment();
|
||||||
|
return attachment.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearUpdateRunnable() {
|
||||||
|
if (updateRunnable != null) {
|
||||||
|
handler.removeCallbacks(updateRunnable);
|
||||||
|
updateRunnable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull String formatFileSize(@NonNull MmsMessageRecord messageRecord) {
|
||||||
|
if (messageRecord.getSlideDeck().getThumbnailSlide() == null) return "";
|
||||||
|
|
||||||
|
long size = messageRecord.getSlideDeck().getThumbnailSlide().getFileSize();
|
||||||
|
return Util.getPrettyFileSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
|
public void onEventAsync(final PartProgressEvent event) {
|
||||||
|
if (event.attachment.equals(attachment)) {
|
||||||
|
progress.setInstantProgress((float) event.progress / (float) event.total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package org.thoughtcrime.securesms.revealable;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
class RevealableMessageViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(RevealableMessageViewModel.class);
|
||||||
|
|
||||||
|
private final Application application;
|
||||||
|
private final RevealableMessageRepository repository;
|
||||||
|
private final MutableLiveData<Optional<MmsMessageRecord>> message;
|
||||||
|
private final ContentObserver observer;
|
||||||
|
|
||||||
|
private RevealableMessageViewModel(@NonNull Application application,
|
||||||
|
long messageId,
|
||||||
|
@NonNull RevealableMessageRepository repository)
|
||||||
|
{
|
||||||
|
this.application = application;
|
||||||
|
this.repository = repository;
|
||||||
|
this.message = new MutableLiveData<>();
|
||||||
|
this.observer = new ContentObserver(new Handler()) {
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
repository.getMessage(messageId, optionalMessage -> onMessageRetrieved(optionalMessage));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
repository.getMessage(messageId, message -> {
|
||||||
|
if (message.isPresent()) {
|
||||||
|
Uri uri = DatabaseContentProviders.Conversation.getUriForThread(message.get().getThreadId());
|
||||||
|
application.getContentResolver().registerContentObserver(uri, true, observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageRetrieved(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull LiveData<Optional<MmsMessageRecord>> getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
application.getContentResolver().unregisterContentObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMessageRetrieved(@NonNull Optional<MmsMessageRecord> optionalMessage) {
|
||||||
|
Util.runOnMain(() -> {
|
||||||
|
MmsMessageRecord current = message.getValue() != null ? message.getValue().orNull() : null;
|
||||||
|
MmsMessageRecord proposed = optionalMessage.orNull();
|
||||||
|
|
||||||
|
if (current != null && proposed != null && current.getId() == proposed.getId()) {
|
||||||
|
Log.d(TAG, "Same ID -- skipping update");
|
||||||
|
} else {
|
||||||
|
message.setValue(optionalMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
||||||
|
|
||||||
|
private final Application application;
|
||||||
|
private final long messageId;
|
||||||
|
private final RevealableMessageRepository repository;
|
||||||
|
|
||||||
|
Factory(@NonNull Application application,
|
||||||
|
long messageId,
|
||||||
|
@NonNull RevealableMessageRepository repository)
|
||||||
|
{
|
||||||
|
this.application = application;
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
return modelClass.cast(new RevealableMessageViewModel(application, messageId, repository));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package org.thoughtcrime.securesms.revealable;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class RevealableUtil {
|
||||||
|
|
||||||
|
public static final long MAX_LIFESPAN = TimeUnit.DAYS.toMillis(30);
|
||||||
|
public static final long DURATION = TimeUnit.SECONDS.toMillis(5);
|
||||||
|
|
||||||
|
public static boolean isViewable(@Nullable MmsMessageRecord message) {
|
||||||
|
if (message.getRevealDuration() == 0) {
|
||||||
|
return true;
|
||||||
|
} else if (message.getSlideDeck().getThumbnailSlide() == null) {
|
||||||
|
return false;
|
||||||
|
} else if (message.getSlideDeck().getThumbnailSlide().getUri() == null) {
|
||||||
|
return false;
|
||||||
|
} else if (message.isOutgoing() && message.getSlideDeck().getThumbnailSlide().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) {
|
||||||
|
return true;
|
||||||
|
} else if (message.getSlideDeck().getThumbnailSlide().getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE) {
|
||||||
|
return false;
|
||||||
|
} else if (isRevealExpired(message)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRevealExpired(@Nullable MmsMessageRecord message) {
|
||||||
|
if (message == null) {
|
||||||
|
return false;
|
||||||
|
} else if (message.getRevealDuration() == 0) {
|
||||||
|
return false;
|
||||||
|
} else if (message.getDateReceived() + MAX_LIFESPAN < System.currentTimeMillis()) {
|
||||||
|
return true;
|
||||||
|
} else if (message.getRevealStartTime() == 0) {
|
||||||
|
return false;
|
||||||
|
} else if (message.getRevealStartTime() + message.getRevealDuration() < System.currentTimeMillis()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasStarted(@Nullable MmsMessageRecord record) {
|
||||||
|
return record != null && record.getRevealStartTime() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasMedia(@Nullable MmsMessageRecord record) {
|
||||||
|
return record != null &&
|
||||||
|
record.getSlideDeck().getThumbnailSlide() != null &&
|
||||||
|
record.getSlideDeck().getThumbnailSlide().getUri() != null &&
|
||||||
|
record.getSlideDeck().getThumbnailSlide().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package org.thoughtcrime.securesms.service;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
|
||||||
|
import androidx.annotation.AnyThread;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to help manage scheduling events to happen in the future, whether the app is open or not.
|
||||||
|
*/
|
||||||
|
public abstract class TimedEventManager<E> {
|
||||||
|
|
||||||
|
private final Application application;
|
||||||
|
private final Handler handler;
|
||||||
|
|
||||||
|
public TimedEventManager(@NonNull Application application, @NonNull String threadName) {
|
||||||
|
HandlerThread handlerThread = new HandlerThread(threadName);
|
||||||
|
handlerThread.start();
|
||||||
|
|
||||||
|
this.application = application;
|
||||||
|
this.handler = new Handler(handlerThread.getLooper());
|
||||||
|
|
||||||
|
scheduleIfNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be called whenever the underlying data of events has changed. Will appropriately
|
||||||
|
* schedule new event executions.
|
||||||
|
*/
|
||||||
|
public void scheduleIfNecessary() {
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
|
||||||
|
handler.post(() -> {
|
||||||
|
E event = getNextClosestEvent();
|
||||||
|
|
||||||
|
if (event != null) {
|
||||||
|
long delay = getDelayForEvent(event);
|
||||||
|
|
||||||
|
handler.postDelayed(() -> {
|
||||||
|
executeEvent(event);
|
||||||
|
scheduleIfNecessary();
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
scheduleAlarm(application, delay);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The next event that should be executed, or {@code null} if there are no events to execute.
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
protected @Nullable abstract E getNextClosestEvent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the provided event.
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
protected abstract void executeEvent(@NonNull E event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return How long before the provided event should be executed.
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
protected abstract long getDelayForEvent(@NonNull E event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules an alarm to call {@link #scheduleIfNecessary()} after the specified delay. You can
|
||||||
|
* use {@link #setAlarm(Context, long, Class)} as a helper method.
|
||||||
|
*/
|
||||||
|
@AnyThread
|
||||||
|
protected abstract void scheduleAlarm(@NonNull Application application, long delay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to set an alarm.
|
||||||
|
*/
|
||||||
|
protected static void setAlarm(@NonNull Context context, long delay, @NonNull Class alarmClass) {
|
||||||
|
Intent intent = new Intent(context, alarmClass);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
|
||||||
|
AlarmManager alarmManager = ServiceUtil.getAlarmManager(context);
|
||||||
|
|
||||||
|
alarmManager.cancel(pendingIntent);
|
||||||
|
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay, pendingIntent);
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
|||||||
public class IncomingJoinedMessage extends IncomingTextMessage {
|
public class IncomingJoinedMessage extends IncomingTextMessage {
|
||||||
|
|
||||||
public IncomingJoinedMessage(Address sender) {
|
public IncomingJoinedMessage(Address sender) {
|
||||||
super(sender, 1, System.currentTimeMillis(), null, Optional.<SignalServiceGroup>absent(), 0, false);
|
super(sender, 1, System.currentTimeMillis(), null, Optional.<SignalServiceGroup>absent(), 0, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -42,6 +42,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
private final boolean push;
|
private final boolean push;
|
||||||
private final int subscriptionId;
|
private final int subscriptionId;
|
||||||
private final long expiresInMillis;
|
private final long expiresInMillis;
|
||||||
|
private final long revealDuration;
|
||||||
private final boolean unidentified;
|
private final boolean unidentified;
|
||||||
|
|
||||||
public IncomingTextMessage(@NonNull Context context, @NonNull SmsMessage message, int subscriptionId) {
|
public IncomingTextMessage(@NonNull Context context, @NonNull SmsMessage message, int subscriptionId) {
|
||||||
@ -55,6 +56,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
this.sentTimestampMillis = message.getTimestampMillis();
|
this.sentTimestampMillis = message.getTimestampMillis();
|
||||||
this.subscriptionId = subscriptionId;
|
this.subscriptionId = subscriptionId;
|
||||||
this.expiresInMillis = 0;
|
this.expiresInMillis = 0;
|
||||||
|
this.revealDuration = 0;
|
||||||
this.groupId = null;
|
this.groupId = null;
|
||||||
this.push = false;
|
this.push = false;
|
||||||
this.unidentified = false;
|
this.unidentified = false;
|
||||||
@ -62,7 +64,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
|
|
||||||
public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis,
|
public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis,
|
||||||
String encodedBody, Optional<SignalServiceGroup> group,
|
String encodedBody, Optional<SignalServiceGroup> group,
|
||||||
long expiresInMillis, boolean unidentified)
|
long expiresInMillis, long revealDuration, boolean unidentified)
|
||||||
{
|
{
|
||||||
this.message = encodedBody;
|
this.message = encodedBody;
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
@ -75,6 +77,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
this.push = true;
|
this.push = true;
|
||||||
this.subscriptionId = -1;
|
this.subscriptionId = -1;
|
||||||
this.expiresInMillis = expiresInMillis;
|
this.expiresInMillis = expiresInMillis;
|
||||||
|
this.revealDuration = revealDuration;
|
||||||
this.unidentified = unidentified;
|
this.unidentified = unidentified;
|
||||||
|
|
||||||
if (group.isPresent()) {
|
if (group.isPresent()) {
|
||||||
@ -97,6 +100,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
this.push = (in.readInt() == 1);
|
this.push = (in.readInt() == 1);
|
||||||
this.subscriptionId = in.readInt();
|
this.subscriptionId = in.readInt();
|
||||||
this.expiresInMillis = in.readLong();
|
this.expiresInMillis = in.readLong();
|
||||||
|
this.revealDuration = in.readLong();
|
||||||
this.unidentified = in.readInt() == 1;
|
this.unidentified = in.readInt() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +117,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
this.push = base.isPush();
|
this.push = base.isPush();
|
||||||
this.subscriptionId = base.getSubscriptionId();
|
this.subscriptionId = base.getSubscriptionId();
|
||||||
this.expiresInMillis = base.getExpiresIn();
|
this.expiresInMillis = base.getExpiresIn();
|
||||||
|
this.revealDuration = base.getRevealDuration();
|
||||||
this.unidentified = base.isUnidentified();
|
this.unidentified = base.isUnidentified();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +140,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
this.push = fragments.get(0).isPush();
|
this.push = fragments.get(0).isPush();
|
||||||
this.subscriptionId = fragments.get(0).getSubscriptionId();
|
this.subscriptionId = fragments.get(0).getSubscriptionId();
|
||||||
this.expiresInMillis = fragments.get(0).getExpiresIn();
|
this.expiresInMillis = fragments.get(0).getExpiresIn();
|
||||||
|
this.revealDuration = fragments.get(0).getRevealDuration();
|
||||||
this.unidentified = fragments.get(0).isUnidentified();
|
this.unidentified = fragments.get(0).isUnidentified();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,6 +158,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
this.push = true;
|
this.push = true;
|
||||||
this.subscriptionId = -1;
|
this.subscriptionId = -1;
|
||||||
this.expiresInMillis = 0;
|
this.expiresInMillis = 0;
|
||||||
|
this.revealDuration = 0;
|
||||||
this.unidentified = false;
|
this.unidentified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +170,10 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
return expiresInMillis;
|
return expiresInMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getRevealDuration() {
|
||||||
|
return revealDuration;
|
||||||
|
}
|
||||||
|
|
||||||
public long getSentTimestampMillis() {
|
public long getSentTimestampMillis() {
|
||||||
return sentTimestampMillis;
|
return sentTimestampMillis;
|
||||||
}
|
}
|
||||||
@ -269,6 +280,8 @@ public class IncomingTextMessage implements Parcelable {
|
|||||||
out.writeParcelable(groupId, flags);
|
out.writeParcelable(groupId, flags);
|
||||||
out.writeInt(push ? 1 : 0);
|
out.writeInt(push ? 1 : 0);
|
||||||
out.writeInt(subscriptionId);
|
out.writeInt(subscriptionId);
|
||||||
|
out.writeLong(expiresInMillis);
|
||||||
|
out.writeLong(revealDuration);
|
||||||
out.writeInt(unidentified ? 1 : 0);
|
out.writeInt(unidentified ? 1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ public class GroupUtil {
|
|||||||
.setType(GroupContext.Type.QUIT)
|
.setType(GroupContext.Type.QUIT)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return Optional.of(new OutgoingGroupMediaMessage(groupRecipient, groupContext, null, System.currentTimeMillis(), 0, null, Collections.emptyList(), Collections.emptyList()));
|
return Optional.of(new OutgoingGroupMediaMessage(groupRecipient, groupContext, null, System.currentTimeMillis(), 0, 0, null, Collections.emptyList(), Collections.emptyList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ public class IdentityUtil {
|
|||||||
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
||||||
|
|
||||||
if (remote) {
|
if (remote) {
|
||||||
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
|
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, 0, false);
|
||||||
|
|
||||||
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
||||||
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
||||||
@ -98,7 +98,7 @@ public class IdentityUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (remote) {
|
if (remote) {
|
||||||
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false);
|
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, 0, false);
|
||||||
|
|
||||||
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
||||||
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
||||||
@ -128,14 +128,14 @@ public class IdentityUtil {
|
|||||||
while ((groupRecord = reader.getNext()) != null) {
|
while ((groupRecord = reader.getNext()) != null) {
|
||||||
if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) {
|
if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) {
|
||||||
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
||||||
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
|
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, 0, false);
|
||||||
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);
|
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);
|
||||||
|
|
||||||
smsDatabase.insertMessageInbox(groupUpdate);
|
smsDatabase.insertMessageInbox(groupUpdate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false);
|
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, 0, false);
|
||||||
IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming);
|
IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming);
|
||||||
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate);
|
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate);
|
||||||
|
|
||||||
|
@ -183,6 +183,8 @@ public class TextSecurePreferences {
|
|||||||
|
|
||||||
private static final String MEDIA_KEYBOARD_MODE = "pref_media_keyboard_mode";
|
private static final String MEDIA_KEYBOARD_MODE = "pref_media_keyboard_mode";
|
||||||
|
|
||||||
|
private static final String REVEALABLE_MESSAGE_DEFAULT = "pref_revealable_message_default";
|
||||||
|
|
||||||
public static boolean isScreenLockEnabled(@NonNull Context context) {
|
public static boolean isScreenLockEnabled(@NonNull Context context) {
|
||||||
return getBooleanPreference(context, SCREEN_LOCK, false);
|
return getBooleanPreference(context, SCREEN_LOCK, false);
|
||||||
}
|
}
|
||||||
@ -1098,6 +1100,14 @@ public class TextSecurePreferences {
|
|||||||
return MediaKeyboardMode.valueOf(name);
|
return MediaKeyboardMode.valueOf(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setIsRevealableMessageEnabled(Context context, boolean value) {
|
||||||
|
setBooleanPreference(context, REVEALABLE_MESSAGE_DEFAULT, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRevealableMessageEnabled(Context context) {
|
||||||
|
return getBooleanPreference(context, REVEALABLE_MESSAGE_DEFAULT, false);
|
||||||
|
}
|
||||||
|
|
||||||
public static void setBooleanPreference(Context context, String key, boolean value) {
|
public static void setBooleanPreference(Context context, String key, boolean value) {
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
|
||||||
}
|
}
|
||||||
|