replies
@ -75,7 +75,7 @@ dependencies {
|
||||
compile('org.whispersystems:libpastelog:1.1.2') {
|
||||
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
||||
}
|
||||
compile 'org.whispersystems:signal-service-android:2.7.2'
|
||||
compile 'org.whispersystems:signal-service-android:2.7.3'
|
||||
compile 'org.whispersystems:webrtc-android:M64'
|
||||
|
||||
compile "me.leolin:ShortcutBadger:1.1.16"
|
||||
@ -164,7 +164,7 @@ dependencyVerification {
|
||||
'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718',
|
||||
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
|
||||
'org.whispersystems:libpastelog:fe56b4db9ec743c8b565e3e4caa9228fafe132dc0bf82000d6e359b97a81177c',
|
||||
'org.whispersystems:signal-service-android:a7dfcb2f88ec69e8a1d31215cc7b67f0db50a96cd9d3832bfe75f56e67188537',
|
||||
'org.whispersystems:signal-service-android:dd0c21b37b239ac9c3eaf0b290791a3708817daa13e82e24b0544631f948d8d3',
|
||||
'org.whispersystems:webrtc-android:ed297e8b795dad9658cf306c2aa0f7d296c65f0997a2ac4353fd0157910acc12',
|
||||
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
|
||||
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
|
||||
@ -203,7 +203,7 @@ dependencyVerification {
|
||||
'com.github.bumptech.glide:gifdecoder:59ccf3bb0cec11dab4b857382cbe0b171111b6fc62bf141adce4e1180889af15',
|
||||
'com.android.support:support-annotations:af05330d997eb92a066534dbe0a3ea24347d26d7001221092113ae02a8f233da',
|
||||
'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1',
|
||||
'org.whispersystems:signal-service-java:f5ca4595eb09e25b9c9fd39c83bdcf1978a61d8a4b6f770bb548f3dd40ecc493',
|
||||
'org.whispersystems:signal-service-java:6654e52469b77db5c720de9557abe41bf99a9034c170c8a09e00bd2487c86430',
|
||||
'com.github.bumptech.glide:disklrucache:c1b1b6f5bbd01e2fcdc9d7f60913c8d338bdb65ed4a93bfa02b56f19daaade4b',
|
||||
'com.github.bumptech.glide:annotations:bede99ef9f71517a4274bac18fd3e483e9f2b6108d7d6fe8f4949be4aa4d9512',
|
||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
||||
|
BIN
res/drawable-hdpi/ic_close_white_18dp.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
res/drawable-hdpi/ic_insert_photo_white_18dp.png
Normal file
After Width: | Height: | Size: 210 B |
BIN
res/drawable-hdpi/ic_reply_white_24dp.png
Normal file
After Width: | Height: | Size: 253 B |
BIN
res/drawable-mdpi/ic_close_white_18dp.png
Normal file
After Width: | Height: | Size: 149 B |
BIN
res/drawable-mdpi/ic_insert_photo_white_18dp.png
Normal file
After Width: | Height: | Size: 148 B |
BIN
res/drawable-mdpi/ic_reply_white_24dp.png
Normal file
After Width: | Height: | Size: 186 B |
BIN
res/drawable-xhdpi/ic_close_white_18dp.png
Normal file
After Width: | Height: | Size: 221 B |
BIN
res/drawable-xhdpi/ic_insert_photo_white_18dp.png
Normal file
After Width: | Height: | Size: 247 B |
BIN
res/drawable-xhdpi/ic_reply_white_24dp.png
Normal file
After Width: | Height: | Size: 306 B |
BIN
res/drawable-xxhdpi/ic_close_white_18dp.png
Normal file
After Width: | Height: | Size: 302 B |
BIN
res/drawable-xxhdpi/ic_insert_photo_white_18dp.png
Normal file
After Width: | Height: | Size: 334 B |
BIN
res/drawable-xxhdpi/ic_reply_white_24dp.png
Normal file
After Width: | Height: | Size: 436 B |
BIN
res/drawable-xxxhdpi/ic_close_white_18dp.png
Normal file
After Width: | Height: | Size: 347 B |
BIN
res/drawable-xxxhdpi/ic_insert_photo_white_18dp.png
Normal file
After Width: | Height: | Size: 450 B |
BIN
res/drawable-xxxhdpi/ic_reply_white_24dp.png
Normal file
After Width: | Height: | Size: 579 B |
6
res/drawable/quote_background.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/gray5"/>
|
||||
<stroke android:color="@color/gray10" android:width="1dp"/>
|
||||
<corners android:radius="5dp" />
|
||||
</shape>
|
9
res/drawable/quote_bar.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="@color/white"/>
|
||||
<stroke android:color="@color/transparent"/>
|
||||
<corners android:topLeftRadius="5dp" android:bottomLeftRadius="5dp"/>
|
||||
|
||||
</shape>
|
@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge 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">
|
||||
|
||||
<org.thoughtcrime.securesms.components.InputPanel
|
||||
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/bottom_panel"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -27,77 +26,93 @@
|
||||
android:paddingBottom="8dp"
|
||||
android:background="@drawable/sent_bubble"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiToggle
|
||||
android:id="@+id/emoji_toggle"
|
||||
android:layout_width="37dp"
|
||||
android:layout_height="37dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@drawable/touch_highlight_background"
|
||||
android:contentDescription="@string/conversation_activity__emoji_toggle_description" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.ComposeText
|
||||
style="@style/ComposeEditText"
|
||||
android:id="@+id/embedded_text_editor"
|
||||
android:layout_width="0dp"
|
||||
<org.thoughtcrime.securesms.components.QuoteView
|
||||
android:id="@+id/quote_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="37dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:nextFocusForward="@+id/send_button"
|
||||
android:nextFocusRight="@+id/send_button"
|
||||
tools:visibility="invisible"
|
||||
tools:hint="Send TextSecure message" >
|
||||
<requestFocus />
|
||||
</org.thoughtcrime.securesms.components.ComposeText>
|
||||
android:visibility="gone"
|
||||
app:quote_dismissable="true"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.HidingLinearLayout
|
||||
android:id="@+id/quick_attachment_toggle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/quick_camera_toggle"
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiToggle
|
||||
android:id="@+id/emoji_toggle"
|
||||
android:layout_width="37dp"
|
||||
android:layout_height="37dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:src="?quick_camera_icon"
|
||||
android:background="@drawable/touch_highlight_background"
|
||||
android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_camera_description"
|
||||
android:padding="10dp"/>
|
||||
android:contentDescription="@string/conversation_activity__emoji_toggle_description" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.MicrophoneRecorderView
|
||||
android:id="@+id/recorder_view"
|
||||
android:layout_width="37dp"
|
||||
android:layout_height="37dp"
|
||||
<org.thoughtcrime.securesms.components.ComposeText
|
||||
style="@style/ComposeEditText"
|
||||
android:id="@+id/embedded_text_editor"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="37dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:nextFocusForward="@+id/send_button"
|
||||
android:nextFocusRight="@+id/send_button"
|
||||
tools:visibility="invisible"
|
||||
tools:hint="Send TextSecure message" >
|
||||
<requestFocus />
|
||||
</org.thoughtcrime.securesms.components.ComposeText>
|
||||
|
||||
<org.thoughtcrime.securesms.components.HidingLinearLayout
|
||||
android:id="@+id/quick_attachment_toggle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/quick_audio_toggle"
|
||||
android:id="@+id/quick_camera_toggle"
|
||||
android:layout_width="37dp"
|
||||
android:layout_height="37dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:src="?quick_mic_icon"
|
||||
android:background="@null"
|
||||
android:contentDescription="@string/conversation_activity__quick_attachment_drawer_record_and_send_audio_description"
|
||||
android:src="?quick_camera_icon"
|
||||
android:background="@drawable/touch_highlight_background"
|
||||
android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_camera_description"
|
||||
android:padding="10dp"/>
|
||||
|
||||
<ImageView android:id="@+id/quick_audio_fab"
|
||||
android:layout_width="74dp"
|
||||
android:layout_height="74dp"
|
||||
android:src="@drawable/ic_mic_white_48dp"
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:backgroundTint="@color/red_400"
|
||||
android:visibility="gone"
|
||||
android:scaleType="center"/>
|
||||
<org.thoughtcrime.securesms.components.MicrophoneRecorderView
|
||||
android:id="@+id/recorder_view"
|
||||
android:layout_width="37dp"
|
||||
android:layout_height="37dp"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
</org.thoughtcrime.securesms.components.MicrophoneRecorderView>
|
||||
<ImageButton
|
||||
android:id="@+id/quick_audio_toggle"
|
||||
android:layout_width="37dp"
|
||||
android:layout_height="37dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:src="?quick_mic_icon"
|
||||
android:background="@null"
|
||||
android:contentDescription="@string/conversation_activity__quick_attachment_drawer_record_and_send_audio_description"
|
||||
android:padding="10dp"/>
|
||||
|
||||
</org.thoughtcrime.securesms.components.HidingLinearLayout>
|
||||
<ImageView android:id="@+id/quick_audio_fab"
|
||||
android:layout_width="74dp"
|
||||
android:layout_height="74dp"
|
||||
android:src="@drawable/ic_mic_white_48dp"
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:backgroundTint="@color/red_400"
|
||||
android:visibility="gone"
|
||||
android:scaleType="center"/>
|
||||
|
||||
</org.thoughtcrime.securesms.components.MicrophoneRecorderView>
|
||||
|
||||
</org.thoughtcrime.securesms.components.HidingLinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:id="@+id/recording_container"
|
||||
@ -174,4 +189,3 @@
|
||||
|
||||
</org.thoughtcrime.securesms.components.AnimatingToggle>
|
||||
</org.thoughtcrime.securesms.components.InputPanel>
|
||||
</merge>
|
||||
|
@ -75,6 +75,14 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.components.QuoteView
|
||||
android:id="@+id/quote_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:quote_dismissable="false"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/image_view_stub"
|
||||
android:layout="@layout/conversation_item_received_thumbnail"
|
||||
|
@ -38,6 +38,14 @@
|
||||
android:background="@drawable/sent_bubble"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.QuoteView
|
||||
android:id="@+id/quote_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:quote_dismissable="false"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/image_view_stub"
|
||||
android:layout_width="@dimen/media_bubble_default_dimens"
|
||||
|
101
res/layout/quote_view.xml
Normal file
@ -0,0 +1,101 @@
|
||||
<?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"
|
||||
android:id="@+id/quote_container"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_margin="3dp"
|
||||
android:background="@drawable/quote_background"
|
||||
tools:visibility="visible"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<ImageView android:id="@+id/quote_bar"
|
||||
android:layout_width="5dp"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/quote_bar"
|
||||
tools:tint="@color/purple_400"/>
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<TextView android:id="@+id/quote_author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:maxLines="1"
|
||||
tools:textColor="@color/purple_400"
|
||||
tools:text="Riya"/>
|
||||
|
||||
<LinearLayout android:id="@+id/media_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="7dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView android:id="@+id/media_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:tint="@color/gray50"
|
||||
android:src="@drawable/ic_insert_photo_white_18dp"/>
|
||||
|
||||
<TextView android:id="@+id/media_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:textSize="11sp"
|
||||
tools:text="Photo"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView android:id="@+id/quote_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
tools:text="Short text."
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.makeramen.roundedimageview.RoundedImageView
|
||||
android:id="@+id/quote_attachment"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="match_parent"
|
||||
app:riv_corner_radius_top_right="5dp"
|
||||
app:riv_corner_radius_bottom_right="5dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:visibility="gone"
|
||||
tools:src="@drawable/surfwalk2"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<ImageView android:id="@+id/quote_dismiss"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:src="@drawable/ic_close_white_18dp"
|
||||
android:tint="@color/gray70"
|
||||
android:background="@drawable/circle_alpha"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_marginEnd="5dp"/>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</merge>
|
@ -30,4 +30,11 @@
|
||||
android:visible="false"
|
||||
android:icon="?menu_save_icon"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item android:title="Reply"
|
||||
android:id="@+id/menu_context_reply"
|
||||
android:visible="true"
|
||||
android:icon="?menu_reply_icon"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
||||
|
@ -108,6 +108,7 @@
|
||||
<attr name="menu_info_icon" format="reference" />
|
||||
<attr name="menu_forward_icon" format="reference" />
|
||||
<attr name="menu_save_icon" format="reference" />
|
||||
<attr name="menu_reply_icon" format="reference" />
|
||||
|
||||
<attr name="pref_icon_tint" format="color"/>
|
||||
|
||||
@ -241,5 +242,9 @@
|
||||
<attr name="vcv_textColor" format="color"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="QuoteView">
|
||||
<attr name="quote_dismissable" format="boolean"/>
|
||||
</declare-styleable>
|
||||
|
||||
|
||||
</resources>
|
||||
|
@ -23,6 +23,19 @@
|
||||
<color name="gray78">#ff383838</color>
|
||||
<color name="gray95">#ff111111</color>
|
||||
|
||||
<color name="transparent_black_05">#05000000</color>
|
||||
<color name="transparent_black_10">#10000000</color>
|
||||
<color name="transparent_black_20">#20000000</color>
|
||||
<color name="transparent_black_30">#30000000</color>
|
||||
<color name="transparent_black_40">#40000000</color>
|
||||
|
||||
<color name="transparent_white_05">#05ffffff</color>
|
||||
<color name="transparent_white_10">#10ffffff</color>
|
||||
<color name="transparent_white_20">#20ffffff</color>
|
||||
<color name="transparent_white_30">#30ffffff</color>
|
||||
<color name="transparent_white_40">#40ffffff</color>
|
||||
<color name="transparent_white_aa">#aaffffff</color>
|
||||
|
||||
<color name="conversation_compose_divider">#32000000</color>
|
||||
|
||||
<color name="action_mode_status_bar">@color/gray65</color>
|
||||
|
@ -219,6 +219,7 @@
|
||||
<item name="menu_info_icon">@drawable/ic_info_outline_white_24dp</item>
|
||||
<item name="menu_forward_icon">@drawable/ic_forward_white_24dp</item>
|
||||
<item name="menu_save_icon">@drawable/ic_save_white_24dp</item>
|
||||
<item name="menu_reply_icon">@drawable/ic_reply_white_24dp</item>
|
||||
|
||||
<item name="conversation_icon_attach_audio">@drawable/ic_audio_light</item>
|
||||
<item name="conversation_icon_attach_video">@drawable/ic_video_light</item>
|
||||
@ -343,6 +344,7 @@
|
||||
<item name="menu_info_icon">@drawable/ic_info_outline_white_24dp</item>
|
||||
<item name="menu_forward_icon">@drawable/ic_forward_white_24dp</item>
|
||||
<item name="menu_save_icon">@drawable/ic_save_white_24dp</item>
|
||||
<item name="menu_reply_icon">@drawable/ic_reply_white_24dp</item>
|
||||
|
||||
<item name="conversation_icon_attach_audio">@drawable/ic_audio_dark</item>
|
||||
<item name="conversation_icon_attach_video">@drawable/ic_video_dark</item>
|
||||
|
@ -65,6 +65,7 @@ import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.android.gms.location.places.ui.PlacePicker;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
@ -111,6 +112,8 @@ import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
|
||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
|
||||
@ -766,7 +769,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
.setType(GroupContext.Type.QUIT)
|
||||
.build();
|
||||
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipient(), context, null, System.currentTimeMillis(), 0);
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipient(), context, null, System.currentTimeMillis(), 0, null);
|
||||
MessageSender.send(self, outgoingMessage, threadId, false, null);
|
||||
DatabaseFactory.getGroupDatabase(self).remove(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(self)));
|
||||
initializeEnabledCheck();
|
||||
@ -1648,7 +1651,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
handleUnverifiedRecipients();
|
||||
} else if (!forceSms && identityRecords.isUntrusted()) {
|
||||
handleUntrustedRecipients();
|
||||
} else if (attachmentManager.isAttachmentPresent() || recipient.isGroupRecipient() || recipient.getAddress().isEmail()) {
|
||||
} else if (attachmentManager.isAttachmentPresent() || recipient.isGroupRecipient() || recipient.getAddress().isEmail() || inputPanel.getQuote().isPresent()) {
|
||||
sendMediaMessage(forceSms, expiresIn, subscriptionId, initiating);
|
||||
} else {
|
||||
sendTextMessage(forceSms, expiresIn, subscriptionId, initiating);
|
||||
@ -1668,12 +1671,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private void sendMediaMessage(final boolean forceSms, final long expiresIn, final int subscriptionId, boolean initiating)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
Log.w(TAG, "Sending media message...");
|
||||
sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), expiresIn, subscriptionId, initiating);
|
||||
}
|
||||
|
||||
private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final long expiresIn, final int subscriptionId, final boolean initiating) {
|
||||
|
||||
OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, distributionType);
|
||||
OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, distributionType, inputPanel.getQuote().orNull());
|
||||
|
||||
final SettableFuture<Void> future = new SettableFuture<>();
|
||||
final Context context = getApplicationContext();
|
||||
@ -1691,6 +1694,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
.ifNecessary(!isSecureText || forceSms)
|
||||
.withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms))
|
||||
.onAllGranted(() -> {
|
||||
inputPanel.clearQuote();
|
||||
attachmentManager.clear(glideRequests, false);
|
||||
composeText.setText("");
|
||||
final long id = fragment.stageOutgoingMessage(outgoingMessage);
|
||||
@ -2048,6 +2052,23 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
this.threadId = threadId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleReplyMessage(MessageRecord messageRecord) {
|
||||
Recipient author;
|
||||
|
||||
if (messageRecord.isOutgoing()) {
|
||||
author = Recipient.from(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), true);
|
||||
} else {
|
||||
author = messageRecord.getIndividualRecipient();
|
||||
}
|
||||
|
||||
inputPanel.setQuote(GlideApp.with(this),
|
||||
messageRecord.getTimestamp(),
|
||||
author,
|
||||
messageRecord.getBody(),
|
||||
messageRecord.isMms() ? ((MmsMessageRecord)messageRecord).getSlideDeck() : new SlideDeck());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachmentChanged() {
|
||||
handleSecurityChange(isSecureText, isDefaultSms);
|
||||
|
@ -29,7 +29,10 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.FastCursorRecyclerViewAdapter;
|
||||
@ -54,6 +57,7 @@ import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -260,10 +264,11 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
@Override
|
||||
public long getItemId(@NonNull Cursor cursor) {
|
||||
String fastPreflightId = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.FAST_PREFLIGHT_ID));
|
||||
List<DatabaseAttachment> attachments = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachment(cursor);
|
||||
List<DatabaseAttachment> messageAttachments = Stream.of(attachments).filterNot(DatabaseAttachment::isQuote).toList();
|
||||
|
||||
if (fastPreflightId != null) {
|
||||
return Long.valueOf(fastPreflightId);
|
||||
if (messageAttachments.size() > 0 && messageAttachments.get(0).getFastPreflightId() != null) {
|
||||
return Long.valueOf(messageAttachments.get(0).getFastPreflightId());
|
||||
}
|
||||
|
||||
final String unique = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsColumns.UNIQUE_ROW_ID));
|
||||
|
@ -386,6 +386,10 @@ public class ConversationFragment extends Fragment
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
|
||||
}
|
||||
|
||||
private void handleReplyMessage(final MessageRecord message) {
|
||||
listener.handleReplyMessage(message);
|
||||
}
|
||||
|
||||
private void handleSaveAttachment(final MediaMmsMessageRecord message) {
|
||||
SaveAttachmentTask.showWarningDialog(getActivity(), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
@ -493,6 +497,7 @@ public class ConversationFragment extends Fragment
|
||||
|
||||
public interface ConversationFragmentListener {
|
||||
void setThreadId(long threadId);
|
||||
void handleReplyMessage(MessageRecord messageRecord);
|
||||
}
|
||||
|
||||
private class ConversationScrollListener extends OnScrollListener {
|
||||
@ -668,6 +673,10 @@ public class ConversationFragment extends Fragment
|
||||
handleSaveAttachment((MediaMmsMessageRecord)getSelectedMessageRecord());
|
||||
actionMode.finish();
|
||||
return true;
|
||||
case R.id.menu_context_reply:
|
||||
handleReplyMessage(getSelectedMessageRecord());
|
||||
actionMode.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.ExpirationTimerView;
|
||||
import org.thoughtcrime.securesms.components.QuoteView;
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@ -63,6 +64,7 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.Quote;
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.MmsDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.MmsSendJob;
|
||||
@ -111,6 +113,7 @@ public class ConversationItem extends LinearLayout
|
||||
private GlideRequests glideRequests;
|
||||
|
||||
protected View bodyBubble;
|
||||
private QuoteView quoteView;
|
||||
private TextView bodyText;
|
||||
private TextView dateText;
|
||||
private TextView simInfoText;
|
||||
@ -173,6 +176,7 @@ public class ConversationItem extends LinearLayout
|
||||
this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub));
|
||||
this.expirationTimer = findViewById(R.id.expiration_indicator);
|
||||
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
||||
this.quoteView = findViewById(R.id.quote_view);
|
||||
|
||||
setOnClickListener(new ClickListener(null));
|
||||
|
||||
@ -210,6 +214,7 @@ public class ConversationItem extends LinearLayout
|
||||
setMinimumWidth();
|
||||
setSimInfo(messageRecord);
|
||||
setExpiration(messageRecord);
|
||||
setQuote(messageRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -506,6 +511,17 @@ public class ConversationItem extends LinearLayout
|
||||
}
|
||||
}
|
||||
|
||||
private void setQuote(@NonNull MessageRecord messageRecord) {
|
||||
if (messageRecord.isMms() && !messageRecord.isMmsNotification() && ((MediaMmsMessageRecord)messageRecord).getQuote() != null) {
|
||||
Quote quote = ((MediaMmsMessageRecord)messageRecord).getQuote();
|
||||
assert quote != null;
|
||||
quoteView.setQuote(glideRequests, quote.getId(), Recipient.from(context, quote.getAuthor(), true), quote.getText(), quote.getAttachment());
|
||||
quoteView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
quoteView.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void setFailedStatusIcons() {
|
||||
alertView.setFailed();
|
||||
deliveryStatusIndicator.setNone();
|
||||
|
@ -35,10 +35,12 @@ public abstract class Attachment {
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
private final boolean quote;
|
||||
|
||||
public Attachment(@NonNull String contentType, int transferState, long size, @Nullable String fileName,
|
||||
@Nullable String location, @Nullable String key, @Nullable String relay,
|
||||
@Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote,
|
||||
int width, int height)
|
||||
int width, int height, boolean quote)
|
||||
{
|
||||
this.contentType = contentType;
|
||||
this.transferState = transferState;
|
||||
@ -52,6 +54,7 @@ public abstract class Attachment {
|
||||
this.voiceNote = voiceNote;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.quote = quote;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -119,4 +122,8 @@ public abstract class Attachment {
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public boolean isQuote() {
|
||||
return quote;
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ public class DatabaseAttachment extends Attachment {
|
||||
String contentType, int transferProgress, long size,
|
||||
String fileName, String location, String key, String relay,
|
||||
byte[] digest, String fastPreflightId, boolean voiceNote,
|
||||
int width, int height)
|
||||
int width, int height, boolean quote)
|
||||
{
|
||||
super(contentType, transferProgress, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height);
|
||||
super(contentType, transferProgress, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote);
|
||||
this.attachmentId = attachmentId;
|
||||
this.hasData = hasData;
|
||||
this.hasThumbnail = hasThumbnail;
|
||||
|
@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
public class MmsNotificationAttachment extends Attachment {
|
||||
|
||||
public MmsNotificationAttachment(int status, long size) {
|
||||
super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0);
|
||||
super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -15,12 +15,12 @@ import java.util.List;
|
||||
public class PointerAttachment extends Attachment {
|
||||
|
||||
private PointerAttachment(@NonNull String contentType, int transferState, long size,
|
||||
@Nullable String fileName, @NonNull String location,
|
||||
@NonNull String key, @NonNull String relay,
|
||||
@Nullable byte[] digest, boolean voiceNote,
|
||||
@Nullable String fileName, @NonNull String location,
|
||||
@Nullable String key, @NonNull String relay,
|
||||
@Nullable byte[] digest, boolean voiceNote,
|
||||
int width, int height)
|
||||
{
|
||||
super(contentType, transferState, size, fileName, location, key, relay, digest, null, voiceNote, width, height);
|
||||
super(contentType, transferState, size, fileName, location, key, relay, digest, null, voiceNote, width, height, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -41,23 +41,36 @@ public class PointerAttachment extends Attachment {
|
||||
|
||||
if (pointers.isPresent()) {
|
||||
for (SignalServiceAttachment pointer : pointers.get()) {
|
||||
if (pointer.isPointer()) {
|
||||
String encodedKey = Base64.encodeBytes(pointer.asPointer().getKey());
|
||||
Optional<Attachment> result = forPointer(Optional.of(pointer));
|
||||
|
||||
results.add(new PointerAttachment(pointer.getContentType(),
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||
pointer.asPointer().getSize().or(0),
|
||||
pointer.asPointer().getFileName().orNull(),
|
||||
String.valueOf(pointer.asPointer().getId()),
|
||||
encodedKey, pointer.asPointer().getRelay().orNull(),
|
||||
pointer.asPointer().getDigest().orNull(),
|
||||
pointer.asPointer().getVoiceNote(),
|
||||
pointer.asPointer().getWidth(),
|
||||
pointer.asPointer().getHeight()));
|
||||
if (result.isPresent()) {
|
||||
results.add(result.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer) {
|
||||
if (!pointer.isPresent() || !pointer.get().isPointer()) return Optional.absent();
|
||||
|
||||
String encodedKey = null;
|
||||
|
||||
if (pointer.get().asPointer().getKey() != null) {
|
||||
encodedKey = Base64.encodeBytes(pointer.get().asPointer().getKey());
|
||||
}
|
||||
|
||||
return Optional.of(new PointerAttachment(pointer.get().getContentType(),
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||
pointer.get().asPointer().getSize().or(0),
|
||||
pointer.get().asPointer().getFileName().orNull(),
|
||||
String.valueOf(pointer.get().asPointer().getId()),
|
||||
encodedKey, pointer.get().asPointer().getRelay().orNull(),
|
||||
pointer.get().asPointer().getDigest().orNull(),
|
||||
pointer.get().asPointer().getVoiceNote(),
|
||||
pointer.get().asPointer().getWidth(),
|
||||
pointer.get().asPointer().getHeight()));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,17 +10,17 @@ public class UriAttachment extends Attachment {
|
||||
private final @Nullable Uri thumbnailUri;
|
||||
|
||||
public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size,
|
||||
@Nullable String fileName, boolean voiceNote)
|
||||
@Nullable String fileName, boolean voiceNote, boolean quote)
|
||||
{
|
||||
this(uri, uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote);
|
||||
this(uri, uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote, quote);
|
||||
}
|
||||
|
||||
public UriAttachment(@NonNull Uri dataUri, @Nullable Uri thumbnailUri,
|
||||
@NonNull String contentType, int transferState, long size, int width, int height,
|
||||
@Nullable String fileName, @Nullable String fastPreflightId,
|
||||
boolean voiceNote)
|
||||
boolean voiceNote, boolean quote)
|
||||
{
|
||||
super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, width, height);
|
||||
super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, width, height, quote);
|
||||
this.dataUri = dataUri;
|
||||
this.thumbnailUri = thumbnailUri;
|
||||
}
|
||||
|
@ -23,12 +23,17 @@ import android.widget.Toast;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@ -43,12 +48,13 @@ public class InputPanel extends LinearLayout
|
||||
|
||||
private static final int FADE_TIME = 150;
|
||||
|
||||
private EmojiToggle emojiToggle;
|
||||
private ComposeText composeText;
|
||||
private View quickCameraToggle;
|
||||
private View quickAudioToggle;
|
||||
private View buttonToggle;
|
||||
private View recordingContainer;
|
||||
private QuoteView quoteView;
|
||||
private EmojiToggle emojiToggle;
|
||||
private ComposeText composeText;
|
||||
private View quickCameraToggle;
|
||||
private View quickAudioToggle;
|
||||
private View buttonToggle;
|
||||
private View recordingContainer;
|
||||
|
||||
private MicrophoneRecorderView microphoneRecorderView;
|
||||
private SlideToCancel slideToCancel;
|
||||
@ -74,15 +80,18 @@ public class InputPanel extends LinearLayout
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
this.emojiToggle = ViewUtil.findById(this, R.id.emoji_toggle);
|
||||
this.composeText = ViewUtil.findById(this, R.id.embedded_text_editor);
|
||||
this.quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
|
||||
this.quickAudioToggle = ViewUtil.findById(this, R.id.quick_audio_toggle);
|
||||
this.buttonToggle = ViewUtil.findById(this, R.id.button_toggle);
|
||||
this.recordingContainer = ViewUtil.findById(this, R.id.recording_container);
|
||||
this.recordTime = new RecordTime((TextView) ViewUtil.findById(this, R.id.record_time));
|
||||
this.slideToCancel = new SlideToCancel(ViewUtil.findById(this, R.id.slide_to_cancel));
|
||||
this.microphoneRecorderView = ViewUtil.findById(this, R.id.recorder_view);
|
||||
View quoteDismiss = findViewById(R.id.quote_dismiss);
|
||||
|
||||
this.quoteView = findViewById(R.id.quote_view);
|
||||
this.emojiToggle = findViewById(R.id.emoji_toggle);
|
||||
this.composeText = findViewById(R.id.embedded_text_editor);
|
||||
this.quickCameraToggle = findViewById(R.id.quick_camera_toggle);
|
||||
this.quickAudioToggle = findViewById(R.id.quick_audio_toggle);
|
||||
this.buttonToggle = findViewById(R.id.button_toggle);
|
||||
this.recordingContainer = findViewById(R.id.recording_container);
|
||||
this.recordTime = new RecordTime(findViewById(R.id.record_time));
|
||||
this.slideToCancel = new SlideToCancel(findViewById(R.id.slide_to_cancel));
|
||||
this.microphoneRecorderView = findViewById(R.id.recorder_view);
|
||||
this.microphoneRecorderView.setListener(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
@ -97,6 +106,8 @@ public class InputPanel extends LinearLayout
|
||||
emojiToggle.setVisibility(View.VISIBLE);
|
||||
emojiVisible = true;
|
||||
}
|
||||
|
||||
quoteDismiss.setOnClickListener(v -> clearQuote());
|
||||
}
|
||||
|
||||
public void setListener(final @NonNull Listener listener) {
|
||||
@ -109,6 +120,23 @@ public class InputPanel extends LinearLayout
|
||||
composeText.setMediaListener(listener);
|
||||
}
|
||||
|
||||
public void setQuote(@NonNull GlideRequests glideRequests, long id, @NonNull Recipient author, @NonNull String body, @NonNull SlideDeck attachments) {
|
||||
this.quoteView.setQuote(glideRequests, id, author, body, attachments);
|
||||
this.quoteView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void clearQuote() {
|
||||
this.quoteView.dismiss();
|
||||
}
|
||||
|
||||
public Optional<QuoteModel> getQuote() {
|
||||
if (quoteView.getQuoteId() > 0 && quoteView.getVisibility() == View.VISIBLE) {
|
||||
return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getAddress(), quoteView.getBody(), quoteView.getAttachments()));
|
||||
} else {
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
public void setEmojiDrawer(@NonNull EmojiDrawer emojiDrawer) {
|
||||
emojiToggle.attach(emojiDrawer);
|
||||
}
|
||||
@ -210,6 +238,7 @@ public class InputPanel extends LinearLayout
|
||||
composeText.insertEmoji(emoji);
|
||||
}
|
||||
|
||||
|
||||
public interface Listener {
|
||||
void onRecorderStarted();
|
||||
void onRecorderFinished();
|
||||
|
203
src/org/thoughtcrime/securesms/components/QuoteView.java
Normal file
@ -0,0 +1,203 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QuoteView extends LinearLayout implements RecipientModifiedListener {
|
||||
|
||||
private static final String TAG = QuoteView.class.getSimpleName();
|
||||
|
||||
private TextView authorView;
|
||||
private TextView bodyView;
|
||||
private ImageView quoteBarView;
|
||||
private ImageView attachmentView;
|
||||
private ImageView dismissView;
|
||||
|
||||
private long id;
|
||||
private Recipient author;
|
||||
private String body;
|
||||
private View mediaDescription;
|
||||
private ImageView mediaDescriptionIcon;
|
||||
private TextView mediaDescriptionText;
|
||||
private SlideDeck attachments;
|
||||
|
||||
public QuoteView(Context context) {
|
||||
super(context);
|
||||
initialize(null);
|
||||
}
|
||||
|
||||
public QuoteView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
public QuoteView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public QuoteView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
private void initialize(@Nullable AttributeSet attrs) {
|
||||
inflate(getContext(), R.layout.quote_view, this);
|
||||
|
||||
this.authorView = findViewById(R.id.quote_author);
|
||||
this.bodyView = findViewById(R.id.quote_text);
|
||||
this.quoteBarView = findViewById(R.id.quote_bar);
|
||||
this.attachmentView = findViewById(R.id.quote_attachment);
|
||||
this.dismissView = findViewById(R.id.quote_dismiss);
|
||||
this.mediaDescriptionIcon = findViewById(R.id.media_icon);
|
||||
this.mediaDescriptionText = findViewById(R.id.media_name);
|
||||
this.mediaDescription = findViewById(R.id.media_description);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.QuoteView, 0, 0);
|
||||
boolean dismissable = typedArray.getBoolean(R.styleable.QuoteView_quote_dismissable, true);
|
||||
typedArray.recycle();
|
||||
|
||||
if (!dismissable) dismissView.setVisibility(View.GONE);
|
||||
else dismissView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
dismissView.setOnClickListener(view -> setVisibility(View.GONE));
|
||||
|
||||
setBackgroundDrawable(getContext().getResources().getDrawable(R.drawable.quote_background));
|
||||
}
|
||||
|
||||
public void setQuote(GlideRequests glideRequests, long id, @NonNull Recipient author, @Nullable String body, @NonNull SlideDeck attachments) {
|
||||
if (this.author != null) this.author.removeListener(this);
|
||||
|
||||
this.id = id;
|
||||
this.author = author;
|
||||
this.body = body;
|
||||
this.attachments = attachments;
|
||||
|
||||
author.addListener(this);
|
||||
setQuoteAuthor(author);
|
||||
setQuoteText(body, attachments);
|
||||
setQuoteAttachment(glideRequests, attachments);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
if (this.author != null) this.author.removeListener(this);
|
||||
|
||||
this.id = 0;
|
||||
this.author = null;
|
||||
this.body = null;
|
||||
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
Util.runOnMain(() -> {
|
||||
if (recipient == author) {
|
||||
setQuoteAuthor(recipient);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setQuoteAuthor(@NonNull Recipient author) {
|
||||
this.authorView.setText(author.toShortString());
|
||||
this.authorView.setTextColor(author.getColor().toActionBarColor(getContext()));
|
||||
this.quoteBarView.setColorFilter(author.getColor().toActionBarColor(getContext()), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
private void setQuoteText(@Nullable String body, @NonNull SlideDeck attachments) {
|
||||
if (TextUtils.isEmpty(body) && attachments.containsMediaSlide()) {
|
||||
mediaDescription.setVisibility(View.VISIBLE);
|
||||
bodyView.setVisibility(View.GONE);
|
||||
|
||||
List<Slide> audioSlides = Stream.of(attachments.getSlides()).filter(Slide::hasAudio).limit(1).toList();
|
||||
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
|
||||
List<Slide> imageSlides = Stream.of(attachments.getSlides()).filter(Slide::hasImage).limit(1).toList();
|
||||
List<Slide> videoSlides = Stream.of(attachments.getSlides()).filter(Slide::hasVideo).limit(1).toList();
|
||||
|
||||
if (!audioSlides.isEmpty()) {
|
||||
mediaDescriptionIcon.setImageResource(R.drawable.ic_mic_white_24dp);
|
||||
mediaDescriptionText.setText("Audio");
|
||||
} else if (!documentSlides.isEmpty()) {
|
||||
mediaDescriptionIcon.setImageResource(R.drawable.ic_insert_drive_file_white_24dp);
|
||||
mediaDescriptionText.setText(String.format("%s (%s)", documentSlides.get(0).getFileName(), Util.getPrettyFileSize(documentSlides.get(0).getFileSize())));
|
||||
} else if (!videoSlides.isEmpty()) {
|
||||
mediaDescriptionIcon.setImageResource(R.drawable.ic_videocam_white_24dp);
|
||||
mediaDescriptionText.setText("Video");
|
||||
} else if (!imageSlides.isEmpty()) {
|
||||
mediaDescriptionIcon.setImageResource(R.drawable.ic_camera_alt_white_24dp);
|
||||
mediaDescriptionText.setText("Photo");
|
||||
}
|
||||
} else {
|
||||
mediaDescription.setVisibility(View.GONE);
|
||||
bodyView.setVisibility(View.VISIBLE);
|
||||
|
||||
bodyView.setText(body == null ? "" : body);
|
||||
}
|
||||
}
|
||||
|
||||
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) {
|
||||
List<Slide> imageVideoSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo()).limit(1).toList();
|
||||
|
||||
if (!imageVideoSlides.isEmpty() && imageVideoSlides.get(0).getThumbnailUri() != null) {
|
||||
attachmentView.setVisibility(View.VISIBLE);
|
||||
dismissView.setBackgroundResource(R.drawable.circle_alpha);
|
||||
glideRequests.load(new DecryptableUri(imageVideoSlides.get(0).getThumbnailUri()))
|
||||
.centerCrop()
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.into(attachmentView);
|
||||
} else {
|
||||
attachmentView.setVisibility(View.GONE);
|
||||
dismissView.setBackgroundDrawable(null);
|
||||
}
|
||||
}
|
||||
|
||||
public long getQuoteId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Recipient getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public List<Attachment> getAttachments() {
|
||||
return attachments.asAttachments();
|
||||
}
|
||||
}
|
@ -33,6 +33,8 @@ import android.util.Pair;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
@ -44,6 +46,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.mms.MediaStream;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
@ -55,6 +58,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
@ -67,7 +71,7 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
public static final String TABLE_NAME = "part";
|
||||
public static final String ROW_ID = "_id";
|
||||
public static final String ATTACHMENT_ID_ALIAS = "attachment_id";
|
||||
static final String ATTACHMENT_JSON_ALIAS = "attachment_json";
|
||||
static final String MMS_ID = "mid";
|
||||
static final String CONTENT_TYPE = "ct";
|
||||
static final String NAME = "name";
|
||||
@ -82,7 +86,8 @@ public class AttachmentDatabase extends Database {
|
||||
public static final String UNIQUE_ID = "unique_id";
|
||||
static final String DIGEST = "digest";
|
||||
static final String VOICE_NOTE = "voice_note";
|
||||
public static final String FAST_PREFLIGHT_ID = "fast_preflight_id";
|
||||
static final String QUOTE = "quote";
|
||||
static final String FAST_PREFLIGHT_ID = "fast_preflight_id";
|
||||
public static final String DATA_RANDOM = "data_random";
|
||||
private static final String THUMBNAIL_RANDOM = "thumbnail_random";
|
||||
static final String WIDTH = "width";
|
||||
@ -97,12 +102,12 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?";
|
||||
|
||||
private static final String[] PROJECTION = new String[] {ROW_ID + " AS " + ATTACHMENT_ID_ALIAS,
|
||||
private static final String[] PROJECTION = new String[] {ROW_ID,
|
||||
MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION,
|
||||
CONTENT_LOCATION, DATA, THUMBNAIL, TRANSFER_STATE,
|
||||
SIZE, FILE_NAME, THUMBNAIL, THUMBNAIL_ASPECT_RATIO,
|
||||
UNIQUE_ID, DIGEST, FAST_PREFLIGHT_ID, VOICE_NOTE,
|
||||
DATA_RANDOM, THUMBNAIL_RANDOM, WIDTH, HEIGHT};
|
||||
QUOTE, DATA_RANDOM, THUMBNAIL_RANDOM, WIDTH, HEIGHT};
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
|
||||
MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " +
|
||||
@ -114,7 +119,7 @@ public class AttachmentDatabase extends Database {
|
||||
FILE_NAME + " TEXT, " + THUMBNAIL + " TEXT, " + THUMBNAIL_ASPECT_RATIO + " REAL, " +
|
||||
UNIQUE_ID + " INTEGER NOT NULL, " + DIGEST + " BLOB, " + FAST_PREFLIGHT_ID + " TEXT, " +
|
||||
VOICE_NOTE + " INTEGER DEFAULT 0, " + DATA_RANDOM + " BLOB, " + THUMBNAIL_RANDOM + " BLOB, " +
|
||||
WIDTH + " INTEGER DEFAULT 0, " + HEIGHT + " INTEGER DEFAULT 0);";
|
||||
QUOTE + " INTEGER DEFAULT 0, " + WIDTH + " INTEGER DEFAULT 0, " + HEIGHT + " INTEGER DEFAULT 0);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
|
||||
@ -181,9 +186,15 @@ public class AttachmentDatabase extends Database {
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, PROJECTION, PART_ID_WHERE, attachmentId.toStrings(), null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) return getAttachment(cursor);
|
||||
else return null;
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
List<DatabaseAttachment> list = getAttachment(cursor);
|
||||
|
||||
if (list != null && list.size() > 0) {
|
||||
return list.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
@ -200,7 +211,7 @@ public class AttachmentDatabase extends Database {
|
||||
null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
results.add(getAttachment(cursor));
|
||||
results.addAll(getAttachment(cursor));
|
||||
}
|
||||
|
||||
return results;
|
||||
@ -218,7 +229,7 @@ public class AttachmentDatabase extends Database {
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, PROJECTION, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null);
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
attachments.add(getAttachment(cursor));
|
||||
attachments.addAll(getAttachment(cursor));
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
@ -327,15 +338,19 @@ public class AttachmentDatabase extends Database {
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId));
|
||||
}
|
||||
|
||||
void insertAttachmentsForMessage(long mmsId, @NonNull List<Attachment> attachments)
|
||||
void insertAttachmentsForMessage(long mmsId, @NonNull List<Attachment> attachments, @NonNull List<Attachment> quoteAttachment)
|
||||
throws MmsException
|
||||
{
|
||||
Log.w(TAG, "insertParts(" + attachments.size() + ")");
|
||||
|
||||
for (Attachment attachment : attachments) {
|
||||
AttachmentId attachmentId = insertAttachment(mmsId, attachment);
|
||||
AttachmentId attachmentId = insertAttachment(mmsId, attachment, attachment.isQuote());
|
||||
Log.w(TAG, "Inserted attachment at ID: " + attachmentId);
|
||||
}
|
||||
|
||||
for (Attachment attachment : quoteAttachment) {
|
||||
insertAttachment(mmsId, attachment, true);
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull Attachment updateAttachmentData(@NonNull Attachment attachment,
|
||||
@ -376,7 +391,8 @@ public class AttachmentDatabase extends Database {
|
||||
databaseAttachment.getFastPreflightId(),
|
||||
databaseAttachment.isVoiceNote(),
|
||||
mediaStream.getWidth(),
|
||||
mediaStream.getHeight());
|
||||
mediaStream.getHeight(),
|
||||
databaseAttachment.isQuote());
|
||||
}
|
||||
|
||||
|
||||
@ -519,28 +535,68 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseAttachment getAttachment(@NonNull Cursor cursor) {
|
||||
return new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ATTACHMENT_ID_ALIAS)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow(DATA)),
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow(THUMBNAIL)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
|
||||
StorageUtil.getCleanFileName(cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME))),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(NAME)),
|
||||
cursor.getBlob(cursor.getColumnIndexOrThrow(DIGEST)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FAST_PREFLIGHT_ID)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(VOICE_NOTE)) == 1,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(WIDTH)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(HEIGHT)));
|
||||
public List<DatabaseAttachment> getAttachment(@NonNull Cursor cursor) {
|
||||
try {
|
||||
if (cursor.getColumnIndex(AttachmentDatabase.ATTACHMENT_JSON_ALIAS) != -1) {
|
||||
if (cursor.isNull(cursor.getColumnIndexOrThrow(ATTACHMENT_JSON_ALIAS))) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
|
||||
List<DatabaseAttachment> result = new LinkedList<>();
|
||||
JSONArray array = new JSONArray(cursor.getString(cursor.getColumnIndexOrThrow(ATTACHMENT_JSON_ALIAS)));
|
||||
|
||||
for (int i=0;i<array.length();i++) {
|
||||
JsonUtils.SaneJSONObject object = new JsonUtils.SaneJSONObject(array.getJSONObject(i));
|
||||
|
||||
if (!object.isNull(ROW_ID)) {
|
||||
result.add(new DatabaseAttachment(new AttachmentId(object.getLong(ROW_ID), object.getLong(UNIQUE_ID)),
|
||||
object.getLong(MMS_ID),
|
||||
!TextUtils.isEmpty(object.getString(DATA)),
|
||||
!TextUtils.isEmpty(object.getString(THUMBNAIL)),
|
||||
object.getString(CONTENT_TYPE),
|
||||
object.getInt(TRANSFER_STATE),
|
||||
object.getLong(SIZE),
|
||||
object.getString(FILE_NAME),
|
||||
object.getString(CONTENT_LOCATION),
|
||||
object.getString(CONTENT_DISPOSITION),
|
||||
object.getString(NAME),
|
||||
null,
|
||||
object.getString(FAST_PREFLIGHT_ID),
|
||||
object.getInt(VOICE_NOTE) == 1,
|
||||
object.getInt(WIDTH),
|
||||
object.getInt(HEIGHT),
|
||||
object.getInt(QUOTE) == 1));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return Collections.singletonList(new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow(DATA)),
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow(THUMBNAIL)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(NAME)),
|
||||
cursor.getBlob(cursor.getColumnIndexOrThrow(DIGEST)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FAST_PREFLIGHT_ID)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(VOICE_NOTE)) == 1,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(WIDTH)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(HEIGHT)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE)) == 1));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private AttachmentId insertAttachment(long mmsId, Attachment attachment)
|
||||
private AttachmentId insertAttachment(long mmsId, Attachment attachment, boolean quote)
|
||||
throws MmsException
|
||||
{
|
||||
Log.w(TAG, "Inserting attachment for mms id: " + mmsId);
|
||||
@ -569,6 +625,7 @@ public class AttachmentDatabase extends Database {
|
||||
contentValues.put(VOICE_NOTE, attachment.isVoiceNote() ? 1 : 0);
|
||||
contentValues.put(WIDTH, attachment.getWidth());
|
||||
contentValues.put(HEIGHT, attachment.getHeight());
|
||||
contentValues.put(QUOTE, quote);
|
||||
|
||||
if (dataInfo != null) {
|
||||
contentValues.put(DATA, dataInfo.file.getAbsolutePath());
|
||||
|
@ -13,9 +13,11 @@ import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MediaDatabase extends Database {
|
||||
|
||||
private static final String BASE_MEDIA_QUERY = "SELECT " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS + ", "
|
||||
private static final String BASE_MEDIA_QUERY = "SELECT " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ROW_ID + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL_ASPECT_RATIO + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", "
|
||||
@ -32,6 +34,7 @@ public class MediaDatabase extends Database {
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VOICE_NOTE + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.WIDTH + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.HEIGHT + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.QUOTE + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.NAME + ", "
|
||||
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.MESSAGE_BOX + ", "
|
||||
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", "
|
||||
@ -48,7 +51,7 @@ public class MediaDatabase extends Database {
|
||||
private static final String GALLERY_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentDatabase.CONTENT_TYPE + " LIKE 'image/%' OR " + AttachmentDatabase.CONTENT_TYPE + " LIKE 'video/%'");
|
||||
private static final String DOCUMENT_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'image/%' AND " + AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'video/%' AND " + AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'audio/%'");
|
||||
|
||||
public MediaDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
MediaDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
@ -89,11 +92,11 @@ public class MediaDatabase extends Database {
|
||||
}
|
||||
|
||||
public static MediaRecord from(@NonNull Context context, @NonNull Cursor cursor) {
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
DatabaseAttachment attachment = attachmentDatabase.getAttachment(cursor);
|
||||
String serializedAddress = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
|
||||
boolean outgoing = MessagingDatabase.Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)));
|
||||
Address address = null;
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
List<DatabaseAttachment> attachments = attachmentDatabase.getAttachment(cursor);
|
||||
String serializedAddress = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
|
||||
boolean outgoing = MessagingDatabase.Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)));
|
||||
Address address = null;
|
||||
|
||||
if (serializedAddress != null) {
|
||||
address = Address.fromSerialized(serializedAddress);
|
||||
@ -107,7 +110,7 @@ public class MediaDatabase extends Database {
|
||||
date = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.DATE_RECEIVED));
|
||||
}
|
||||
|
||||
return new MediaRecord(attachment, address, date, outgoing);
|
||||
return new MediaRecord(attachments != null && attachments.size() > 0 ? attachments.get(0) : null, address, date, outgoing);
|
||||
}
|
||||
|
||||
public DatabaseAttachment getAttachment() {
|
||||
|
@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.Quote;
|
||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
@ -51,6 +52,7 @@ import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
@ -86,6 +88,11 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
static final String PART_COUNT = "part_count";
|
||||
static final String NETWORK_FAILURE = "network_failures";
|
||||
|
||||
static final String QUOTE_ID = "quote_id";
|
||||
static final String QUOTE_AUTHOR = "quote_author";
|
||||
static final String QUOTE_BODY = "quote_body";
|
||||
static final String QUOTE_ATTACHMENT = "quote_attachment";
|
||||
|
||||
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, " +
|
||||
READ + " INTEGER DEFAULT 0, " + "m_id" + " TEXT, " + "sub" + " TEXT, " +
|
||||
@ -102,7 +109,8 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
NETWORK_FAILURE + " TEXT DEFAULT NULL," + "d_rpt" + " INTEGER, " +
|
||||
SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
|
||||
EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " INTEGER DEFAULT 0, " +
|
||||
READ_RECEIPT_COUNT + " 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);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||
@ -122,24 +130,26 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
MESSAGE_SIZE, STATUS, TRANSACTION_ID,
|
||||
BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
||||
DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID,
|
||||
EXPIRES_IN, EXPIRE_STARTED, NOTIFIED,
|
||||
AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
AttachmentDatabase.SIZE,
|
||||
AttachmentDatabase.FILE_NAME,
|
||||
AttachmentDatabase.DATA,
|
||||
AttachmentDatabase.THUMBNAIL,
|
||||
AttachmentDatabase.CONTENT_TYPE,
|
||||
AttachmentDatabase.CONTENT_LOCATION,
|
||||
AttachmentDatabase.DIGEST,
|
||||
AttachmentDatabase.FAST_PREFLIGHT_ID,
|
||||
AttachmentDatabase.VOICE_NOTE,
|
||||
AttachmentDatabase.WIDTH,
|
||||
AttachmentDatabase.HEIGHT,
|
||||
AttachmentDatabase.CONTENT_DISPOSITION,
|
||||
AttachmentDatabase.NAME,
|
||||
AttachmentDatabase.TRANSFER_STATE
|
||||
EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT,
|
||||
"json_group_array(json_object(" +
|
||||
"'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " +
|
||||
"'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " +
|
||||
"'" + AttachmentDatabase.MMS_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + ", " +
|
||||
"'" + AttachmentDatabase.SIZE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.SIZE + ", " +
|
||||
"'" + AttachmentDatabase.FILE_NAME + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FILE_NAME + ", " +
|
||||
"'" + AttachmentDatabase.DATA + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " +
|
||||
"'" + AttachmentDatabase.THUMBNAIL + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", " +
|
||||
"'" + AttachmentDatabase.CONTENT_TYPE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", " +
|
||||
"'" + AttachmentDatabase.CONTENT_LOCATION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " +
|
||||
"'" + AttachmentDatabase.FAST_PREFLIGHT_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + "," +
|
||||
"'" + AttachmentDatabase.VOICE_NOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VOICE_NOTE + "," +
|
||||
"'" + AttachmentDatabase.WIDTH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.WIDTH + "," +
|
||||
"'" + AttachmentDatabase.HEIGHT + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.HEIGHT + "," +
|
||||
"'" + AttachmentDatabase.QUOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.QUOTE + ", " +
|
||||
"'" + AttachmentDatabase.CONTENT_DISPOSITION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_DISPOSITION + ", " +
|
||||
"'" + AttachmentDatabase.NAME + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.NAME + ", " +
|
||||
"'" + AttachmentDatabase.TRANSFER_STATE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFER_STATE +
|
||||
")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS,
|
||||
};
|
||||
|
||||
private static final String RAW_ID_WHERE = TABLE_NAME + "._id = ?";
|
||||
@ -276,7 +286,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return database.rawQuery("SELECT " + Util.join(MMS_PROJECTION, ",") +
|
||||
" FROM " + MmsDatabase.TABLE_NAME + " LEFT OUTER JOIN " + AttachmentDatabase.TABLE_NAME +
|
||||
" ON (" + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " = " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + ")" +
|
||||
" WHERE " + where, arguments);
|
||||
" WHERE " + where + " GROUP BY " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID, arguments);
|
||||
}
|
||||
|
||||
public Cursor getMessage(long messageId) {
|
||||
@ -537,25 +547,37 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
cursor = rawQuery(RAW_ID_WHERE, new String[] {String.valueOf(messageId)});
|
||||
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
List<DatabaseAttachment> associatedAttachments = attachmentDatabase.getAttachmentsForMessage(messageId);
|
||||
|
||||
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
|
||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
|
||||
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID));
|
||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
|
||||
List<Attachment> attachments = new LinkedList<>(attachmentDatabase.getAttachmentsForMessage(messageId));
|
||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
||||
int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId);
|
||||
List<Attachment> attachments = Stream.of(associatedAttachments).filterNot(Attachment::isQuote).map(a -> (Attachment)a).toList();
|
||||
|
||||
Recipient recipient = Recipient.from(context, Address.fromSerialized(address), false);
|
||||
long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID));
|
||||
String quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR));
|
||||
String quoteText = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_BODY));
|
||||
List<Attachment> quoteAttachments = Stream.of(associatedAttachments).filter(Attachment::isQuote).map(a -> (Attachment)a).toList();
|
||||
|
||||
Recipient recipient = Recipient.from(context, Address.fromSerialized(address), false);
|
||||
QuoteModel quote = null;
|
||||
|
||||
if (quoteId > 0 && (!TextUtils.isEmpty(quoteText) || !quoteAttachments.isEmpty())) {
|
||||
quote = new QuoteModel(quoteId, Address.fromSerialized(quoteAuthor), quoteText, quoteAttachments);
|
||||
}
|
||||
|
||||
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
|
||||
return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0);
|
||||
return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, quote);
|
||||
} else if (Types.isExpirationTimerUpdate(outboxType)) {
|
||||
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
|
||||
}
|
||||
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType);
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote);
|
||||
|
||||
if (Types.isSecureType(outboxType)) {
|
||||
return new OutgoingSecureMediaMessage(message);
|
||||
@ -604,11 +626,13 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
databaseAttachment.getFastPreflightId(),
|
||||
databaseAttachment.isVoiceNote(),
|
||||
databaseAttachment.getWidth(),
|
||||
databaseAttachment.getHeight()));
|
||||
databaseAttachment.getHeight(),
|
||||
databaseAttachment.isQuote()));
|
||||
}
|
||||
|
||||
return insertMediaMessage(request.getBody(),
|
||||
attachments,
|
||||
new LinkedList<>(),
|
||||
contentValues,
|
||||
null);
|
||||
} catch (NoSuchMessageException e) {
|
||||
@ -651,12 +675,22 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
|
||||
}
|
||||
|
||||
List<Attachment> quoteAttachments = new LinkedList<>();
|
||||
|
||||
if (retrieved.getQuote() != null) {
|
||||
contentValues.put(QUOTE_ID, retrieved.getQuote().getId());
|
||||
contentValues.put(QUOTE_BODY, retrieved.getQuote().getText());
|
||||
contentValues.put(QUOTE_AUTHOR, retrieved.getQuote().getAuthor().serialize());
|
||||
|
||||
quoteAttachments = retrieved.getQuote().getAttachments();
|
||||
}
|
||||
|
||||
if (retrieved.isPushMessage() && isDuplicate(retrieved, threadId)) {
|
||||
Log.w(TAG, "Ignoring duplicate media message (" + retrieved.getSentTimeMillis() + ")");
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
long messageId = insertMediaMessage(retrieved.getBody(), retrieved.getAttachments(), contentValues, null);
|
||||
long messageId = insertMediaMessage(retrieved.getBody(), retrieved.getAttachments(), quoteAttachments, contentValues, null);
|
||||
|
||||
if (!Types.isExpirationTimerUpdate(mailbox)) {
|
||||
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
|
||||
@ -784,7 +818,17 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
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());
|
||||
|
||||
long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), contentValues, insertListener);
|
||||
List<Attachment> quoteAttachments = new LinkedList<>();
|
||||
|
||||
if (message.getOutgoingQuote() != null) {
|
||||
contentValues.put(QUOTE_ID, message.getOutgoingQuote().getId());
|
||||
contentValues.put(QUOTE_AUTHOR, message.getOutgoingQuote().getAuthor().serialize());
|
||||
contentValues.put(QUOTE_BODY, message.getOutgoingQuote().getText());
|
||||
|
||||
quoteAttachments.addAll(message.getOutgoingQuote().getAttachments());
|
||||
}
|
||||
|
||||
long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), quoteAttachments, contentValues, insertListener);
|
||||
|
||||
if (message.getRecipient().getAddress().isGroup()) {
|
||||
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().getAddress().toGroupString(), false);
|
||||
@ -806,6 +850,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
private long insertMediaMessage(@Nullable String body,
|
||||
@NonNull List<Attachment> attachments,
|
||||
@NonNull List<Attachment> quoteAttachments,
|
||||
@NonNull ContentValues contentValues,
|
||||
@Nullable SmsDatabase.InsertListener insertListener)
|
||||
throws MmsException
|
||||
@ -820,7 +865,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
try {
|
||||
long messageId = db.insert(TABLE_NAME, null, contentValues);
|
||||
|
||||
partsDatabase.insertAttachmentsForMessage(messageId, attachments);
|
||||
partsDatabase.insertAttachmentsForMessage(messageId, attachments, quoteAttachments);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
return messageId;
|
||||
@ -870,7 +915,6 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*package*/ void deleteThreads(Set<Long> threadIds) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
String where = "";
|
||||
@ -1021,7 +1065,13 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
new LinkedList<NetworkFailure>(),
|
||||
message.getSubscriptionId(),
|
||||
message.getExpiresIn(),
|
||||
System.currentTimeMillis(), 0);
|
||||
System.currentTimeMillis(), 0,
|
||||
message.getOutgoingQuote() != null ?
|
||||
new Quote(message.getOutgoingQuote().getId(),
|
||||
message.getOutgoingQuote().getAuthor(),
|
||||
message.getOutgoingQuote().getText(),
|
||||
new SlideDeck(context, message.getOutgoingQuote().getAttachments())) :
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1118,12 +1168,13 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
List<IdentityKeyMismatch> mismatches = getMismatchedIdentities(mismatchDocument);
|
||||
List<NetworkFailure> networkFailures = getFailures(networkDocument);
|
||||
SlideDeck slideDeck = getSlideDeck(cursor);
|
||||
Quote quote = getQuote(cursor);
|
||||
|
||||
return new MediaMmsMessageRecord(context, id, recipient, recipient,
|
||||
addressDeviceId, dateSent, dateReceived, deliveryReceiptCount,
|
||||
threadId, body, slideDeck, partCount, box, mismatches,
|
||||
networkFailures, subscriptionId, expiresIn, expireStarted,
|
||||
readReceiptCount);
|
||||
readReceiptCount, quote);
|
||||
}
|
||||
|
||||
private Recipient getRecipientFor(String serialized) {
|
||||
@ -1163,8 +1214,24 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
|
||||
private SlideDeck getSlideDeck(@NonNull Cursor cursor) {
|
||||
Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor);
|
||||
return new SlideDeck(context, attachment);
|
||||
List<DatabaseAttachment> attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor);
|
||||
List<? extends Attachment> messageAttachmnets = Stream.of(attachment).filterNot(Attachment::isQuote).toList();
|
||||
return new SlideDeck(context, messageAttachmnets);
|
||||
}
|
||||
|
||||
private @Nullable Quote getQuote(@NonNull Cursor cursor) {
|
||||
long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_ID));
|
||||
String quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_AUTHOR));
|
||||
String quoteText = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_BODY));
|
||||
List<DatabaseAttachment> attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor);
|
||||
List<? extends Attachment> quoteAttachments = Stream.of(attachments).filter(Attachment::isQuote).toList();
|
||||
SlideDeck quoteDeck = new SlideDeck(context, quoteAttachments);
|
||||
|
||||
if (quoteId > 0 && !TextUtils.isEmpty(quoteAuthor)) {
|
||||
return new Quote(quoteId, Address.fromExternal(context, quoteAuthor), quoteText, quoteDeck);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
@ -60,28 +60,20 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsColumns.EXPIRE_STARTED,
|
||||
MmsSmsColumns.NOTIFIED,
|
||||
TRANSPORT,
|
||||
AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
AttachmentDatabase.SIZE,
|
||||
AttachmentDatabase.FILE_NAME,
|
||||
AttachmentDatabase.DATA,
|
||||
AttachmentDatabase.THUMBNAIL,
|
||||
AttachmentDatabase.CONTENT_TYPE,
|
||||
AttachmentDatabase.CONTENT_LOCATION,
|
||||
AttachmentDatabase.DIGEST,
|
||||
AttachmentDatabase.FAST_PREFLIGHT_ID,
|
||||
AttachmentDatabase.VOICE_NOTE,
|
||||
AttachmentDatabase.WIDTH,
|
||||
AttachmentDatabase.HEIGHT,
|
||||
AttachmentDatabase.CONTENT_DISPOSITION,
|
||||
AttachmentDatabase.NAME,
|
||||
AttachmentDatabase.TRANSFER_STATE};
|
||||
AttachmentDatabase.ATTACHMENT_JSON_ALIAS,
|
||||
MmsDatabase.QUOTE_ID,
|
||||
MmsDatabase.QUOTE_AUTHOR,
|
||||
MmsDatabase.QUOTE_BODY,
|
||||
MmsDatabase.QUOTE_ATTACHMENT};
|
||||
|
||||
public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public Cursor getMessagesFor(long timestamp) {
|
||||
return queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null);
|
||||
}
|
||||
|
||||
public Cursor getConversation(long threadId, long limit) {
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||
@ -155,7 +147,25 @@ public class MmsSmsDatabase extends Database {
|
||||
"'MMS::' || " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID
|
||||
+ " || '::' || " + MmsDatabase.DATE_SENT
|
||||
+ " AS " + MmsSmsColumns.UNIQUE_ROW_ID,
|
||||
AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
"json_group_array(json_object(" +
|
||||
"'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " +
|
||||
"'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " +
|
||||
"'" + AttachmentDatabase.MMS_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + "," +
|
||||
"'" + AttachmentDatabase.SIZE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.SIZE + ", " +
|
||||
"'" + AttachmentDatabase.FILE_NAME + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FILE_NAME + ", " +
|
||||
"'" + AttachmentDatabase.DATA + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " +
|
||||
"'" + AttachmentDatabase.THUMBNAIL + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", " +
|
||||
"'" + AttachmentDatabase.CONTENT_TYPE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", " +
|
||||
"'" + AttachmentDatabase.CONTENT_LOCATION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " +
|
||||
"'" + AttachmentDatabase.FAST_PREFLIGHT_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + ", " +
|
||||
"'" + AttachmentDatabase.VOICE_NOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VOICE_NOTE + ", " +
|
||||
"'" + AttachmentDatabase.WIDTH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.WIDTH + ", " +
|
||||
"'" + AttachmentDatabase.HEIGHT + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.HEIGHT + ", " +
|
||||
"'" + AttachmentDatabase.QUOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.QUOTE + ", " +
|
||||
"'" + AttachmentDatabase.CONTENT_DISPOSITION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_DISPOSITION + ", " +
|
||||
"'" + AttachmentDatabase.NAME + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.NAME + ", " +
|
||||
"'" + AttachmentDatabase.TRANSFER_STATE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFER_STATE +
|
||||
")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS,
|
||||
SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
|
||||
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
@ -166,22 +176,10 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
|
||||
MmsSmsColumns.NOTIFIED,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
AttachmentDatabase.SIZE,
|
||||
AttachmentDatabase.FILE_NAME,
|
||||
AttachmentDatabase.DATA,
|
||||
AttachmentDatabase.THUMBNAIL,
|
||||
AttachmentDatabase.CONTENT_TYPE,
|
||||
AttachmentDatabase.CONTENT_LOCATION,
|
||||
AttachmentDatabase.DIGEST,
|
||||
AttachmentDatabase.FAST_PREFLIGHT_ID,
|
||||
AttachmentDatabase.VOICE_NOTE,
|
||||
AttachmentDatabase.WIDTH,
|
||||
AttachmentDatabase.HEIGHT,
|
||||
AttachmentDatabase.CONTENT_DISPOSITION,
|
||||
AttachmentDatabase.NAME,
|
||||
AttachmentDatabase.TRANSFER_STATE};
|
||||
MmsDatabase.QUOTE_ID,
|
||||
MmsDatabase.QUOTE_AUTHOR,
|
||||
MmsDatabase.QUOTE_BODY,
|
||||
MmsDatabase.QUOTE_ATTACHMENT};
|
||||
|
||||
String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
@ -189,7 +187,7 @@ public class MmsSmsDatabase extends Database {
|
||||
"'SMS::' || " + MmsSmsColumns.ID
|
||||
+ " || '::' || " + SmsDatabase.DATE_SENT
|
||||
+ " AS " + MmsSmsColumns.UNIQUE_ROW_ID,
|
||||
"NULL AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
"NULL AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS,
|
||||
SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
|
||||
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
@ -200,22 +198,10 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
|
||||
MmsSmsColumns.NOTIFIED,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
AttachmentDatabase.SIZE,
|
||||
AttachmentDatabase.FILE_NAME,
|
||||
AttachmentDatabase.DATA,
|
||||
AttachmentDatabase.THUMBNAIL,
|
||||
AttachmentDatabase.CONTENT_TYPE,
|
||||
AttachmentDatabase.CONTENT_LOCATION,
|
||||
AttachmentDatabase.DIGEST,
|
||||
AttachmentDatabase.FAST_PREFLIGHT_ID,
|
||||
AttachmentDatabase.VOICE_NOTE,
|
||||
AttachmentDatabase.WIDTH,
|
||||
AttachmentDatabase.HEIGHT,
|
||||
AttachmentDatabase.CONTENT_DISPOSITION,
|
||||
AttachmentDatabase.NAME,
|
||||
AttachmentDatabase.TRANSFER_STATE};
|
||||
MmsDatabase.QUOTE_ID,
|
||||
MmsDatabase.QUOTE_AUTHOR,
|
||||
MmsDatabase.QUOTE_BODY,
|
||||
MmsDatabase.QUOTE_ATTACHMENT};
|
||||
|
||||
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
||||
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
|
||||
@ -226,11 +212,7 @@ public class MmsSmsDatabase extends Database {
|
||||
smsQueryBuilder.setTables(SmsDatabase.TABLE_NAME);
|
||||
mmsQueryBuilder.setTables(MmsDatabase.TABLE_NAME + " LEFT OUTER JOIN " +
|
||||
AttachmentDatabase.TABLE_NAME +
|
||||
" ON " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " = " +
|
||||
" (SELECT " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID +
|
||||
" FROM " + AttachmentDatabase.TABLE_NAME + " WHERE " +
|
||||
AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " = " +
|
||||
MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " LIMIT 1)");
|
||||
" ON " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID);
|
||||
|
||||
|
||||
Set<String> mmsColumnsPresent = new HashSet<>();
|
||||
@ -273,9 +255,15 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsColumnsPresent.add(AttachmentDatabase.VOICE_NOTE);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.WIDTH);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.HEIGHT);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.QUOTE);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.NAME);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.TRANSFER_STATE);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.ATTACHMENT_JSON_ALIAS);
|
||||
mmsColumnsPresent.add(MmsDatabase.QUOTE_ID);
|
||||
mmsColumnsPresent.add(MmsDatabase.QUOTE_AUTHOR);
|
||||
mmsColumnsPresent.add(MmsDatabase.QUOTE_BODY);
|
||||
mmsColumnsPresent.add(MmsDatabase.QUOTE_ATTACHMENT);
|
||||
|
||||
Set<String> smsColumnsPresent = new HashSet<>();
|
||||
smsColumnsPresent.add(MmsSmsColumns.ID);
|
||||
@ -298,7 +286,7 @@ public class MmsSmsDatabase extends Database {
|
||||
smsColumnsPresent.add(SmsDatabase.STATUS);
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 4, MMS_TRANSPORT, selection, null, null, null);
|
||||
String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 4, MMS_TRANSPORT, selection, null, MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID, null);
|
||||
@SuppressWarnings("deprecation")
|
||||
String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(TRANSPORT, smsProjection, smsColumnsPresent, 4, SMS_TRANSPORT, selection, null, null, null);
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class PagingMediaLoader extends AsyncLoader<Pair<Cursor, Integer>> {
|
||||
Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
AttachmentId attachmentId = new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ATTACHMENT_ID_ALIAS)), cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID)));
|
||||
AttachmentId attachmentId = new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID)), cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID)));
|
||||
Uri attachmentUri = PartAuthority.getAttachmentDataUri(attachmentId);
|
||||
|
||||
if (attachmentUri.equals(uri)) {
|
||||
|
@ -18,6 +18,7 @@ package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannableString;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@ -52,11 +53,12 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
int partCount, long mailbox,
|
||||
List<IdentityKeyMismatch> mismatches,
|
||||
List<NetworkFailure> failures, int subscriptionId,
|
||||
long expiresIn, long expireStarted, int readReceiptCount)
|
||||
long expiresIn, long expireStarted, int readReceiptCount,
|
||||
@Nullable Quote quote)
|
||||
{
|
||||
super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
|
||||
dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
|
||||
subscriptionId, expiresIn, expireStarted, slideDeck, readReceiptCount);
|
||||
subscriptionId, expiresIn, expireStarted, slideDeck, readReceiptCount, quote);
|
||||
|
||||
this.context = context.getApplicationContext();
|
||||
this.partCount = partCount;
|
||||
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
@ -14,17 +15,21 @@ import java.util.List;
|
||||
|
||||
public abstract class MmsMessageRecord extends MessageRecord {
|
||||
|
||||
private final @NonNull SlideDeck slideDeck;
|
||||
private final @NonNull SlideDeck slideDeck;
|
||||
private final @Nullable Quote quote;
|
||||
|
||||
MmsMessageRecord(Context context, long id, String body, Recipient conversationRecipient,
|
||||
Recipient individualRecipient, int recipientDeviceId, long dateSent,
|
||||
long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount,
|
||||
long type, List<IdentityKeyMismatch> mismatches,
|
||||
List<NetworkFailure> networkFailures, int subscriptionId, long expiresIn,
|
||||
long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount)
|
||||
long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount,
|
||||
@Nullable Quote quote)
|
||||
{
|
||||
super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount);
|
||||
|
||||
this.slideDeck = slideDeck;
|
||||
this.quote = quote;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -52,5 +57,7 @@ public abstract class MmsMessageRecord extends MessageRecord {
|
||||
return slideDeck.containsMediaSlide();
|
||||
}
|
||||
|
||||
|
||||
public @Nullable Quote getQuote() {
|
||||
return quote;
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
|
||||
super(context, id, "", conversationRecipient, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
|
||||
new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId,
|
||||
0, 0, slideDeck, readReceiptCount);
|
||||
0, 0, slideDeck, readReceiptCount, null);
|
||||
|
||||
this.contentLocation = contentLocation;
|
||||
this.messageSize = messageSize;
|
||||
|
39
src/org/thoughtcrime/securesms/database/model/Quote.java
Normal file
@ -0,0 +1,39 @@
|
||||
package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
|
||||
public class Quote {
|
||||
|
||||
private final long id;
|
||||
private final Address author;
|
||||
private final String text;
|
||||
private final SlideDeck attachment;
|
||||
|
||||
public Quote(long id, @NonNull Address author, @Nullable String text, @NonNull SlideDeck attachment) {
|
||||
this.id = id;
|
||||
this.author = author;
|
||||
this.text = text;
|
||||
this.attachment = attachment;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public @NonNull Address getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public @Nullable String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public @NonNull SlideDeck getAttachment() {
|
||||
return attachment;
|
||||
}
|
||||
}
|
@ -111,10 +111,10 @@ public class GroupManager {
|
||||
|
||||
if (avatar != null) {
|
||||
Uri avatarUri = SingleUseBlobProvider.getInstance().createUri(avatar);
|
||||
avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false);
|
||||
avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false);
|
||||
}
|
||||
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0);
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, null);
|
||||
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
|
||||
|
||||
return new GroupActionResult(groupRecipient, threadId);
|
||||
|
@ -212,7 +212,7 @@ public class GroupMessageProcessor {
|
||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||
Address addres = Address.fromExternal(context, GroupUtil.getEncodedId(group.getGroupId(), false));
|
||||
Recipient recipient = Recipient.from(context, addres, false);
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, envelope.getTimestamp(), 0);
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, envelope.getTimestamp(), 0, null);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
|
||||
|
||||
|
@ -220,7 +220,7 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
|
||||
attachments.add(new UriAttachment(uri, Util.toIsoString(part.getContentType()),
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_DONE,
|
||||
part.getData().length, name, false));
|
||||
part.getData().length, name, false, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
@ -14,6 +14,7 @@ import android.util.Pair;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.ConversationListActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
@ -27,13 +28,17 @@ import org.thoughtcrime.securesms.database.MessagingDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
@ -53,6 +58,7 @@ import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libsignal.DuplicateMessageException;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
@ -87,6 +93,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -168,11 +175,11 @@ public class PushDecryptJob extends ContextJob {
|
||||
if (content.getDataMessage().isPresent()) {
|
||||
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||
|
||||
if (message.isEndSession()) handleEndSessionMessage(envelope, message, smsMessageId);
|
||||
else if (message.isGroupUpdate()) handleGroupMessage(envelope, message, smsMessageId);
|
||||
else if (message.isExpirationUpdate()) handleExpirationUpdate(envelope, message, smsMessageId);
|
||||
else if (message.getAttachments().isPresent()) handleMediaMessage(envelope, message, smsMessageId);
|
||||
else if (message.getBody().isPresent()) handleTextMessage(envelope, message, smsMessageId);
|
||||
if (message.isEndSession()) handleEndSessionMessage(envelope, message, smsMessageId);
|
||||
else if (message.isGroupUpdate()) handleGroupMessage(envelope, message, smsMessageId);
|
||||
else if (message.isExpirationUpdate()) handleExpirationUpdate(envelope, message, smsMessageId);
|
||||
else if (message.getAttachments().isPresent() || message.getQuote().isPresent()) handleMediaMessage(envelope, message, smsMessageId);
|
||||
else if (message.getBody().isPresent()) handleTextMessage(envelope, message, smsMessageId);
|
||||
|
||||
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) {
|
||||
handleUnknownGroupMessage(envelope, message.getGroupInfo().get());
|
||||
@ -398,7 +405,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
message.getExpiresInSeconds() * 1000L, true,
|
||||
Optional.fromNullable(envelope.getRelay()),
|
||||
Optional.absent(), message.getGroupInfo(),
|
||||
Optional.absent());
|
||||
Optional.absent(), Optional.absent());
|
||||
|
||||
|
||||
|
||||
@ -429,7 +436,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
threadId = GroupMessageProcessor.process(context, envelope, message.getMessage(), true);
|
||||
} else if (message.getMessage().isExpirationUpdate()) {
|
||||
threadId = handleSynchronizeSentExpirationUpdate(message);
|
||||
} else if (message.getMessage().getAttachments().isPresent()) {
|
||||
} else if (message.getMessage().getAttachments().isPresent() || message.getMessage().getQuote().isPresent()) {
|
||||
threadId = handleSynchronizeSentMediaMessage(message);
|
||||
} else {
|
||||
threadId = handleSynchronizeSentTextMessage(message);
|
||||
@ -517,13 +524,15 @@ public class PushDecryptJob extends ContextJob {
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Recipient recipient = getMessageDestination(envelope, message);
|
||||
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000L, false,
|
||||
Optional.fromNullable(envelope.getRelay()),
|
||||
message.getBody(),
|
||||
message.getGroupInfo(),
|
||||
message.getAttachments());
|
||||
message.getAttachments(),
|
||||
quote);
|
||||
|
||||
if (message.getExpiresInSeconds() != recipient.getExpireMessages()) {
|
||||
handleExpirationUpdate(envelope, message, Optional.absent());
|
||||
@ -573,11 +582,12 @@ public class PushDecryptJob extends ContextJob {
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Recipient recipients = getSyncMessageDestination(message);
|
||||
Optional<QuoteModel> quote = getValidatedQuote(message.getMessage().getQuote());
|
||||
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipients, message.getMessage().getBody().orNull(),
|
||||
PointerAttachment.forPointers(message.getMessage().getAttachments()),
|
||||
message.getTimestamp(), -1,
|
||||
message.getMessage().getExpiresInSeconds() * 1000L,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT);
|
||||
message.getMessage().getExpiresInSeconds() * 1000,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull());
|
||||
|
||||
mediaMessage = new OutgoingSecureMediaMessage(mediaMessage);
|
||||
|
||||
@ -664,7 +674,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
long messageId;
|
||||
|
||||
if (recipient.getAddress().isGroup()) {
|
||||
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT);
|
||||
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT, null);
|
||||
outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage);
|
||||
|
||||
messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null);
|
||||
@ -842,6 +852,51 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<QuoteModel> getValidatedQuote(Optional<SignalServiceDataMessage.Quote> quote) {
|
||||
if (!quote.isPresent()) return Optional.absent();
|
||||
|
||||
if (quote.get().getId() <= 0) {
|
||||
Log.w(TAG, "Received quote without an ID! Ignoring...");
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
if (quote.get().getAuthor() == null) {
|
||||
Log.w(TAG, "Received quote without an author! Ignoring...");
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);
|
||||
Address author = Address.fromExternal(context, quote.get().getAuthor().getNumber());
|
||||
|
||||
try (Cursor cursor = db.getMessagesFor(quote.get().getId())) {
|
||||
MmsSmsDatabase.Reader reader = db.readerFor(cursor);
|
||||
|
||||
MessageRecord messageRecord;
|
||||
|
||||
while ((messageRecord = reader.getNext()) != null) {
|
||||
if ((Util.isOwnNumber(context, author) && messageRecord.isOutgoing()) ||
|
||||
(!Util.isOwnNumber(context, author) && messageRecord.getIndividualRecipient().getAddress().equals(author)))
|
||||
{
|
||||
Log.w(TAG, "Found matching message record...");
|
||||
List<Attachment> attachments = new LinkedList<>();
|
||||
|
||||
if (messageRecord.isMms()) {
|
||||
attachments = ((MmsMessageRecord)messageRecord).getSlideDeck().asAttachments();
|
||||
}
|
||||
|
||||
return Optional.of(new QuoteModel(quote.get().getId(), author, messageRecord.getBody(), attachments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "Didn't find matching message record...");
|
||||
|
||||
return Optional.of(new QuoteModel(quote.get().getId(),
|
||||
author,
|
||||
quote.get().getText(),
|
||||
PointerAttachment.forPointers(Optional.of(quote.get().getAttachments()))));
|
||||
}
|
||||
|
||||
private Optional<InsertResult> insertPlaceholder(@NonNull SignalServiceEnvelope envelope) {
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
@ -861,6 +916,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Recipient getMessageDestination(SignalServiceEnvelope envelope, SignalServiceDataMessage message) {
|
||||
if (message.getGroupInfo().isPresent()) {
|
||||
return Recipient.from(context, Address.fromExternal(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)), false);
|
||||
|
@ -32,6 +32,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Quote;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
||||
@ -143,6 +144,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
List<Attachment> scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments());
|
||||
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(scaledAttachments);
|
||||
Optional<Quote> quote = getQuoteFor(message);
|
||||
|
||||
List<SignalServiceAddress> addresses;
|
||||
|
||||
@ -171,6 +173,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.asExpirationUpdate(message.isExpirationUpdate())
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.withQuote(quote.orNull())
|
||||
.build();
|
||||
|
||||
messageSender.sendMessage(addresses, groupMessage);
|
||||
|
@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
@ -17,14 +18,19 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@ -105,19 +111,21 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
}
|
||||
|
||||
try {
|
||||
SignalServiceAddress address = getPushAddress(message.getRecipient().getAddress());
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
List<Attachment> scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments());
|
||||
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(scaledAttachments);
|
||||
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
|
||||
SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withBody(message.getBody())
|
||||
.withAttachments(attachmentStreams)
|
||||
.withTimestamp(message.getSentTimeMillis())
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.asExpirationUpdate(message.isExpirationUpdate())
|
||||
.build();
|
||||
SignalServiceAddress address = getPushAddress(message.getRecipient().getAddress());
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
List<Attachment> scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments());
|
||||
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(scaledAttachments);
|
||||
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
|
||||
Optional<SignalServiceDataMessage.Quote> quote = getQuoteFor(message);
|
||||
SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withBody(message.getBody())
|
||||
.withAttachments(attachmentStreams)
|
||||
.withTimestamp(message.getSentTimeMillis())
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.withQuote(quote.orNull())
|
||||
.asExpirationUpdate(message.isExpirationUpdate())
|
||||
.build();
|
||||
|
||||
messageSender.sendMessage(address, mediaMessage);
|
||||
} catch (UnregisteredUserException e) {
|
||||
@ -131,4 +139,5 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
throw new RetryLaterException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,17 +14,24 @@ import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
@ -110,5 +117,45 @@ public abstract class PushSendJob extends SendJob {
|
||||
}
|
||||
}
|
||||
|
||||
protected Optional<SignalServiceDataMessage.Quote> getQuoteFor(OutgoingMediaMessage message) {
|
||||
if (message.getOutgoingQuote() == null) return Optional.absent();
|
||||
|
||||
long quoteId = message.getOutgoingQuote().getId();
|
||||
String quoteBody = message.getOutgoingQuote().getText();
|
||||
Address quoteAuthor = message.getOutgoingQuote().getAuthor();
|
||||
List<SignalServiceAttachment> quoteAttachments = new LinkedList<>();
|
||||
|
||||
for (Attachment attachment : message.getOutgoingQuote().getAttachments()) {
|
||||
BitmapUtil.ScaleResult attachmentData = null;
|
||||
|
||||
try {
|
||||
if (MediaUtil.isImageType(attachment.getContentType()) && attachment.getDataUri() != null) {
|
||||
attachmentData = BitmapUtil.createScaledBytes(context, new DecryptableStreamUriLoader.DecryptableUri(attachment.getDataUri()), 100, 100, 500 * 1024);
|
||||
} else if (MediaUtil.isVideoType(attachment.getContentType()) && attachment.getThumbnailUri() != null) {
|
||||
attachmentData = BitmapUtil.createScaledBytes(context, new DecryptableStreamUriLoader.DecryptableUri(attachment.getThumbnailUri()), 100, 100, 500 * 1024);
|
||||
}
|
||||
|
||||
if (attachmentData != null) {
|
||||
quoteAttachments.add(SignalServiceAttachment.newStreamBuilder()
|
||||
.withContentType("image/jpeg")
|
||||
.withFileName(attachment.getFileName())
|
||||
.withHeight(attachmentData.getHeight())
|
||||
.withWidth(attachmentData.getWidth())
|
||||
.withLength(attachmentData.getBitmap().length)
|
||||
.withStream(new ByteArrayInputStream(attachmentData.getBitmap()))
|
||||
.build());
|
||||
} else {
|
||||
quoteAttachments.add(new SignalServiceAttachmentPointer(0, attachment.getContentType(), null, null, Optional.absent(), Optional.absent(), 0, 0, Optional.absent(), Optional.fromNullable(attachment.getFileName()), attachment.isVoiceNote()));
|
||||
}
|
||||
|
||||
} catch (BitmapDecodingException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.of(new SignalServiceDataMessage.Quote(quoteId, new SignalServiceAddress(quoteAuthor.serialize()), quoteBody, quoteAttachments));
|
||||
}
|
||||
|
||||
|
||||
protected abstract void onPushSend() throws Exception;
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ import org.thoughtcrime.securesms.util.ResUtil;
|
||||
public class AudioSlide extends Slide {
|
||||
|
||||
public AudioSlide(Context context, Uri uri, long dataSize, boolean voiceNote) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.AUDIO_UNSPECIFIED, dataSize, 0, 0, false, null, voiceNote));
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.AUDIO_UNSPECIFIED, dataSize, 0, 0, false, null, voiceNote, false));
|
||||
}
|
||||
|
||||
public AudioSlide(Context context, Uri uri, long dataSize, String contentType, boolean voiceNote) {
|
||||
super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote));
|
||||
super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote, false));
|
||||
}
|
||||
|
||||
public AudioSlide(Context context, Attachment attachment) {
|
||||
|
@ -19,7 +19,7 @@ public class DocumentSlide extends Slide {
|
||||
@NonNull String contentType, long size,
|
||||
@Nullable String fileName)
|
||||
{
|
||||
super(context, constructAttachmentFromUri(context, uri, contentType, size, 0, 0, true, StorageUtil.getCleanFileName(fileName), false));
|
||||
super(context, constructAttachmentFromUri(context, uri, contentType, size, 0, 0, true, StorageUtil.getCleanFileName(fileName), false, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -14,7 +14,7 @@ public class GifSlide extends ImageSlide {
|
||||
}
|
||||
|
||||
public GifSlide(Context context, Uri uri, long size, int width, int height) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_GIF, size, width, height, true, null, false));
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_GIF, size, width, height, true, null, false, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,7 +37,7 @@ public class ImageSlide extends Slide {
|
||||
}
|
||||
|
||||
public ImageSlide(Context context, Uri uri, long size, int width, int height) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_JPEG, size, width, height, true, null, false));
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_JPEG, size, width, height, true, null, false, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -13,14 +13,15 @@ import java.util.List;
|
||||
|
||||
public class IncomingMediaMessage {
|
||||
|
||||
private final Address from;
|
||||
private final Address groupId;
|
||||
private final String body;
|
||||
private final boolean push;
|
||||
private final long sentTimeMillis;
|
||||
private final int subscriptionId;
|
||||
private final long expiresIn;
|
||||
private final boolean expirationUpdate;
|
||||
private final Address from;
|
||||
private final Address groupId;
|
||||
private final String body;
|
||||
private final boolean push;
|
||||
private final long sentTimeMillis;
|
||||
private final int subscriptionId;
|
||||
private final long expiresIn;
|
||||
private final boolean expirationUpdate;
|
||||
private final QuoteModel quote;
|
||||
|
||||
private final List<Attachment> attachments = new LinkedList<>();
|
||||
|
||||
@ -41,6 +42,7 @@ public class IncomingMediaMessage {
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresIn = expiresIn;
|
||||
this.expirationUpdate = expirationUpdate;
|
||||
this.quote = null;
|
||||
|
||||
this.attachments.addAll(attachments);
|
||||
}
|
||||
@ -53,7 +55,8 @@ public class IncomingMediaMessage {
|
||||
Optional<String> relay,
|
||||
Optional<String> body,
|
||||
Optional<SignalServiceGroup> group,
|
||||
Optional<List<SignalServiceAttachment>> attachments)
|
||||
Optional<List<SignalServiceAttachment>> attachments,
|
||||
Optional<QuoteModel> quote)
|
||||
{
|
||||
this.push = true;
|
||||
this.from = from;
|
||||
@ -62,6 +65,7 @@ public class IncomingMediaMessage {
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresIn = expiresIn;
|
||||
this.expirationUpdate = expirationUpdate;
|
||||
this.quote = quote.orNull();
|
||||
|
||||
if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false));
|
||||
else this.groupId = null;
|
||||
@ -108,4 +112,8 @@ public class IncomingMediaMessage {
|
||||
public boolean isGroupMessage() {
|
||||
return groupId != null;
|
||||
}
|
||||
|
||||
public QuoteModel getQuote() {
|
||||
return quote;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage
|
||||
|
||||
public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) {
|
||||
super(recipient, "", new LinkedList<Attachment>(), sentTimeMillis,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn);
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -21,11 +21,12 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
||||
@NonNull String encodedGroupContext,
|
||||
@NonNull List<Attachment> avatar,
|
||||
long sentTimeMillis,
|
||||
long expiresIn)
|
||||
long expiresIn,
|
||||
@Nullable QuoteModel quote)
|
||||
throws IOException
|
||||
{
|
||||
super(recipient, encodedGroupContext, avatar, sentTimeMillis,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn);
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, quote);
|
||||
|
||||
this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext));
|
||||
}
|
||||
@ -34,12 +35,13 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
||||
@NonNull GroupContext group,
|
||||
@Nullable final Attachment avatar,
|
||||
long sentTimeMillis,
|
||||
long expireIn)
|
||||
long expireIn,
|
||||
@Nullable QuoteModel quote)
|
||||
{
|
||||
super(recipient, Base64.encodeBytes(group.toByteArray()),
|
||||
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
|
||||
System.currentTimeMillis(),
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, expireIn);
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, expireIn, quote);
|
||||
|
||||
this.group = group;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
@ -16,11 +17,12 @@ public class OutgoingMediaMessage {
|
||||
private final int distributionType;
|
||||
private final int subscriptionId;
|
||||
private final long expiresIn;
|
||||
private final QuoteModel outgoingQuote;
|
||||
|
||||
public OutgoingMediaMessage(Recipient recipient, String message,
|
||||
List<Attachment> attachments, long sentTimeMillis,
|
||||
int subscriptionId, long expiresIn,
|
||||
int distributionType)
|
||||
int distributionType, @Nullable QuoteModel outgoingQuote)
|
||||
{
|
||||
this.recipient = recipient;
|
||||
this.body = message;
|
||||
@ -29,15 +31,16 @@ public class OutgoingMediaMessage {
|
||||
this.attachments = attachments;
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresIn = expiresIn;
|
||||
this.outgoingQuote = outgoingQuote;
|
||||
}
|
||||
|
||||
public OutgoingMediaMessage(Recipient recipient, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, long expiresIn, int distributionType)
|
||||
public OutgoingMediaMessage(Recipient recipient, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, long expiresIn, int distributionType, @Nullable QuoteModel outgoingQuote)
|
||||
{
|
||||
this(recipient,
|
||||
buildMessage(slideDeck, message),
|
||||
slideDeck.asAttachments(),
|
||||
sentTimeMillis, subscriptionId,
|
||||
expiresIn, distributionType);
|
||||
expiresIn, distributionType, outgoingQuote);
|
||||
}
|
||||
|
||||
public OutgoingMediaMessage(OutgoingMediaMessage that) {
|
||||
@ -48,6 +51,7 @@ public class OutgoingMediaMessage {
|
||||
this.sentTimeMillis = that.sentTimeMillis;
|
||||
this.subscriptionId = that.subscriptionId;
|
||||
this.expiresIn = that.expiresIn;
|
||||
this.outgoingQuote = that.outgoingQuote;
|
||||
}
|
||||
|
||||
public Recipient getRecipient() {
|
||||
@ -90,6 +94,10 @@ public class OutgoingMediaMessage {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public @Nullable QuoteModel getOutgoingQuote() {
|
||||
return outgoingQuote;
|
||||
}
|
||||
|
||||
private static String buildMessage(SlideDeck slideDeck, String message) {
|
||||
if (!TextUtils.isEmpty(message) && !TextUtils.isEmpty(slideDeck.getBody())) {
|
||||
return slideDeck.getBody() + "\n\n" + message;
|
||||
@ -99,4 +107,5 @@ public class OutgoingMediaMessage {
|
||||
return slideDeck.getBody();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
@ -11,9 +13,10 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
|
||||
List<Attachment> attachments,
|
||||
long sentTimeMillis,
|
||||
int distributionType,
|
||||
long expiresIn)
|
||||
long expiresIn,
|
||||
@Nullable QuoteModel quote)
|
||||
{
|
||||
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType);
|
||||
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote);
|
||||
}
|
||||
|
||||
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {
|
||||
|
40
src/org/thoughtcrime/securesms/mms/QuoteModel.java
Normal file
@ -0,0 +1,40 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QuoteModel {
|
||||
|
||||
private final long id;
|
||||
private final Address author;
|
||||
private final String text;
|
||||
private final List<Attachment> attachments;
|
||||
|
||||
public QuoteModel(long id, Address author, String text, @Nullable List<Attachment> attachments) {
|
||||
this.id = id;
|
||||
this.author = author;
|
||||
this.text = text;
|
||||
this.attachments = attachments;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Address getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public List<Attachment> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
}
|
@ -137,7 +137,8 @@ public abstract class Slide {
|
||||
int height,
|
||||
boolean hasThumbnail,
|
||||
@Nullable String fileName,
|
||||
boolean voiceNote)
|
||||
boolean voiceNote,
|
||||
boolean quote)
|
||||
{
|
||||
try {
|
||||
String resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime);
|
||||
@ -151,7 +152,8 @@ public abstract class Slide {
|
||||
height,
|
||||
fileName,
|
||||
fastPreflightId,
|
||||
voiceNote);
|
||||
voiceNote,
|
||||
quote);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
@ -31,14 +31,14 @@ public class SlideDeck {
|
||||
|
||||
private final List<Slide> slides = new LinkedList<>();
|
||||
|
||||
public SlideDeck(Context context, List<Attachment> attachments) {
|
||||
public SlideDeck(@NonNull Context context, @NonNull List<? extends Attachment> attachments) {
|
||||
for (Attachment attachment : attachments) {
|
||||
Slide slide = MediaUtil.getSlideForAttachment(context, attachment);
|
||||
if (slide != null) slides.add(slide);
|
||||
}
|
||||
}
|
||||
|
||||
public SlideDeck(Context context, Attachment attachment) {
|
||||
public SlideDeck(@NonNull Context context, @NonNull Attachment attachment) {
|
||||
Slide slide = MediaUtil.getSlideForAttachment(context, attachment);
|
||||
if (slide != null) slides.add(slide);
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.ResUtil;
|
||||
public class VideoSlide extends Slide {
|
||||
|
||||
public VideoSlide(Context context, Uri uri, long dataSize) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.VIDEO_UNSPECIFIED, dataSize, 0, 0, MediaUtil.hasVideoThumbnail(uri), null, false));
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.VIDEO_UNSPECIFIED, dataSize, 0, 0, MediaUtil.hasVideoThumbnail(uri), null, false, false));
|
||||
}
|
||||
|
||||
public VideoSlide(Context context, Attachment attachment) {
|
||||
|
@ -75,7 +75,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver {
|
||||
|
||||
if (recipient.isGroupRecipient()) {
|
||||
Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message");
|
||||
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0);
|
||||
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null);
|
||||
replyThreadId = MessageSender.send(context, reply, threadId, false, null);
|
||||
} else {
|
||||
Log.w("AndroidAutoReplyReceiver", "Sending regular message ");
|
||||
|
@ -68,7 +68,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
|
||||
long expiresIn = recipient.getExpireMessages() * 1000L;
|
||||
|
||||
if (recipient.isGroupRecipient()) {
|
||||
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0);
|
||||
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null);
|
||||
threadId = MessageSender.send(context, reply, -1, false, null);
|
||||
} else {
|
||||
OutgoingTextMessage reply = new OutgoingTextMessage(recipient, responseText.toString(), expiresIn, subscriptionId);
|
||||
|
@ -44,9 +44,19 @@ public class BitmapUtil {
|
||||
private static final int MAX_COMPRESSION_ATTEMPTS = 5;
|
||||
private static final int MIN_COMPRESSION_QUALITY_DECREASE = 5;
|
||||
|
||||
@android.support.annotation.WorkerThread
|
||||
@WorkerThread
|
||||
public static <T> ScaleResult createScaledBytes(Context context, T model, MediaConstraints constraints)
|
||||
throws BitmapDecodingException
|
||||
{
|
||||
return createScaledBytes(context, model,
|
||||
constraints.getImageMaxWidth(context),
|
||||
constraints.getImageMaxHeight(context),
|
||||
constraints.getImageMaxSize(context));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static <T> ScaleResult createScaledBytes(Context context, T model, int maxImageWidth, int maxImageHeight, int maxImageSize)
|
||||
throws BitmapDecodingException
|
||||
{
|
||||
try {
|
||||
int quality = MAX_COMPRESSION_QUALITY;
|
||||
@ -59,8 +69,7 @@ public class BitmapUtil {
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.downsample(DownsampleStrategy.AT_MOST)
|
||||
.submit(constraints.getImageMaxWidth(context),
|
||||
constraints.getImageMaxWidth(context))
|
||||
.submit(maxImageWidth, maxImageHeight)
|
||||
.get();
|
||||
|
||||
if (scaledBitmap == null) {
|
||||
@ -76,14 +85,14 @@ public class BitmapUtil {
|
||||
Log.w(TAG, "iteration with quality " + quality + " size " + (bytes.length / 1024) + "kb");
|
||||
if (quality == MIN_COMPRESSION_QUALITY) break;
|
||||
|
||||
int nextQuality = (int)Math.floor(quality * Math.sqrt((double)constraints.getImageMaxSize(context) / bytes.length));
|
||||
int nextQuality = (int)Math.floor(quality * Math.sqrt((double)maxImageSize / bytes.length));
|
||||
if (quality - nextQuality < MIN_COMPRESSION_QUALITY_DECREASE) {
|
||||
nextQuality = quality - MIN_COMPRESSION_QUALITY_DECREASE;
|
||||
}
|
||||
quality = Math.max(nextQuality, MIN_COMPRESSION_QUALITY);
|
||||
}
|
||||
while (bytes.length > constraints.getImageMaxSize(context) && attempts++ < MAX_COMPRESSION_ATTEMPTS);
|
||||
if (bytes.length > constraints.getImageMaxSize(context)) {
|
||||
while (bytes.length > maxImageSize && attempts++ < MAX_COMPRESSION_ATTEMPTS);
|
||||
if (bytes.length > maxImageSize) {
|
||||
throw new BitmapDecodingException("Unable to scale image below: " + bytes.length);
|
||||
}
|
||||
Log.w(TAG, "createScaledBytes(" + model.toString() + ") -> quality " + Math.min(quality, MAX_COMPRESSION_QUALITY) + ", " + attempts + " attempt(s)");
|
||||
|
@ -3,6 +3,9 @@ package org.thoughtcrime.securesms.util;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@ -39,4 +42,30 @@ public class JsonUtils {
|
||||
public static ObjectMapper getMapper() {
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
public static class SaneJSONObject {
|
||||
|
||||
private final JSONObject delegate;
|
||||
|
||||
public SaneJSONObject(JSONObject delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public String getString(String name) throws JSONException {
|
||||
if (delegate.isNull(name)) return null;
|
||||
else return delegate.getString(name);
|
||||
}
|
||||
|
||||
public long getLong(String name) throws JSONException {
|
||||
return delegate.getLong(name);
|
||||
}
|
||||
|
||||
public boolean isNull(String name) {
|
||||
return delegate.isNull(name);
|
||||
}
|
||||
|
||||
public int getInt(String name) throws JSONException {
|
||||
return delegate.getInt(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,78 +0,0 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.thoughtcrime.securesms.TextSecureTestCase;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyFloat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class AttachmentDatabaseTest extends TextSecureTestCase {
|
||||
private static final long ROW_ID = 1L;
|
||||
private static final long UNIQUE_ID = 2L;
|
||||
|
||||
private AttachmentDatabase database;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
database = spy(DatabaseFactory.getAttachmentDatabase(getInstrumentation().getTargetContext()));
|
||||
}
|
||||
|
||||
public void testThumbnailGenerationTaskNotRunWhenThumbnailExists() throws Exception {
|
||||
final AttachmentId attachmentId = new AttachmentId(ROW_ID, UNIQUE_ID);
|
||||
|
||||
DatabaseAttachment mockAttachment = getMockAttachment("x/x");
|
||||
when(database.getAttachment(null, attachmentId)).thenReturn(mockAttachment);
|
||||
|
||||
InputStream mockInputStream = mock(InputStream.class);
|
||||
doReturn(mockInputStream).when(database).getDataStream(any(MasterSecret.class), any(AttachmentId.class), eq("thumbnail"));
|
||||
database.getThumbnailStream(mock(MasterSecret.class), attachmentId);
|
||||
|
||||
// Works as the Future#get() call in AttachmentDatabase#getThumbnailStream() makes updating synchronous
|
||||
verify(database, never()).updateAttachmentThumbnail(any(MasterSecret.class), any(AttachmentId.class), any(InputStream.class), anyFloat());
|
||||
}
|
||||
|
||||
public void testThumbnailGenerationTaskRunWhenThumbnailMissing() throws Exception {
|
||||
final AttachmentId attachmentId = new AttachmentId(ROW_ID, UNIQUE_ID);
|
||||
|
||||
DatabaseAttachment mockAttachment = getMockAttachment("image/png");
|
||||
when(database.getAttachment(null, attachmentId)).thenReturn(mockAttachment);
|
||||
|
||||
doReturn(null).when(database).getDataStream(any(MasterSecret.class), any(AttachmentId.class), eq("thumbnail"));
|
||||
doNothing().when(database).updateAttachmentThumbnail(any(MasterSecret.class), any(AttachmentId.class), any(InputStream.class), anyFloat());
|
||||
|
||||
try {
|
||||
database.new ThumbnailFetchCallable(mock(MasterSecret.class), attachmentId).call();
|
||||
throw new AssertionError("Didn't try to generate thumbnail");
|
||||
} catch (BitmapDecodingException bde) {
|
||||
if (!(bde.getCause() instanceof FileNotFoundException)) {
|
||||
throw new AssertionError("Thumbnail generation failed for another reason than a FileNotFoundException: " + bde.getMessage());
|
||||
} // else success
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseAttachment getMockAttachment(String contentType) {
|
||||
DatabaseAttachment attachment = mock(DatabaseAttachment.class);
|
||||
when(attachment.getContentType()).thenReturn(contentType);
|
||||
when(attachment.getDataUri()).thenReturn(Uri.EMPTY);
|
||||
when(attachment.hasData()).thenReturn(true);
|
||||
|
||||
return attachment;
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
//package org.thoughtcrime.securesms.database;
|
||||
//
|
||||
//import org.thoughtcrime.securesms.TextSecureTestCase;
|
||||
//
|
||||
//import static org.assertj.core.api.Assertions.assertThat;
|
||||
//
|
||||
//public class CanonicalAddressDatabaseTest extends TextSecureTestCase {
|
||||
// private static final String AMBIGUOUS_NUMBER = "222-3333";
|
||||
// private static final String SPECIFIC_NUMBER = "+49 444 222 3333";
|
||||
// private static final String EMAIL = "a@b.fom";
|
||||
// private static final String SIMILAR_EMAIL = "a@b.com";
|
||||
// private static final String GROUP = "__textsecure_group__!000111222333";
|
||||
// private static final String SIMILAR_GROUP = "__textsecure_group__!100111222333";
|
||||
// private static final String ALPHA = "T-Mobile";
|
||||
// private static final String SIMILAR_ALPHA = "T-Mobila";
|
||||
//
|
||||
// private CanonicalAddressDatabase db;
|
||||
//
|
||||
// @Override
|
||||
// public void setUp() {
|
||||
// super.setUp();
|
||||
// this.db = CanonicalAddressDatabase.getInstance(getInstrumentation().getTargetContext());
|
||||
// }
|
||||
//
|
||||
// public void tearDown() throws Exception {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Throw two equivalent numbers (one without locale info, one with full info) at the canonical
|
||||
// * address db and see that the caching and DB operations work properly in revealing the right
|
||||
// * addresses. This is run twice to ensure cache logic is hit.
|
||||
// *
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public void testNumberAddressUpdates() throws Exception {
|
||||
// final long id = db.getCanonicalAddressId(AMBIGUOUS_NUMBER);
|
||||
//
|
||||
// assertThat(db.getAddressFromId(id)).isEqualTo(AMBIGUOUS_NUMBER);
|
||||
// assertThat(db.getCanonicalAddressId(SPECIFIC_NUMBER)).isEqualTo(id);
|
||||
// assertThat(db.getAddressFromId(id)).isEqualTo(SPECIFIC_NUMBER);
|
||||
// assertThat(db.getCanonicalAddressId(AMBIGUOUS_NUMBER)).isEqualTo(id);
|
||||
//
|
||||
// assertThat(db.getCanonicalAddressId(AMBIGUOUS_NUMBER)).isEqualTo(id);
|
||||
// assertThat(db.getAddressFromId(id)).isEqualTo(AMBIGUOUS_NUMBER);
|
||||
// assertThat(db.getCanonicalAddressId(SPECIFIC_NUMBER)).isEqualTo(id);
|
||||
// assertThat(db.getAddressFromId(id)).isEqualTo(SPECIFIC_NUMBER);
|
||||
// assertThat(db.getCanonicalAddressId(AMBIGUOUS_NUMBER)).isEqualTo(id);
|
||||
// }
|
||||
//
|
||||
// public void testSimilarNumbers() throws Exception {
|
||||
// assertThat(db.getCanonicalAddressId("This is a phone number 222-333-444"))
|
||||
// .isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
// assertThat(db.getCanonicalAddressId("222-333-444"))
|
||||
// .isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
// assertThat(db.getCanonicalAddressId("222-333-44"))
|
||||
// .isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
// assertThat(db.getCanonicalAddressId("222-333-4"))
|
||||
// .isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
// assertThat(db.getCanonicalAddressId("+49 222-333-4444"))
|
||||
// .isNotEqualTo(db.getCanonicalAddressId("+1 222-333-4444"));
|
||||
//
|
||||
// assertThat(db.getCanonicalAddressId("1 222-333-4444"))
|
||||
// .isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
// assertThat(db.getCanonicalAddressId("1 (222) 333-4444"))
|
||||
// .isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
// assertThat(db.getCanonicalAddressId("+12223334444"))
|
||||
// .isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
// assertThat(db.getCanonicalAddressId("+1 (222) 333.4444"))
|
||||
// .isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
// assertThat(db.getCanonicalAddressId("+49 (222) 333.4444"))
|
||||
// .isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
//
|
||||
// }
|
||||
//
|
||||
// public void testEmailAddresses() throws Exception {
|
||||
// final long emailId = db.getCanonicalAddressId(EMAIL);
|
||||
// final long similarEmailId = db.getCanonicalAddressId(SIMILAR_EMAIL);
|
||||
//
|
||||
// assertThat(emailId).isNotEqualTo(similarEmailId);
|
||||
//
|
||||
// assertThat(db.getAddressFromId(emailId)).isEqualTo(EMAIL);
|
||||
// assertThat(db.getAddressFromId(similarEmailId)).isEqualTo(SIMILAR_EMAIL);
|
||||
// }
|
||||
//
|
||||
// public void testGroups() throws Exception {
|
||||
// final long groupId = db.getCanonicalAddressId(GROUP);
|
||||
// final long similarGroupId = db.getCanonicalAddressId(SIMILAR_GROUP);
|
||||
//
|
||||
// assertThat(groupId).isNotEqualTo(similarGroupId);
|
||||
//
|
||||
// assertThat(db.getAddressFromId(groupId)).isEqualTo(GROUP);
|
||||
// assertThat(db.getAddressFromId(similarGroupId)).isEqualTo(SIMILAR_GROUP);
|
||||
// }
|
||||
//
|
||||
// public void testAlpha() throws Exception {
|
||||
// final long id = db.getCanonicalAddressId(ALPHA);
|
||||
// final long similarId = db.getCanonicalAddressId(SIMILAR_ALPHA);
|
||||
//
|
||||
// assertThat(id).isNotEqualTo(similarId);
|
||||
//
|
||||
// assertThat(db.getAddressFromId(id)).isEqualTo(ALPHA);
|
||||
// assertThat(db.getAddressFromId(similarId)).isEqualTo(SIMILAR_ALPHA);
|
||||
// }
|
||||
//
|
||||
// public void testIsNumber() throws Exception {
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("+495556666777")).isTrue();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("(222) 333-4444")).isTrue();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("1 (222) 333-4444")).isTrue();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("T-Mobile123")).isTrue();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("333-4444")).isTrue();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("12345")).isTrue();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("T-Mobile")).isFalse();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("T-Mobile1")).isFalse();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("Wherever bank")).isFalse();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("__textsecure_group__!afafafafafaf")).isFalse();
|
||||
// assertThat(CanonicalAddressDatabase.isNumberAddress("email@domain.com")).isFalse();
|
||||
// }
|
||||
//}
|