diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 493fd573a67510cc6b2d50ddf4d10878025a0eb9..31ae95df3d77e965290c99a72a951d9d18240548 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -74,6 +74,7 @@ + diff --git a/res/drawable-hdpi/ic_attach_grey600_24dp.png b/res/drawable-hdpi/ic_attach_grey600_24dp.png deleted file mode 100644 index 13c7f0c28dd4ccd70cecb059b1fc70ed11a86fea..0000000000000000000000000000000000000000 Binary files a/res/drawable-hdpi/ic_attach_grey600_24dp.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_file_attachment_dark.png b/res/drawable-hdpi/ic_file_attachment_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d3139364996a65f8004a165148e882e42dda2766 Binary files /dev/null and b/res/drawable-hdpi/ic_file_attachment_dark.png differ diff --git a/res/drawable-hdpi/ic_file_attachment_light.png b/res/drawable-hdpi/ic_file_attachment_light.png new file mode 100644 index 0000000000000000000000000000000000000000..5261267fb1102225ce46913346a893e80faecbd7 Binary files /dev/null and b/res/drawable-hdpi/ic_file_attachment_light.png differ diff --git a/res/drawable-hdpi/ic_file_dark.png b/res/drawable-hdpi/ic_file_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d1000674624ddd8f41737856a210bd5d4b4d5a47 Binary files /dev/null and b/res/drawable-hdpi/ic_file_dark.png differ diff --git a/res/drawable-hdpi/ic_file_light.png b/res/drawable-hdpi/ic_file_light.png new file mode 100644 index 0000000000000000000000000000000000000000..a00baf46eb188af480c7dd0242c6caa76bf7e846 Binary files /dev/null and b/res/drawable-hdpi/ic_file_light.png differ diff --git a/res/drawable-mdpi/ic_attach_grey600_24dp.png b/res/drawable-mdpi/ic_attach_grey600_24dp.png deleted file mode 100644 index 0ea546b1f94beb88a43af1267c2ec87c84b11792..0000000000000000000000000000000000000000 Binary files a/res/drawable-mdpi/ic_attach_grey600_24dp.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_file_attachment_dark.png b/res/drawable-mdpi/ic_file_attachment_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..23d012adc4b98b6b17962a0093ce0dac8cf7c906 Binary files /dev/null and b/res/drawable-mdpi/ic_file_attachment_dark.png differ diff --git a/res/drawable-mdpi/ic_file_attachment_light.png b/res/drawable-mdpi/ic_file_attachment_light.png new file mode 100644 index 0000000000000000000000000000000000000000..cd12b0e1789765cf57af99da9f86401fda6b4ad5 Binary files /dev/null and b/res/drawable-mdpi/ic_file_attachment_light.png differ diff --git a/res/drawable-mdpi/ic_file_dark.png b/res/drawable-mdpi/ic_file_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..657e7a960c21713fccd07de70683b8fedfac8785 Binary files /dev/null and b/res/drawable-mdpi/ic_file_dark.png differ diff --git a/res/drawable-mdpi/ic_file_light.png b/res/drawable-mdpi/ic_file_light.png new file mode 100644 index 0000000000000000000000000000000000000000..9eb60e9eba41b0228b819f1a66d3b12590e06e57 Binary files /dev/null and b/res/drawable-mdpi/ic_file_light.png differ diff --git a/res/drawable-xhdpi/ic_attach_grey600_24dp.png b/res/drawable-xhdpi/ic_attach_grey600_24dp.png deleted file mode 100644 index 28aef9e9b1c20bf3dbd0046e1fa59dea46b5bc55..0000000000000000000000000000000000000000 Binary files a/res/drawable-xhdpi/ic_attach_grey600_24dp.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_file_attachment_dark.png b/res/drawable-xhdpi/ic_file_attachment_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a9341832112d5a3809a1391050e53ad0da6a4a1c Binary files /dev/null and b/res/drawable-xhdpi/ic_file_attachment_dark.png differ diff --git a/res/drawable-xhdpi/ic_file_attachment_light.png b/res/drawable-xhdpi/ic_file_attachment_light.png new file mode 100644 index 0000000000000000000000000000000000000000..6237f46349d68b34589112f9a32b0b9d44256903 Binary files /dev/null and b/res/drawable-xhdpi/ic_file_attachment_light.png differ diff --git a/res/drawable-xhdpi/ic_file_dark.png b/res/drawable-xhdpi/ic_file_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b1d32899b65bb03371b23f21709382f72970979c Binary files /dev/null and b/res/drawable-xhdpi/ic_file_dark.png differ diff --git a/res/drawable-xhdpi/ic_file_light.png b/res/drawable-xhdpi/ic_file_light.png new file mode 100644 index 0000000000000000000000000000000000000000..15a87c4a7afaf74f639b3fd26afd6db121e5c11f Binary files /dev/null and b/res/drawable-xhdpi/ic_file_light.png differ diff --git a/res/drawable-xxhdpi/ic_attach_grey600_24dp.png b/res/drawable-xxhdpi/ic_attach_grey600_24dp.png deleted file mode 100644 index 7b0785e116d1b744600ae5b74aad60d36c0d9035..0000000000000000000000000000000000000000 Binary files a/res/drawable-xxhdpi/ic_attach_grey600_24dp.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_file_attachment_dark.png b/res/drawable-xxhdpi/ic_file_attachment_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1efba0a65f1c5bf90d0feb3b579652fd74abfeb0 Binary files /dev/null and b/res/drawable-xxhdpi/ic_file_attachment_dark.png differ diff --git a/res/drawable-xxhdpi/ic_file_attachment_light.png b/res/drawable-xxhdpi/ic_file_attachment_light.png new file mode 100644 index 0000000000000000000000000000000000000000..36b2d14f50e7abf55669df434824473f88615e2c Binary files /dev/null and b/res/drawable-xxhdpi/ic_file_attachment_light.png differ diff --git a/res/drawable-xxhdpi/ic_file_dark.png b/res/drawable-xxhdpi/ic_file_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9a982fee52c011185d599192b920d00867dfd7a5 Binary files /dev/null and b/res/drawable-xxhdpi/ic_file_dark.png differ diff --git a/res/drawable-xxhdpi/ic_file_light.png b/res/drawable-xxhdpi/ic_file_light.png new file mode 100644 index 0000000000000000000000000000000000000000..9d4172b40fb3dabfa6a137fc31b7dd47e10f3f0d Binary files /dev/null and b/res/drawable-xxhdpi/ic_file_light.png differ diff --git a/res/drawable-xxxhdpi/ic_file_dark.png b/res/drawable-xxxhdpi/ic_file_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5858d5a764c30973ae2ec9a2986c93e712f041fe Binary files /dev/null and b/res/drawable-xxxhdpi/ic_file_dark.png differ diff --git a/res/drawable-xxxhdpi/ic_file_light.png b/res/drawable-xxxhdpi/ic_file_light.png new file mode 100644 index 0000000000000000000000000000000000000000..9622ae38118be94cbaa008be1d51b73eaeb61914 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_file_light.png differ diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 341d02395b90f269bd328c59bcd8f65d22d12c38..71337ecdf10a208e34d468ad4ce9955d5d2c262b 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -40,7 +40,7 @@ - + @@ -71,6 +71,7 @@ + diff --git a/res/values/strings.xml b/res/values/strings.xml index c0ae806213fbcac58a5af17b59deff3ba4098a48..219bc6bd008e20912a20a7e0ba0a0d6c7beadf67 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -49,6 +49,7 @@ (image) (audio) (video) + (file) Can\'t find an app to select media. @@ -58,6 +59,7 @@ Picture Video Audio + File Contact info @@ -118,6 +120,7 @@ MMS not supported This message cannot be sent since your carrier doesn\'t support MMS. Please choose a contact + Sorry, files cannot be attached to insecure conversations. Attachment exceeds size limits. diff --git a/res/values/themes.xml b/res/values/themes.xml index 5747d433fd76bad672614f554565a45f59605b92..e2f6090a33701cc1889bd62099e097e8fbddf6dd 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -116,7 +116,7 @@ @drawable/ic_movie_creation_light @drawable/ic_volume_up_light @drawable/ic_account_box_light - @drawable/ic_attach_grey600_24dp + @drawable/ic_file_attachment_light @color/gray12 #66555555 @@ -172,6 +172,7 @@ @drawable/ic_audio_light @drawable/ic_video_light + @drawable/ic_file_light #ff1d85d7 @@ -248,7 +249,7 @@ @drawable/ic_movie_creation_dark @drawable/ic_volume_up_dark @drawable/ic_account_box_dark - @drawable/ic_attach_white_24dp + @drawable/ic_file_attachment_dark @color/gray95 @color/gray65 @@ -285,6 +286,7 @@ @drawable/ic_audio_dark @drawable/ic_video_dark + @drawable/ic_file_dark @color/smssecure_primary_dark diff --git a/src/org/smssecure/smssecure/ConversationActivity.java b/src/org/smssecure/smssecure/ConversationActivity.java index cc64dc0d240a878f2e481e6fed5a9b977f94837b..b7747cb898063fed5fb29408b8337b23633167ab 100644 --- a/src/org/smssecure/smssecure/ConversationActivity.java +++ b/src/org/smssecure/smssecure/ConversationActivity.java @@ -147,6 +147,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public static final String THREAD_ID_EXTRA = "thread_id"; public static final String IS_ARCHIVED_EXTRA = "is_archived"; public static final String TEXT_EXTRA = "draft_text"; + public static final String FILENAME_EXTRA = "filename"; public static final String DISTRIBUTION_TYPE_EXTRA = "distribution_type"; private static final int PICK_IMAGE = 1; @@ -156,6 +157,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private static final int GROUP_EDIT = 5; private static final int TAKE_PHOTO = 6; private static final int ADD_CONTACT = 7; + private static final int PICK_FILE = 8; private MasterSecret masterSecret; protected ComposeText composeText; @@ -314,6 +316,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity recipients.addListener(this); fragment.reloadList(); break; + case PICK_FILE: + setMedia(data.getData(), MediaType.FILE); + break; } } @@ -678,10 +683,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void initializeDraft() { final String draftText = getIntent().getStringExtra(TEXT_EXTRA); final Uri draftMedia = getIntent().getData(); + final String draftFilename = getIntent().getStringExtra(FILENAME_EXTRA); final MediaType draftMediaType = MediaType.from(getIntent().getType()); if (draftText != null) composeText.setText(draftText); - if (draftMedia != null && draftMediaType != null) setMedia(draftMedia, draftMediaType); + if (draftMedia != null && draftMediaType != null){ + Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient(); + boolean isSecureSmsDestination = isSingleConversation() && SessionUtil.hasSession(this, masterSecret, primaryRecipient); + if (draftMediaType.equals(MediaType.FILE) && !isSecureSmsDestination) { + Toast.makeText(this, R.string.ConversationActivity_sorry_no_files_attaching_on_insecure_chat, Toast.LENGTH_LONG).show(); + } else if (draftFilename != null) { + setMedia(draftMedia, draftMediaType, draftFilename); + } else { + setMedia(draftMedia, draftMediaType); + } + } if (draftText == null && draftMedia == null && draftMediaType == null) { initializeDraftFromDatabase(); @@ -720,6 +736,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO); } else if (draft.getType().equals(Draft.VIDEO)) { setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO); + } else if (draft.getType().equals(Draft.FILE)) { + setMedia(Uri.parse(draft.getValue()) , MediaType.FILE); } } @@ -739,7 +757,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } else { this.isEncryptedConversation = false; } - + attachmentAdapter.setSecureDestination(isSecureSmsDestination); sendButton.resetAvailableTransports(isMediaMessage); if (!isSecureSmsDestination ) sendButton.disableTransport(Type.SECURE_SMS); if (recipients.isGroupRecipient()) sendButton.disableTransport(Type.INSECURE_SMS); @@ -806,7 +824,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity composeBubble.getBackground().setColorFilter(defaultColor, PorterDuff.Mode.MULTIPLY); colors.recycle(); - attachmentAdapter = new AttachmentTypeSelectorAdapter(this); + Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient(); + boolean isSecureSmsDestination = isSingleConversation() && SessionUtil.hasSession(this, masterSecret, primaryRecipient); + + attachmentAdapter = new AttachmentTypeSelectorAdapter(this, isSecureSmsDestination); attachmentManager = new AttachmentManager(this, this); SendButtonListener sendButtonListener = new SendButtonListener(); @@ -952,6 +973,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity AttachmentManager.selectContactInfo(this, PICK_CONTACT_INFO); break; case AttachmentTypeSelectorAdapter.TAKE_PHOTO: attachmentManager.capturePhoto(this, TAKE_PHOTO); break; + case AttachmentTypeSelectorAdapter.ADD_FILE: + attachmentManager.selectFile(masterSecret, this, PICK_FILE); break; } } @@ -959,6 +982,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity attachmentManager.setMedia(masterSecret, uri, mediaType, getCurrentMediaConstraints()); } + private void setMedia(Uri uri, MediaType mediaType, String fileName) { + attachmentManager.setMedia(masterSecret, uri, mediaType, getCurrentMediaConstraints(), fileName); + } + private void addAttachmentContactInfo(Uri contactUri) { ContactAccessor contactDataList = ContactAccessor.getInstance(); ContactData contactData = contactDataList.getContactData(this, contactUri); @@ -999,6 +1026,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity for (Slide slide : attachmentManager.buildSlideDeck().getSlides()) { if (slide.hasAudio()) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString())); else if (slide.hasVideo()) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString())); + else if (slide.hasFile() ) drafts.add(new Draft(Draft.FILE, slide.getUri().toString())); else if (slide.hasImage()) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString())); } diff --git a/src/org/smssecure/smssecure/ConversationFragment.java b/src/org/smssecure/smssecure/ConversationFragment.java index 04a1a15e39898c91b20361eea89af6d2cc3b5bb0..955bcbcceefe0989044591b44ff07c6fefa0741f 100644 --- a/src/org/smssecure/smssecure/ConversationFragment.java +++ b/src/org/smssecure/smssecure/ConversationFragment.java @@ -340,9 +340,9 @@ public class ConversationFragment extends Fragment SaveAttachmentTask.showWarningDialog(getActivity(), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { for (Slide slide : message.getSlideDeck().getSlides()) { - if (slide.hasImage() || slide.hasVideo() || slide.hasAudio()) { + if (slide.hasImage() || slide.hasVideo() || slide.hasAudio() || slide.hasFile()) { SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity(), masterSecret); - saveTask.execute(new Attachment(slide.getUri(), slide.getContentType(), message.getDateReceived())); + saveTask.execute(new Attachment(slide.getUri(), slide.getContentType(), message.getDateReceived(), slide.getFileName())); return; } } diff --git a/src/org/smssecure/smssecure/ConversationItem.java b/src/org/smssecure/smssecure/ConversationItem.java index 76034bee723791e8e9f9e9eef8739826ceda2a30..a6adc33a86fbc9d504448f4f3c6e1a2dafb19159 100644 --- a/src/org/smssecure/smssecure/ConversationItem.java +++ b/src/org/smssecure/smssecure/ConversationItem.java @@ -63,16 +63,20 @@ import org.smssecure.smssecure.protocol.AutoInitiate; import org.smssecure.smssecure.recipients.Recipient; import org.smssecure.smssecure.recipients.Recipients; import org.smssecure.smssecure.util.DateUtils; +import org.smssecure.smssecure.util.SaveAttachmentTask; import org.smssecure.smssecure.util.TelephonyUtil; import org.smssecure.smssecure.util.Util; import org.smssecure.smssecure.util.dualsim.SubscriptionInfoCompat; import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat; import org.whispersystems.libaxolotl.util.guava.Optional; +import java.util.Date; import java.util.HashSet; import java.util.Locale; import java.util.Set; +import ws.com.google.android.mms.ContentType; + /** * A view that displays an individual conversation item within a conversation * thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter. @@ -525,6 +529,15 @@ public class ConversationItem extends LinearLayout intent.putExtra(MediaPreviewActivity.DATE_EXTRA, messageRecord.getTimestamp()); context.startActivity(intent); + } else if (slide.hasFile()) { + SaveAttachmentTask.showWarningDialog(context, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + SaveAttachmentTask saveTask = new SaveAttachmentTask(context, masterSecret); + saveTask.execute(new SaveAttachmentTask.Attachment(slide.getUri(), slide.getContentType(), new Date().getTime(), slide.getFileName())); + return; + } + }); + } else { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.ConversationItem_view_secure_media_question); diff --git a/src/org/smssecure/smssecure/MediaOverviewActivity.java b/src/org/smssecure/smssecure/MediaOverviewActivity.java index 152098702db19b57ff522018f795ab2d37f51ce2..27dbe5a2beab03c5702ef78037c78ace2a50cb09 100644 --- a/src/org/smssecure/smssecure/MediaOverviewActivity.java +++ b/src/org/smssecure/smssecure/MediaOverviewActivity.java @@ -164,7 +164,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i ImageRecord record = ImageRecord.from(cursor); attachments.add(new SaveAttachmentTask.Attachment(record.getAttachment().getDataUri(), record.getContentType(), - record.getDate())); + record.getDate(), null)); } return attachments; diff --git a/src/org/smssecure/smssecure/MediaPreviewActivity.java b/src/org/smssecure/smssecure/MediaPreviewActivity.java index 61be05d4624f86afe6f589e54083b4401fddc303..b79102a3087be49bb6902ca38772e06e509bbade 100644 --- a/src/org/smssecure/smssecure/MediaPreviewActivity.java +++ b/src/org/smssecure/smssecure/MediaPreviewActivity.java @@ -171,7 +171,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im @Override public void onClick(DialogInterface dialogInterface, int i) { SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this, masterSecret); - saveTask.execute(new Attachment(mediaUri, mediaType, date)); + saveTask.execute(new Attachment(mediaUri, mediaType, date, null)); } }); } diff --git a/src/org/smssecure/smssecure/ShareActivity.java b/src/org/smssecure/smssecure/ShareActivity.java index 8d983952367263afa42fb246db650aa68c212074..52b963164fa9570916c0fa9b8303f1122d42b39a 100644 --- a/src/org/smssecure/smssecure/ShareActivity.java +++ b/src/org/smssecure/smssecure/ShareActivity.java @@ -31,6 +31,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import org.smssecure.smssecure.attachments.UriAttachment; import org.smssecure.smssecure.crypto.MasterSecret; import org.smssecure.smssecure.mms.PartAuthority; import org.smssecure.smssecure.providers.PersistentBlobProvider; @@ -43,6 +44,8 @@ import org.smssecure.smssecure.util.ViewUtil; import java.io.IOException; import java.io.InputStream; +import ws.com.google.android.mms.ContentType; + /** * An activity to quickly share content with contacts * @@ -62,6 +65,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity private Uri resolvedExtra; private String mimeType; private boolean isPassingAlongMedia; + private String resolvedFilename; @Override protected void onPreCreate() { @@ -112,7 +116,9 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity isPassingAlongMedia = false; Uri streamExtra = getIntent().getParcelableExtra(Intent.EXTRA_STREAM); - mimeType = getMimeType(streamExtra); + mimeType = streamExtra != null && !ContentType.isTextType(getMimeType(streamExtra)) ? getMimeType(streamExtra) + : ContentType.SMS_SECURE_FILE; + resolvedFilename = UriAttachment.getFilenameFromUri(streamExtra, context); if (streamExtra != null && PartAuthority.isLocalUri(streamExtra)) { isPassingAlongMedia = true; resolvedExtra = streamExtra; @@ -171,6 +177,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity final String textExtra = getIntent().getStringExtra(Intent.EXTRA_TEXT); intent.putExtra(ConversationActivity.TEXT_EXTRA, textExtra); if (resolvedExtra != null) intent.setDataAndType(resolvedExtra, mimeType); + if (resolvedFilename != null) intent.putExtra(ConversationActivity.FILENAME_EXTRA, resolvedFilename); return intent; } diff --git a/src/org/smssecure/smssecure/attachments/Attachment.java b/src/org/smssecure/smssecure/attachments/Attachment.java index 73c901a6c7e0d04babf3c6a7178162b46b6adf7f..30941faa2651b5f4ef82d810f472f611c492734a 100644 --- a/src/org/smssecure/smssecure/attachments/Attachment.java +++ b/src/org/smssecure/smssecure/attachments/Attachment.java @@ -13,6 +13,7 @@ public abstract class Attachment { private final String contentType; private final int transferState; private final long size; + private final String fileName; @Nullable private final String location; @@ -28,7 +29,7 @@ public abstract class Attachment { private Bitmap thumbnail; public Attachment(@NonNull String contentType, int transferState, long size, - @Nullable String location, @Nullable String key, @Nullable String relay) + @Nullable String location, @Nullable String key, @Nullable String relay, @Nullable String fileName) { this.contentType = contentType; this.transferState = transferState; @@ -36,6 +37,7 @@ public abstract class Attachment { this.location = location; this.key = key; this.relay = relay; + this.fileName = fileName; } @Nullable @@ -85,4 +87,9 @@ public abstract class Attachment { public Bitmap getThumbnail() { return thumbnail; } + + @Nullable + public String getFileName(){ + return fileName; + } } diff --git a/src/org/smssecure/smssecure/attachments/DatabaseAttachment.java b/src/org/smssecure/smssecure/attachments/DatabaseAttachment.java index 388c6a76020b46fab189297d59d59bc12e74f035..c9987de597fbfd60a91912a8863741b9612f0d8c 100644 --- a/src/org/smssecure/smssecure/attachments/DatabaseAttachment.java +++ b/src/org/smssecure/smssecure/attachments/DatabaseAttachment.java @@ -13,14 +13,21 @@ public class DatabaseAttachment extends Attachment { public DatabaseAttachment(AttachmentId attachmentId, long mmsId, boolean hasData, String contentType, int transferProgress, long size, - String location, String key, String relay) + String location, String key, String relay, String fileName) { - super(contentType, transferProgress, size, location, key, relay); + super(contentType, transferProgress, size, location, key, relay, fileName); this.attachmentId = attachmentId; this.hasData = hasData; this.mmsId = mmsId; } + public DatabaseAttachment(AttachmentId attachmentId, long mmsId, boolean hasData, + String contentType, int transferProgress, long size, + String location, String key, String relay) + { + this(attachmentId, mmsId, hasData, contentType, transferProgress, size, location, key, relay, null); + } + @Override @NonNull public Uri getDataUri() { diff --git a/src/org/smssecure/smssecure/attachments/UriAttachment.java b/src/org/smssecure/smssecure/attachments/UriAttachment.java index a366a6d19732a8513ee8402d5726b59b1ab3d62c..167ae75732ae309fc6b8517720ea74230f355ef2 100644 --- a/src/org/smssecure/smssecure/attachments/UriAttachment.java +++ b/src/org/smssecure/smssecure/attachments/UriAttachment.java @@ -1,31 +1,40 @@ package org.smssecure.smssecure.attachments; import android.content.Context; +import android.database.Cursor; import android.net.Uri; +import android.provider.OpenableColumns; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; -import org.smssecure.smssecure.crypto.MasterSecret; -import org.smssecure.smssecure.util.MediaUtil; -import org.whispersystems.libaxolotl.util.guava.Optional; - -import java.io.IOException; -import java.io.InputStream; +import ws.com.google.android.mms.ContentType; public class UriAttachment extends Attachment { + private static final String TAG = UriAttachment.class.getSimpleName(); + private final @NonNull Uri dataUri; private final @NonNull Uri thumbnailUri; - public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size) { - this(uri, uri, contentType, transferState, size); + public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size, Context context) { + this(uri, uri, contentType, transferState, size, UriAttachment.getFilenameFromUri(uri, context)); + } + + public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size, String inputFilename) { + this(uri, uri, contentType, transferState, size, inputFilename); } public UriAttachment(@NonNull Uri dataUri, @NonNull Uri thumbnailUri, - @NonNull String contentType, int transferState, long size) + @NonNull String contentType, int transferState, long size, @Nullable String fileName) { - super(contentType, transferState, size, null, null, null); + super(contentType, transferState, size, null, null, null, fileName); this.dataUri = dataUri; - this.thumbnailUri = thumbnailUri; + if(!ContentType.isVendorFileType(contentType)) { + this.thumbnailUri = thumbnailUri; + } else { + this.thumbnailUri = null; + } } @Override @@ -49,4 +58,27 @@ public class UriAttachment extends Attachment { public int hashCode() { return dataUri.hashCode(); } + + public static String getFilenameFromUri(Uri uri, Context context) { + String fileName = null; + if (uri != null && uri.getScheme() != null && uri.getScheme().equals("content")) { + Log.w(TAG, "contenturi: "+uri.toString()); + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } finally { + if (cursor != null) cursor.close(); + } + } + if (fileName == null) { + fileName = uri.getPath(); + int cut = fileName != null ? fileName.lastIndexOf('/') : -1; + if (cut != -1) { + fileName = fileName.substring(cut + 1); + } + } + return fileName; + } } diff --git a/src/org/smssecure/smssecure/components/ThumbnailView.java b/src/org/smssecure/smssecure/components/ThumbnailView.java index 4096912a71d469fb807a74515e1479a5a5369493..a85ab91b2163cb679b02de7df0fc65dcca916f57 100644 --- a/src/org/smssecure/smssecure/components/ThumbnailView.java +++ b/src/org/smssecure/smssecure/components/ThumbnailView.java @@ -118,9 +118,9 @@ public class ThumbnailView extends FrameLayout { this.slide = slide; - if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(slide, masterSecret).into(image); - else if (slide.hasPlaceholder()) buildPlaceholderGlideRequest(slide).into(image); - else Glide.clear(image); + if (slide.getThumbnailUri() != null && !slide.hasFile()) buildThumbnailGlideRequest(slide, masterSecret).into(image); + else if (slide.hasPlaceholder()) buildPlaceholderGlideRequest(slide).into(image); + else Glide.clear(image); } public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull Uri uri) { diff --git a/src/org/smssecure/smssecure/database/AttachmentDatabase.java b/src/org/smssecure/smssecure/database/AttachmentDatabase.java index cce5ecf3325b5bab3dcc491534d2dfbcef39f2d7..f7baa6c0bcfe52c495dc2e9d8393307e6d9d6c77 100644 --- a/src/org/smssecure/smssecure/database/AttachmentDatabase.java +++ b/src/org/smssecure/smssecure/database/AttachmentDatabase.java @@ -65,6 +65,7 @@ public class AttachmentDatabase extends Database { static final String MMS_ID = "mid"; static final String CONTENT_TYPE = "ct"; static final String NAME = "name"; + static final String FILENAME = "filename"; static final String CONTENT_DISPOSITION = "cd"; static final String CONTENT_LOCATION = "cl"; static final String DATA = "_data"; @@ -85,11 +86,11 @@ public class AttachmentDatabase extends Database { MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION, CONTENT_LOCATION, DATA, TRANSFER_STATE, SIZE, THUMBNAIL, THUMBNAIL_ASPECT_RATIO, - UNIQUE_ID}; + UNIQUE_ID, FILENAME}; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " + MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " + - CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + "chset" + " INTEGER, " + + CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + FILENAME + " TEXT, " + "chset" + " INTEGER, " + CONTENT_DISPOSITION + " TEXT, " + "fn" + " TEXT, " + "cid" + " TEXT, " + CONTENT_LOCATION + " TEXT, " + "ctt_s" + " INTEGER, " + "ctt_t" + " TEXT, " + "encrypted" + " INTEGER, " + @@ -260,6 +261,7 @@ public class AttachmentDatabase extends Database { values.put(CONTENT_LOCATION, (String)null); values.put(CONTENT_DISPOSITION, (String)null); values.put(NAME, (String) null); + values.put(FILENAME, (String) null); if (database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()) == 0) { //noinspection ResultOfMethodCallIgnored @@ -305,6 +307,9 @@ public class AttachmentDatabase extends Database { ContentValues contentValues = new ContentValues(); contentValues.put(SIZE, dataSize); contentValues.put(CONTENT_TYPE, mediaStream.getMimeType()); + if (attachment.getFileName() != null) { + contentValues.put(FILENAME, databaseAttachment.getFileName()); + } database.update(TABLE_NAME, contentValues, PART_ID_WHERE, databaseAttachment.getAttachmentId().toStrings()); @@ -316,7 +321,8 @@ public class AttachmentDatabase extends Database { dataSize, databaseAttachment.getLocation(), databaseAttachment.getKey(), - databaseAttachment.getRelay()); + databaseAttachment.getRelay(), + databaseAttachment.getFileName()); } @@ -437,7 +443,8 @@ public class AttachmentDatabase extends Database { cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)), cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)), cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)), - cursor.getString(cursor.getColumnIndexOrThrow(NAME))); + cursor.getString(cursor.getColumnIndexOrThrow(NAME)), + cursor.getString(cursor.getColumnIndexOrThrow(FILENAME))); } @@ -463,7 +470,9 @@ public class AttachmentDatabase extends Database { contentValues.put(CONTENT_LOCATION, attachment.getLocation()); contentValues.put(CONTENT_DISPOSITION, attachment.getKey()); contentValues.put(NAME, attachment.getRelay()); - + if (attachment.getFileName() != null) { + contentValues.put(FILENAME, attachment.getFileName()); + } if (partData != null) { contentValues.put(DATA, partData.first.getAbsolutePath()); contentValues.put(SIZE, partData.second); diff --git a/src/org/smssecure/smssecure/database/DatabaseFactory.java b/src/org/smssecure/smssecure/database/DatabaseFactory.java index 750b9bc836adefb3ad21e9c7bc38c601a5456a5f..7d8c126e0fedeca5990c23d5e2a8975d3db6fd4f 100644 --- a/src/org/smssecure/smssecure/database/DatabaseFactory.java +++ b/src/org/smssecure/smssecure/database/DatabaseFactory.java @@ -73,7 +73,8 @@ public class DatabaseFactory { private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25; private static final int MIGRATED_CONVERSATION_LIST_STATUS_VERSION = 26; private static final int INTRODUCED_SUBSCRIPTION_ID_VERSION = 28; - private static final int DATABASE_VERSION = 28; + private static final int INTRODUCED_ATTACHMENT_FILENAME = 29; + private static final int DATABASE_VERSION = 29; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -392,7 +393,8 @@ public class DatabaseFactory { } } else if (ContentType.isAudioType(contentType) || ContentType.isImageType(contentType) || - ContentType.isVideoType(contentType)) + ContentType.isVideoType(contentType) || + ContentType.isVendorFileType(contentType)) { partCount++; } @@ -817,6 +819,11 @@ public class DatabaseFactory { db.execSQL("ALTER TABLE mms ADD COLUMN subscription_id INTEGER DEFAULT -1"); } + if (oldVersion < INTRODUCED_ATTACHMENT_FILENAME) { + db.execSQL("ALTER TABLE part ADD COLUMN filename TEXT DEFAULT NULL;"); + db.execSQL("UPDATE part SET filename = ?;", new String[]{""}); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/smssecure/smssecure/database/DraftDatabase.java b/src/org/smssecure/smssecure/database/DraftDatabase.java index bf9afafc7464a20c7ce124817530ec0c8ca1eca4..9670545e2c0fbc494312a68766f7a99f74cb3aee 100644 --- a/src/org/smssecure/smssecure/database/DraftDatabase.java +++ b/src/org/smssecure/smssecure/database/DraftDatabase.java @@ -107,6 +107,7 @@ public class DraftDatabase extends Database { public static final String IMAGE = "image"; public static final String VIDEO = "video"; public static final String AUDIO = "audio"; + public static final String FILE = "file"; private final String type; private final String value; @@ -130,6 +131,7 @@ public class DraftDatabase extends Database { case IMAGE: return context.getString(R.string.DraftDatabase_Draft_image_snippet); case VIDEO: return context.getString(R.string.DraftDatabase_Draft_video_snippet); case AUDIO: return context.getString(R.string.DraftDatabase_Draft_audio_snippet); + case FILE: return context.getString(R.string.DraftDatabase_Draft_file_snippet); default: return null; } } diff --git a/src/org/smssecure/smssecure/database/MmsDatabase.java b/src/org/smssecure/smssecure/database/MmsDatabase.java index 9204bdbd29c83e5972380abdb356e4d0efb49ec6..8e97c36187f0252718cc61e485e63e0478858426 100644 --- a/src/org/smssecure/smssecure/database/MmsDatabase.java +++ b/src/org/smssecure/smssecure/database/MmsDatabase.java @@ -138,6 +138,7 @@ public class MmsDatabase extends MessagingDatabase { AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.NAME, + AttachmentDatabase.FILENAME, AttachmentDatabase.TRANSFER_STATE }; @@ -503,7 +504,8 @@ public class MmsDatabase extends MessagingDatabase { databaseAttachment.getSize(), databaseAttachment.getLocation(), databaseAttachment.getKey(), - databaseAttachment.getRelay())); + databaseAttachment.getRelay(), + databaseAttachment.getFileName())); } return insertMediaMessage(masterSecret, diff --git a/src/org/smssecure/smssecure/database/MmsSmsDatabase.java b/src/org/smssecure/smssecure/database/MmsSmsDatabase.java index 05d837d0921db19940e7a6a77a3d86d5a15d62b4..fe3e4b50efca0a71b5600fcb01670c334992e94c 100644 --- a/src/org/smssecure/smssecure/database/MmsSmsDatabase.java +++ b/src/org/smssecure/smssecure/database/MmsSmsDatabase.java @@ -25,6 +25,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; +import org.smssecure.smssecure.attachments.Attachment; import org.smssecure.smssecure.crypto.MasterSecret; import org.smssecure.smssecure.database.model.MessageRecord; import org.whispersystems.libaxolotl.util.guava.Optional; @@ -63,6 +64,7 @@ public class MmsSmsDatabase extends Database { AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.NAME, + AttachmentDatabase.FILENAME, AttachmentDatabase.TRANSFER_STATE}; public MmsSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) { @@ -137,6 +139,7 @@ public class MmsSmsDatabase extends Database { AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.NAME, + AttachmentDatabase.FILENAME, AttachmentDatabase.TRANSFER_STATE}; String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT, @@ -162,6 +165,7 @@ public class MmsSmsDatabase extends Database { AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.NAME, + AttachmentDatabase.FILENAME, AttachmentDatabase.TRANSFER_STATE}; SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); @@ -209,6 +213,7 @@ public class MmsSmsDatabase extends Database { mmsColumnsPresent.add(AttachmentDatabase.CONTENT_LOCATION); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION); mmsColumnsPresent.add(AttachmentDatabase.NAME); + mmsColumnsPresent.add(AttachmentDatabase.FILENAME); mmsColumnsPresent.add(AttachmentDatabase.TRANSFER_STATE); Set smsColumnsPresent = new HashSet<>(); diff --git a/src/org/smssecure/smssecure/jobs/MmsDownloadJob.java b/src/org/smssecure/smssecure/jobs/MmsDownloadJob.java index bdd5591dea4eda59f90fdd3e4dacae1c802a33c8..80d5c6ab8d256e88ef9a52c526541d9c80b45d34 100644 --- a/src/org/smssecure/smssecure/jobs/MmsDownloadJob.java +++ b/src/org/smssecure/smssecure/jobs/MmsDownloadJob.java @@ -7,6 +7,8 @@ import android.os.Build.VERSION_CODES; import android.util.Log; import android.util.Pair; +import com.fasterxml.jackson.databind.util.ISO8601Utils; + import org.smssecure.smssecure.attachments.Attachment; import org.smssecure.smssecure.attachments.UriAttachment; import org.smssecure.smssecure.crypto.MasterSecret; @@ -209,9 +211,10 @@ public class MmsDownloadJob extends MasterSecretJob { if (part.getData() != null) { Uri uri = provider.createUri(part.getData()); + String potentialFilename = part.getFilename() != null ? Util.toIsoString(part.getFilename()) : null; attachments.add(new UriAttachment(uri, Util.toIsoString(part.getContentType()), AttachmentDatabase.TRANSFER_PROGRESS_DONE, - part.getData().length)); + part.getData().length, potentialFilename)); } } } diff --git a/src/org/smssecure/smssecure/jobs/MmsSendJob.java b/src/org/smssecure/smssecure/jobs/MmsSendJob.java index 1f16800f97aed4dd5d907272dc2aa7d040781d8a..666abdc53911bcdd340320749b8520b577bd8006 100644 --- a/src/org/smssecure/smssecure/jobs/MmsSendJob.java +++ b/src/org/smssecure/smssecure/jobs/MmsSendJob.java @@ -25,6 +25,7 @@ import org.smssecure.smssecure.recipients.RecipientFormattingException; import org.smssecure.smssecure.transport.InsecureFallbackApprovalException; import org.smssecure.smssecure.transport.UndeliverableMessageException; import org.smssecure.smssecure.util.Hex; +import org.smssecure.smssecure.util.MediaUtil; import org.smssecure.smssecure.util.NumberUtil; import org.smssecure.smssecure.util.SmilUtil; import org.smssecure.smssecure.util.TelephonyUtil; @@ -234,6 +235,9 @@ public class MmsSendJob extends SendJob { PduPart part = new PduPart(); part.setData(Util.readFully(PartAuthority.getAttachmentStream(context, masterSecret, attachment.getDataUri()))); part.setContentType(Util.toIsoBytes(attachment.getContentType())); + if (MediaUtil.isFile(attachment) && attachment.getFileName() != null) { + part.setFilename(Util.toIsoBytes(attachment.getFileName())); + } part.setContentId((System.currentTimeMillis() + "").getBytes()); part.setName((System.currentTimeMillis() + "").getBytes()); diff --git a/src/org/smssecure/smssecure/mms/AttachmentManager.java b/src/org/smssecure/smssecure/mms/AttachmentManager.java index 7f00091251398dc05ab09f717f0adfad3a562518..3f86bd69dcc10062967212ed347c6c8cba1e6011 100644 --- a/src/org/smssecure/smssecure/mms/AttachmentManager.java +++ b/src/org/smssecure/smssecure/mms/AttachmentManager.java @@ -32,8 +32,6 @@ import android.util.Log; import android.view.View; import android.widget.Toast; -import junit.framework.Assert; - import org.smssecure.smssecure.MediaPreviewActivity; import org.smssecure.smssecure.R; import org.smssecure.smssecure.components.AudioView; @@ -41,12 +39,12 @@ import org.smssecure.smssecure.components.RemovableMediaView; import org.smssecure.smssecure.components.ThumbnailView; import org.smssecure.smssecure.crypto.MasterSecret; import org.smssecure.smssecure.providers.PersistentBlobProvider; -import org.smssecure.smssecure.recipients.Recipients; import org.smssecure.smssecure.util.MediaUtil; import org.smssecure.smssecure.util.ViewUtil; import org.smssecure.smssecure.util.concurrent.ListenableFuture.Listener; import org.whispersystems.libaxolotl.util.guava.Optional; +import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; @@ -82,6 +80,15 @@ public class AttachmentManager { thumbnail.setOnClickListener(new ThumbnailClickListener()); } + public void selectFile(final MasterSecret secret, final Activity activity, int requestCode) { + new FileChooser(activity).setFileListener(new FileChooser.FileSelectedListener() { + @Override + public void fileSelected(final File file) { + setMedia(secret, Uri.fromFile(file), MediaType.FILE, new MmsMediaConstraints()); + } + }).showDialog(); + } + public void clear() { ViewUtil.fadeOut(attachmentView, 200).addListener(new Listener() { @Override @@ -140,7 +147,15 @@ public class AttachmentManager { public void setMedia(@NonNull final MasterSecret masterSecret, @NonNull final Uri uri, @NonNull final MediaType mediaType, - @NonNull final MediaConstraints constraints) + @NonNull final MediaConstraints constraints){ + setMedia(masterSecret, uri, mediaType, constraints, null); + } + + public void setMedia(@NonNull final MasterSecret masterSecret, + @NonNull final Uri uri, + @NonNull final MediaType mediaType, + @NonNull final MediaConstraints constraints, + final String fileName) { new AsyncTask() { @Override protected void onPreExecute() { @@ -153,7 +168,7 @@ public class AttachmentManager { long start = System.currentTimeMillis(); try { final long mediaSize = MediaUtil.getMediaSize(context, masterSecret, uri); - final Slide slide = mediaType.createSlide(context, uri, mediaSize); + final Slide slide = mediaType.createSlide(context, uri, mediaSize, fileName); Log.w(TAG, "slide with size " + mediaSize + " took " + (System.currentTimeMillis() - start) + "ms"); return slide; } catch (IOException ioe) { @@ -306,28 +321,30 @@ public class AttachmentManager { } public enum MediaType { - IMAGE, GIF, AUDIO, VIDEO; + IMAGE, GIF, AUDIO, VIDEO, FILE; public @NonNull Slide createSlide(@NonNull Context context, @NonNull Uri uri, - long dataSize) - throws IOException - { + long dataSize, + String fileName) + throws IOException { switch (this) { case IMAGE: return new ImageSlide(context, uri, dataSize); case GIF: return new GifSlide(context, uri, dataSize); case AUDIO: return new AudioSlide(context, uri, dataSize); case VIDEO: return new VideoSlide(context, uri, dataSize); + case FILE: return new FileSlide(context, uri, dataSize, fileName); default: throw new AssertionError("unrecognized enum"); } } public static @Nullable MediaType from(final @Nullable String mimeType) { - if (TextUtils.isEmpty(mimeType)) return null; - if (MediaUtil.isGif(mimeType)) return GIF; - if (ContentType.isImageType(mimeType)) return IMAGE; - if (ContentType.isAudioType(mimeType)) return AUDIO; - if (ContentType.isVideoType(mimeType)) return VIDEO; + if (TextUtils.isEmpty(mimeType)) return null; + if (MediaUtil.isGif(mimeType)) return GIF; + if (ContentType.isImageType(mimeType)) return IMAGE; + if (ContentType.isAudioType(mimeType)) return AUDIO; + if (ContentType.isVideoType(mimeType)) return VIDEO; + if (ContentType.isVendorFileType(mimeType)) return FILE; return null; } } diff --git a/src/org/smssecure/smssecure/mms/AttachmentTypeSelectorAdapter.java b/src/org/smssecure/smssecure/mms/AttachmentTypeSelectorAdapter.java index e0afe63df03e556eefc85989b87d105ea99bc1cd..8febd2a7148fe8efec3d297fa2a0c25210ebb47b 100644 --- a/src/org/smssecure/smssecure/mms/AttachmentTypeSelectorAdapter.java +++ b/src/org/smssecure/smssecure/mms/AttachmentTypeSelectorAdapter.java @@ -28,6 +28,7 @@ import android.widget.TextView; import org.smssecure.smssecure.R; import org.smssecure.smssecure.util.ResUtil; +import org.smssecure.smssecure.util.SMSSecurePreferences; import java.util.ArrayList; import java.util.List; @@ -39,11 +40,14 @@ public class AttachmentTypeSelectorAdapter extends ArrayAdapter getItemList(Context context) { + private static List getItemList(Context context, boolean isEncryptedConversation) { List data = new ArrayList<>(4); if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){ addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_camera), ResUtil.getDrawableRes(context, R.attr.conversation_attach_camera), TAKE_PHOTO); @@ -80,7 +84,12 @@ public class AttachmentTypeSelectorAdapter extends ArrayAdapter= 0) { + remove(fileIdentifier); + } + } + public static class IconListItem { private final String title; private final int resource; diff --git a/src/org/smssecure/smssecure/mms/FileChooser.java b/src/org/smssecure/smssecure/mms/FileChooser.java new file mode 100644 index 0000000000000000000000000000000000000000..94932eb190e878fc9807b41bff5e26d0627606d8 --- /dev/null +++ b/src/org/smssecure/smssecure/mms/FileChooser.java @@ -0,0 +1,127 @@ +package org.smssecure.smssecure.mms; + +import android.app.Activity; +import android.app.Dialog; +import android.os.Environment; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager.LayoutParams; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; + +public class FileChooser { + private static final String PARENT_DIR = ".."; + + private final Activity activity; + private ListView list; + private Dialog dialog; + private File currentPath; + + private String extension = null; + + public void setExtension(String extension) { + this.extension = (extension == null) ? null : extension.toLowerCase(); + } + + public interface FileSelectedListener { + void fileSelected(File file); + } + + public FileChooser setFileListener(FileSelectedListener fileListener) { + this.fileListener = fileListener; + return this; + } + + private FileSelectedListener fileListener; + + public FileChooser(Activity activity) { + this.activity = activity; + dialog = new Dialog(activity); + list = new ListView(activity); + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override public void onItemClick(AdapterView parent, View view, int which, long id) { + String fileChosen = (String) list.getItemAtPosition(which); + File chosenFile = getChosenFile(fileChosen); + if (chosenFile.isDirectory()) { + refresh(chosenFile); + } else { + if (fileListener != null) { + fileListener.fileSelected(chosenFile); + } + dialog.dismiss(); + } + } + }); + dialog.setContentView(list); + dialog.getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + refresh(Environment.getExternalStorageDirectory()); + } + + public void showDialog() { + dialog.show(); + } + + private void refresh(File path) { + this.currentPath = path; + if (path.exists()) { + File[] dirs = path.listFiles(new FileFilter() { + @Override public boolean accept(File file) { + return (file.isDirectory() && file.canRead()); + } + }); + File[] files = path.listFiles(new FileFilter() { + @Override public boolean accept(File file) { + if (!file.isDirectory()) { + if (!file.canRead()) { + return false; + } else if (extension == null) { + return true; + } else { + return file.getName().toLowerCase().endsWith(extension); + } + } else { + return false; + } + } + }); + + int i = 0; + String[] fileList; + if (path.getParentFile() == null) { + fileList = new String[dirs.length + files.length]; + } else { + fileList = new String[dirs.length + files.length + 1]; + fileList[i++] = PARENT_DIR; + } + Arrays.sort(dirs); + Arrays.sort(files); + for (File dir : dirs) { fileList[i++] = dir.getName(); } + for (File file : files ) { fileList[i++] = file.getName(); } + + dialog.setTitle(currentPath.getPath()); + list.setAdapter(new ArrayAdapter(activity, + android.R.layout.simple_list_item_1, fileList) { + @Override + public View getView(int pos, View view, ViewGroup parent) { + view = super.getView(pos, view, parent); + ((TextView) view).setSingleLine(true); + return view; + } + }); + } + } + + private File getChosenFile(String fileChosen) { + if (fileChosen.equals(PARENT_DIR)) { + return currentPath.getParentFile(); + } else { + return new File(currentPath, fileChosen); + } + } + +} \ No newline at end of file diff --git a/src/org/smssecure/smssecure/mms/FileSlide.java b/src/org/smssecure/smssecure/mms/FileSlide.java new file mode 100644 index 0000000000000000000000000000000000000000..63ae01219dabae30e936ea7531923737535ece41 --- /dev/null +++ b/src/org/smssecure/smssecure/mms/FileSlide.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.smssecure.smssecure.mms; + +import android.content.Context; +import android.content.res.Resources.Theme; +import android.net.Uri; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; + +import org.smssecure.smssecure.R; +import org.smssecure.smssecure.attachments.Attachment; +import org.smssecure.smssecure.crypto.MasterSecret; +import org.smssecure.smssecure.util.ResUtil; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +import ws.com.google.android.mms.ContentType; +import ws.com.google.android.mms.pdu.PduPart; + +public class FileSlide extends Slide { + + public FileSlide(Context context, Uri uri, long dataSize, String fileName) throws IOException { + super(context, constructAttachmentFromUri(context, uri, ContentType.SMS_SECURE_FILE, dataSize, fileName)); + } + + public FileSlide(Context context, Attachment attachment) { + super(context, attachment); + } + + @Override + @Nullable + public Uri getThumbnailUri() { + return null; + } + + @Override + public boolean hasPlaceholder() { + return true; + } + + @Override + public boolean hasFile(){ + return true; + } + + @Override + public boolean hasImage() { + return true; + } + + @Override + public String getFileName(){ + return attachment.getFileName(); + } + + @Override + public @DrawableRes int getPlaceholderRes(Theme theme) { + return ResUtil.getDrawableRes(theme, R.attr.conversation_icon_attach_file); + } +} diff --git a/src/org/smssecure/smssecure/mms/MediaConstraints.java b/src/org/smssecure/smssecure/mms/MediaConstraints.java index fd451f9c3208a3980170ba50985ef1362d6d868c..d6fe50aebaa41e49bdd43933d1de3cb9bb7476f4 100644 --- a/src/org/smssecure/smssecure/mms/MediaConstraints.java +++ b/src/org/smssecure/smssecure/mms/MediaConstraints.java @@ -35,13 +35,16 @@ public abstract class MediaConstraints { public abstract int getAudioMaxSize(); + public abstract int getFileMaxSize(); + public boolean isSatisfied(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Attachment attachment) { try { return (MediaUtil.isGif(attachment) && attachment.getSize() <= getGifMaxSize() && isWithinBounds(context, masterSecret, attachment.getDataUri())) || (MediaUtil.isImage(attachment) && attachment.getSize() <= getImageMaxSize() && isWithinBounds(context, masterSecret, attachment.getDataUri())) || (MediaUtil.isAudio(attachment) && attachment.getSize() <= getAudioMaxSize()) || (MediaUtil.isVideo(attachment) && attachment.getSize() <= getVideoMaxSize()) || - (!MediaUtil.isImage(attachment) && !MediaUtil.isAudio(attachment) && !MediaUtil.isVideo(attachment)); + (MediaUtil.isFile(attachment) && attachment.getSize() <= getFileMaxSize()) || + (!MediaUtil.isImage(attachment) && !MediaUtil.isAudio(attachment) && !MediaUtil.isVideo(attachment) && !MediaUtil.isFile(attachment)); } catch (IOException ioe) { Log.w(TAG, "Failed to determine if media's constraints are satisfied.", ioe); return false; @@ -60,7 +63,7 @@ public abstract class MediaConstraints { } public boolean canResize(@Nullable Attachment attachment) { - return attachment != null && MediaUtil.isImage(attachment) && !MediaUtil.isGif(attachment); + return attachment != null && MediaUtil.isImage(attachment) && !MediaUtil.isGif(attachment) && !MediaUtil.isFile(attachment); } public MediaStream getResizedMedia(@NonNull Context context, diff --git a/src/org/smssecure/smssecure/mms/MmsMediaConstraints.java b/src/org/smssecure/smssecure/mms/MmsMediaConstraints.java index 65e9776cc90757e501d939655cfddf8b898c4a68..4bd79c769cda093c54668fb420f260e019065d28 100644 --- a/src/org/smssecure/smssecure/mms/MmsMediaConstraints.java +++ b/src/org/smssecure/smssecure/mms/MmsMediaConstraints.java @@ -38,4 +38,10 @@ public class MmsMediaConstraints extends MediaConstraints { public int getAudioMaxSize() { return MAX_MESSAGE_SIZE; } + + @Override + public int getFileMaxSize() { + return MAX_MESSAGE_SIZE; + } + } diff --git a/src/org/smssecure/smssecure/mms/OutgoingMediaMessage.java b/src/org/smssecure/smssecure/mms/OutgoingMediaMessage.java index 5a5f3cada64c9aa16a306d6d0b9a2359f4b0e8cf..81c0140fa0f9541f436136c377bf26d9368413f3 100644 --- a/src/org/smssecure/smssecure/mms/OutgoingMediaMessage.java +++ b/src/org/smssecure/smssecure/mms/OutgoingMediaMessage.java @@ -32,6 +32,7 @@ public class OutgoingMediaMessage { public OutgoingMediaMessage(Recipients recipients, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, int distributionType) { + this(recipients, buildMessage(slideDeck, message), slideDeck.asAttachments(), diff --git a/src/org/smssecure/smssecure/mms/PartParser.java b/src/org/smssecure/smssecure/mms/PartParser.java index 920a76cd8ecf09804eb6172f96c7f16e56ff9e44..538a40d5a48d262d55b4c00b12aed09d65d3344f 100644 --- a/src/org/smssecure/smssecure/mms/PartParser.java +++ b/src/org/smssecure/smssecure/mms/PartParser.java @@ -83,6 +83,11 @@ public class PartParser { } public static boolean isDisplayableMedia(PduPart part) { - return isImage(part) || isAudio(part) || isVideo(part); + return isFile(part) || isImage(part) || isAudio(part) || isVideo(part); } + + public static boolean isFile(PduPart part) { + return ContentType.isVendorFileType(Util.toIsoString(part.getContentType())); + } + } diff --git a/src/org/smssecure/smssecure/mms/Slide.java b/src/org/smssecure/smssecure/mms/Slide.java index dc8cec451de893c62c340ec5ae0e976d783d656b..c570be095d4f716295f671984a7149bb22049c14 100644 --- a/src/org/smssecure/smssecure/mms/Slide.java +++ b/src/org/smssecure/smssecure/mms/Slide.java @@ -72,6 +72,14 @@ public abstract class Slide { return false; } + public boolean hasFile() { + return false; + } + + public String getFileName() { + return null; + } + public @NonNull String getContentDescription() { return ""; } public Attachment asAttachment() { @@ -102,10 +110,22 @@ public abstract class Slide { protected static Attachment constructAttachmentFromUri(@NonNull Context context, @NonNull Uri uri, @NonNull String defaultMime, - long size) + long size) + { + Optional resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)); + return new UriAttachment(uri, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size, context); + } + protected static Attachment constructAttachmentFromUri(@NonNull Context context, + @NonNull Uri uri, + @NonNull String defaultMime, + long size, + String fileName) { Optional resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)); - return new UriAttachment(uri, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size); + if (fileName != null) { + return new UriAttachment(uri, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size, fileName); + } + return new UriAttachment(uri, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size, context); } @Override @@ -118,6 +138,7 @@ public abstract class Slide { this.hasAudio() == that.hasAudio() && this.hasImage() == that.hasImage() && this.hasVideo() == that.hasVideo() && + this.hasFile() == that.hasFile() && this.getTransferState() == that.getTransferState() && Util.equals(this.getUri(), that.getUri()) && Util.equals(this.getThumbnailUri(), that.getThumbnailUri()); diff --git a/src/org/smssecure/smssecure/mms/SlideDeck.java b/src/org/smssecure/smssecure/mms/SlideDeck.java index 9bc512b68e614d6addbb8e9ff22862fd24734fd0..054646906fb35c65df150b9b146203300d035e60 100644 --- a/src/org/smssecure/smssecure/mms/SlideDeck.java +++ b/src/org/smssecure/smssecure/mms/SlideDeck.java @@ -86,7 +86,30 @@ public class SlideDeck { public boolean containsMediaSlide() { for (Slide slide : slides) { - if (slide.hasImage() || slide.hasVideo() || slide.hasAudio()) { + if (slide.hasImage() || slide.hasVideo() || slide.hasAudio() || slide.hasFile()) { + return true; + } + } + return false; + } + + public boolean removeFileSlides() { + boolean returnObject = false; + if (slides.isEmpty()) { + return returnObject; + } + for (Slide slide : slides) { + if (slide.hasFile()) { + returnObject = true; + slides.remove(slide); + } + } + return returnObject; + } + + public boolean hasFileSlide() { + for (Slide slide : slides) { + if (slide.hasFile()) { return true; } } diff --git a/src/org/smssecure/smssecure/util/MediaUtil.java b/src/org/smssecure/smssecure/util/MediaUtil.java index ec74669362004731d27122da0c820b8509232288..3961c45a2964f91327fc39fee26a9e245074dd76 100644 --- a/src/org/smssecure/smssecure/util/MediaUtil.java +++ b/src/org/smssecure/smssecure/util/MediaUtil.java @@ -14,6 +14,7 @@ import org.smssecure.smssecure.attachments.Attachment; import org.smssecure.smssecure.crypto.MasterSecret; import org.smssecure.smssecure.mms.AudioSlide; import org.smssecure.smssecure.mms.DecryptableStreamUriLoader.DecryptableUri; +import org.smssecure.smssecure.mms.FileSlide; import org.smssecure.smssecure.mms.GifSlide; import org.smssecure.smssecure.mms.ImageSlide; import org.smssecure.smssecure.mms.PartAuthority; @@ -23,7 +24,6 @@ import org.smssecure.smssecure.providers.PersistentBlobProvider; import java.io.IOException; import java.io.InputStream; -import java.util.concurrent.ExecutionException; import ws.com.google.android.mms.ContentType; @@ -66,6 +66,8 @@ public class MediaUtil { slide = new VideoSlide(context, attachment); } else if (ContentType.isAudioType(attachment.getContentType())) { slide = new AudioSlide(context, attachment); + } else if (ContentType.isVendorFileType(attachment.getContentType())) { + slide = new FileSlide(context, attachment); } return slide; @@ -93,6 +95,9 @@ public class MediaUtil { ? ContentType.IMAGE_JPEG : mimeType; default: + if (ContentType.isNonTextVideoImageAudioType(mimeType)) { + return ContentType.SMS_SECURE_FILE; + } return mimeType; } } @@ -129,6 +134,10 @@ public class MediaUtil { return ContentType.isAudioType(attachment.getContentType()); } + public static boolean isFile(Attachment attachment) { + return ContentType.isVendorFileType(attachment.getContentType()); + } + public static boolean isVideo(Attachment attachment) { return ContentType.isVideoType(attachment.getContentType()); } diff --git a/src/org/smssecure/smssecure/util/SaveAttachmentTask.java b/src/org/smssecure/smssecure/util/SaveAttachmentTask.java index fd81f170934b78a19684e32cb0ca41246dc87eb9..39993b0929d9f71d49320f88aa221804390cbd4b 100644 --- a/src/org/smssecure/smssecure/util/SaveAttachmentTask.java +++ b/src/org/smssecure/smssecure/util/SaveAttachmentTask.java @@ -7,6 +7,7 @@ import android.net.Uri; import android.os.Environment; import android.support.v7.app.AlertDialog; import android.util.Log; +import android.util.Pair; import android.webkit.MimeTypeMap; import android.widget.Toast; @@ -23,7 +24,9 @@ import java.io.OutputStream; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; -public class SaveAttachmentTask extends ProgressDialogAsyncTask { +import ws.com.google.android.mms.ContentType; + +public class SaveAttachmentTask extends ProgressDialogAsyncTask> { private static final String TAG = SaveAttachmentTask.class.getSimpleName(); private static final int SUCCESS = 0; @@ -49,7 +52,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask doInBackground(SaveAttachmentTask.Attachment... attachments) { if (attachments == null || attachments.length == 0) { throw new AssertionError("must pass in at least one attachment"); } @@ -59,33 +62,41 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask(WRITE_ACCESS_FAILURE, null); } if (context == null) { - return FAILURE; + return new Pair(FAILURE, null); } + StringBuilder builder = new StringBuilder(); for (Attachment attachment : attachments) { - if (attachment != null && !saveAttachment(context, masterSecret, attachment)) { - return FAILURE; + if(attachment == null) + return new Pair(FAILURE, null); + Pair saveResult = saveAttachment(context, masterSecret, attachment); + if (saveResult.first.equals(FAILURE)) { + return new Pair(FAILURE, null); + } + else if (saveResult.second != null){ + if(builder.length() != 0) + builder.append(","); + builder.append(saveResult.second); } } - - return SUCCESS; + return new Pair(SUCCESS, builder.toString()); } catch (IOException ioe) { Log.w(TAG, ioe); - return FAILURE; + return new Pair(FAILURE, null); } } - private boolean saveAttachment(Context context, MasterSecret masterSecret, Attachment attachment) throws IOException { + private Pair saveAttachment(Context context, MasterSecret masterSecret, Attachment attachment) throws IOException { String contentType = MediaUtil.getCorrectedMimeType(attachment.contentType); - File mediaFile = constructOutputFile(contentType, attachment.date); + File mediaFile = constructOutputFile(attachment, attachment.date); InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, attachment.uri); if (inputStream == null) { - return false; + return new Pair(FAILURE, null); } OutputStream outputStream = new FileOutputStream(mediaFile); @@ -94,16 +105,16 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask(SUCCESS, mediaFile.getAbsolutePath()); } @Override - protected void onPostExecute(Integer result) { + protected void onPostExecute(Pair result) { super.onPostExecute(result); Context context = contextReference.get(); if (context == null) return; - switch (result) { + switch (result.first) { case FAILURE: Toast.makeText(context, context.getResources().getQuantityText(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, @@ -111,7 +122,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask sSupportedContentTypes = new ArrayList(); private static final ArrayList sSupportedImageTypes = new ArrayList(); private static final ArrayList sSupportedAudioTypes = new ArrayList(); @@ -124,6 +126,7 @@ public class ContentType { sSupportedContentTypes.add(APP_DRM_CONTENT); sSupportedContentTypes.add(APP_DRM_MESSAGE); + sSupportedContentTypes.add(SMS_SECURE_FILE); // add supported image types sSupportedImageTypes.add(IMAGE_JPEG); @@ -197,10 +200,13 @@ public class ContentType { return (null != contentType) && contentType.startsWith("video/"); } - public static boolean isDrmType(String contentType) { + public static boolean isNonTextVideoImageAudioType(String contentType){ + return !isVideoType(contentType) && !isTextType(contentType) && !isAudioType(contentType) && !isImageType(contentType); + } + + public static boolean isVendorFileType(String contentType) { return (null != contentType) - && (contentType.equals(APP_DRM_CONTENT) - || contentType.equals(APP_DRM_MESSAGE)); + && (contentType.equals(SMS_SECURE_FILE)); } public static boolean isUnspecified(String contentType) { diff --git a/src/ws/com/google/android/mms/pdu/PduComposer.java b/src/ws/com/google/android/mms/pdu/PduComposer.java index d2808cc7c50f06f54a0f8422878272c7a627baee..ecbca36846499f6ac7fa886344a8fb4de7bf2a73 100644 --- a/src/ws/com/google/android/mms/pdu/PduComposer.java +++ b/src/ws/com/google/android/mms/pdu/PduComposer.java @@ -961,6 +961,13 @@ public class PduComposer { appendValueLength(contentTypeLength); mStack.copy(); + // file-name for fileslides + if(part.getFilename() != null) { + Log.w("PduComposer", "Filenameheader added to part: " + new String(part.getFilename())); + appendOctet(PduPart.P_FILENAME); + appendTextString(part.getFilename()); + } + // content id byte[] contentId = part.getContentId(); diff --git a/src/ws/com/google/android/mms/pdu/PduParser.java b/src/ws/com/google/android/mms/pdu/PduParser.java index f95e4a0ad1f46d56b3a9540534a7afe5501edd8c..fefbe3956e1a4ea39b54be0dbe9e5163d5270839 100644 --- a/src/ws/com/google/android/mms/pdu/PduParser.java +++ b/src/ws/com/google/android/mms/pdu/PduParser.java @@ -778,7 +778,7 @@ public class PduParser { int partHeaderLen = headerLength - (startPos - endPos); if (partHeaderLen > 0) { if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) { - // Parse part header faild. + // Parse part header failed. return null; } } else if (partHeaderLen < 0) { @@ -1618,6 +1618,13 @@ public class PduParser { lastLen = length - (startPos - tempPos); } break; + case PduPart.P_FILENAME: + // filename + byte[] filename = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (null != filename) { + part.setFilename(filename); + } + break; default: if (LOCAL_LOGV) { Log.v(LOG_TAG, "Not supported Part headers: " + header);