mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 16:57:50 +00:00
in-app image media preview
// FREEBIE
This commit is contained in:
parent
503d1ef452
commit
53da1f849a
@ -192,6 +192,12 @@
|
|||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".MediaPreviewActivity"
|
||||||
|
android:label="@string/AndroidManifest__media_preview"
|
||||||
|
android:theme="@style/TextSecure.DarkTheme"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".DummyActivity"
|
<activity android:name=".DummyActivity"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
@ -36,6 +36,7 @@ dependencies {
|
|||||||
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||||
compile 'org.w3c:smil:1.0.0'
|
compile 'org.w3c:smil:1.0.0'
|
||||||
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
||||||
|
compile 'com.github.chrisbanes.photoview:library:1.2.3'
|
||||||
|
|
||||||
androidTestCompile 'com.squareup:fest-android:1.0.8'
|
androidTestCompile 'com.squareup:fest-android:1.0.8'
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.1'
|
androidTestCompile 'com.google.dexmaker:dexmaker:1.1'
|
||||||
@ -59,6 +60,7 @@ dependencyVerification {
|
|||||||
'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d',
|
'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d',
|
||||||
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
||||||
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
|
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
|
||||||
|
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
res/layout/media_preview_activity.xml
Normal file
13
res/layout/media_preview_activity.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView android:id="@+id/image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/media_preview_activity__image_content_description"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
7
res/menu/media_preview.xml
Normal file
7
res/menu/media_preview.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@+id/save"
|
||||||
|
android:title="@string/media_preview__save_title"
|
||||||
|
android:icon="@drawable/ic_action_save_holo_dark"
|
||||||
|
android:showAsAction="always"/>
|
||||||
|
</menu>
|
@ -102,13 +102,13 @@
|
|||||||
<string name="ConversationFragment_sender_s_transport_s_sent_s_received_s">Sender: %1$s\nTransport: %2$s\nSent: %3$s\nReceived: %4$s</string>
|
<string name="ConversationFragment_sender_s_transport_s_sent_s_received_s">Sender: %1$s\nTransport: %2$s\nSent: %3$s\nReceived: %4$s</string>
|
||||||
<string name="ConversationFragment_confirm_message_delete">Confirm message delete</string>
|
<string name="ConversationFragment_confirm_message_delete">Confirm message delete</string>
|
||||||
<string name="ConversationFragment_are_you_sure_you_want_to_permanently_delete_this_message">Are you sure that you want to permanently delete this message?</string>
|
<string name="ConversationFragment_are_you_sure_you_want_to_permanently_delete_this_message">Are you sure that you want to permanently delete this message?</string>
|
||||||
<string name="ConversationFragment_save_to_sd_card">Save to SD card?</string>
|
<string name="ConversationFragment_save_to_sd_card">Save to storage?</string>
|
||||||
<string name="ConversationFragment_this_media_has_been_stored_in_an_encrypted_database_warning">This media has been stored in an encrypted database. The version you save to the SD card will no longer be encrypted. Would you like to continue?</string>
|
<string name="ConversationFragment_this_media_has_been_stored_in_an_encrypted_database_warning">Saving this media to storage will allow any other apps on your phone to access it.\n\nContinue?</string>
|
||||||
<string name="ConversationFragment_error_while_saving_attachment_to_sd_card">Error while saving attachment to SD card!</string>
|
<string name="ConversationFragment_error_while_saving_attachment_to_sd_card">Error while saving attachment to storage!</string>
|
||||||
<string name="ConversationFragment_success_exclamation">Success!</string>
|
<string name="ConversationFragment_success_exclamation">Success!</string>
|
||||||
<string name="ConversationFragment_unable_to_write_to_sd_card_exclamation">Unable to write to SD card!</string>
|
<string name="ConversationFragment_unable_to_write_to_sd_card_exclamation">Unable to write to storage!</string>
|
||||||
<string name="ConversationFragment_saving_attachment">Saving attachment</string>
|
<string name="ConversationFragment_saving_attachment">Saving attachment</string>
|
||||||
<string name="ConversationFragment_saving_attachment_to_sd_card">Saving attachment to SD card...</string>
|
<string name="ConversationFragment_saving_attachment_to_sd_card">Saving attachment to storage...</string>
|
||||||
|
|
||||||
<!-- ConversationListAdapter -->
|
<!-- ConversationListAdapter -->
|
||||||
<string name="ConversationListAdapter_key_exchange_message">Key exchange message...</string>
|
<string name="ConversationListAdapter_key_exchange_message">Key exchange message...</string>
|
||||||
@ -674,6 +674,7 @@
|
|||||||
<string name="AndroidManifest__manage_identity_keys">Manage identity keys</string>
|
<string name="AndroidManifest__manage_identity_keys">Manage identity keys</string>
|
||||||
<string name="AndroidManifest__complete_key_exchange">Complete key exchange</string>
|
<string name="AndroidManifest__complete_key_exchange">Complete key exchange</string>
|
||||||
<string name="AndroidManifest__log_submit">Submit debug logs</string>
|
<string name="AndroidManifest__log_submit">Submit debug logs</string>
|
||||||
|
<string name="AndroidManifest__media_preview">Media Preview</string>
|
||||||
|
|
||||||
<!-- arrays.xml -->
|
<!-- arrays.xml -->
|
||||||
<string name="arrays__import_export">Import / export</string>
|
<string name="arrays__import_export">Import / export</string>
|
||||||
@ -871,6 +872,15 @@
|
|||||||
<string name="reminder_header_sms_import_text">TextSecure can copy your phone\'s SMS messages into its encrypted database.</string>
|
<string name="reminder_header_sms_import_text">TextSecure can copy your phone\'s SMS messages into its encrypted database.</string>
|
||||||
<string name="reminder_header_push_title">Enable TextSecure messages?</string>
|
<string name="reminder_header_push_title">Enable TextSecure messages?</string>
|
||||||
<string name="reminder_header_push_text">Instant delivery, stronger privacy, and no SMS fees.</string>
|
<string name="reminder_header_push_text">Instant delivery, stronger privacy, and no SMS fees.</string>
|
||||||
|
|
||||||
|
<!-- MediaPreviewActivity -->
|
||||||
|
<string name="MediaPreviewActivity_you">You</string>
|
||||||
|
|
||||||
|
<!-- media_preview -->
|
||||||
|
<string name="media_preview__save_title">Save</string>
|
||||||
|
|
||||||
|
<!-- media_preview_activity -->
|
||||||
|
<string name="media_preview_activity__image_content_description">Image Preview</string>
|
||||||
<!-- EOF -->
|
<!-- EOF -->
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -2,15 +2,11 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.media.MediaScannerConnection;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
@ -20,7 +16,6 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.webkit.MimeTypeMap;
|
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@ -41,19 +36,12 @@ import org.thoughtcrime.securesms.recipients.Recipients;
|
|||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.util.Dialogs;
|
import org.thoughtcrime.securesms.util.Dialogs;
|
||||||
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.util.Util;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.sql.Date;
|
import java.sql.Date;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
public class ConversationFragment extends SherlockListFragment
|
public class ConversationFragment extends SherlockListFragment
|
||||||
implements LoaderManager.LoaderCallbacks<Cursor>
|
implements LoaderManager.LoaderCallbacks<Cursor>
|
||||||
@ -140,17 +128,7 @@ public class ConversationFragment extends SherlockListFragment
|
|||||||
else resend.setVisible(false);
|
else resend.setVisible(false);
|
||||||
|
|
||||||
if (messageRecord.isMms() && !messageRecord.isMmsNotification()) {
|
if (messageRecord.isMms() && !messageRecord.isMmsNotification()) {
|
||||||
try {
|
saveAttachment.setVisible(((MediaMmsMessageRecord)messageRecord).containsMediaSlide());
|
||||||
if (((MediaMmsMessageRecord)messageRecord).getSlideDeck().get().containsMediaSlide()) {
|
|
||||||
saveAttachment.setVisible(true);
|
|
||||||
} else {
|
|
||||||
saveAttachment.setVisible(false);
|
|
||||||
}
|
|
||||||
} catch (InterruptedException ie) {
|
|
||||||
Log.w(TAG, ie);
|
|
||||||
} catch (ExecutionException ee) {
|
|
||||||
Log.w(TAG, ee);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
saveAttachment.setVisible(false);
|
saveAttachment.setVisible(false);
|
||||||
}
|
}
|
||||||
@ -260,20 +238,20 @@ public class ConversationFragment extends SherlockListFragment
|
|||||||
MessageSender.resend(activity, messageId, message.isMms());
|
MessageSender.resend(activity, messageId, message.isMms());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSaveAttachment(final MessageRecord message) {
|
private void handleSaveAttachment(final MediaMmsMessageRecord message) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
SaveAttachmentTask.showWarningDialog(getActivity(), new DialogInterface.OnClickListener() {
|
||||||
builder.setTitle(R.string.ConversationFragment_save_to_sd_card);
|
|
||||||
builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon));
|
|
||||||
builder.setCancelable(true);
|
|
||||||
builder.setMessage(R.string.ConversationFragment_this_media_has_been_stored_in_an_encrypted_database_warning);
|
|
||||||
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity());
|
|
||||||
saveTask.execute((MediaMmsMessageRecord) message);
|
final Slide slide = message.getMediaSlideSync();
|
||||||
|
if (slide == null) {
|
||||||
|
Log.w(TAG, "No slide with attachable media found, failing nicely.");
|
||||||
|
Toast.makeText(getActivity(), R.string.ConversationFragment_error_while_saving_attachment_to_sd_card, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity(), masterSecret);
|
||||||
|
saveTask.execute(new Attachment(slide.getUri(), slide.getContentType(), message.getDateReceived()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(R.string.no, null);
|
|
||||||
builder.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -358,7 +336,7 @@ public class ConversationFragment extends SherlockListFragment
|
|||||||
actionMode.finish();
|
actionMode.finish();
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_context_save_attachment:
|
case R.id.menu_context_save_attachment:
|
||||||
handleSaveAttachment(messageRecord);
|
handleSaveAttachment((MediaMmsMessageRecord)messageRecord);
|
||||||
actionMode.finish();
|
actionMode.finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -366,139 +344,4 @@ public class ConversationFragment extends SherlockListFragment
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private class SaveAttachmentTask extends AsyncTask<MediaMmsMessageRecord, Void, Integer> {
|
|
||||||
|
|
||||||
private static final int SUCCESS = 0;
|
|
||||||
private static final int FAILURE = 1;
|
|
||||||
private static final int WRITE_ACCESS_FAILURE = 2;
|
|
||||||
|
|
||||||
private final WeakReference<Context> contextReference;
|
|
||||||
private ProgressDialog progressDialog;
|
|
||||||
|
|
||||||
public SaveAttachmentTask(Context context) {
|
|
||||||
this.contextReference = new WeakReference<Context>(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
Context context = contextReference.get();
|
|
||||||
|
|
||||||
if (context != null) {
|
|
||||||
progressDialog = ProgressDialog.show(context,
|
|
||||||
context.getString(R.string.ConversationFragment_saving_attachment),
|
|
||||||
context.getString(R.string.ConversationFragment_saving_attachment_to_sd_card),
|
|
||||||
true, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Integer doInBackground(MediaMmsMessageRecord... messageRecord) {
|
|
||||||
try {
|
|
||||||
Context context = contextReference.get();
|
|
||||||
|
|
||||||
if (!Environment.getExternalStorageDirectory().canWrite()) {
|
|
||||||
return WRITE_ACCESS_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context == null) {
|
|
||||||
return FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Slide slide = getAttachment(messageRecord[0]);
|
|
||||||
|
|
||||||
if (slide == null) {
|
|
||||||
return FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
File mediaFile = constructOutputFile(slide, messageRecord[0].getDateReceived());
|
|
||||||
InputStream inputStream = slide.getPartDataInputStream();
|
|
||||||
OutputStream outputStream = new FileOutputStream(mediaFile);
|
|
||||||
|
|
||||||
Util.copy(inputStream, outputStream);
|
|
||||||
|
|
||||||
MediaScannerConnection.scanFile(context, new String[] {mediaFile.getAbsolutePath()},
|
|
||||||
new String[] {slide.getContentType()}, null);
|
|
||||||
|
|
||||||
return SUCCESS;
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
Log.w(TAG, ioe);
|
|
||||||
return FAILURE;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Integer result) {
|
|
||||||
Context context = contextReference.get();
|
|
||||||
if (context == null) return;
|
|
||||||
|
|
||||||
switch (result) {
|
|
||||||
case FAILURE:
|
|
||||||
Toast.makeText(context, R.string.ConversationFragment_error_while_saving_attachment_to_sd_card,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
Toast.makeText(context, R.string.ConversationFragment_success_exclamation,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case WRITE_ACCESS_FAILURE:
|
|
||||||
Toast.makeText(context, R.string.ConversationFragment_unable_to_write_to_sd_card_exclamation,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progressDialog != null)
|
|
||||||
progressDialog.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Slide getAttachment(MediaMmsMessageRecord record)
|
|
||||||
throws ExecutionException, InterruptedException
|
|
||||||
{
|
|
||||||
List<Slide> slides = record.getSlideDeck().get().getSlides();
|
|
||||||
|
|
||||||
for (Slide slide : slides) {
|
|
||||||
if (slide.hasImage() || slide.hasVideo() || slide.hasAudio()) {
|
|
||||||
return slide;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private File constructOutputFile(Slide slide, long timestamp) throws IOException {
|
|
||||||
File sdCard = Environment.getExternalStorageDirectory();
|
|
||||||
File outputDirectory;
|
|
||||||
|
|
||||||
if (slide.hasVideo()) {
|
|
||||||
outputDirectory = new File(sdCard.getAbsoluteFile() + File.separator + "Movies");
|
|
||||||
} else if (slide.hasAudio()) {
|
|
||||||
outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + "Music");
|
|
||||||
} else {
|
|
||||||
outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + "Pictures");
|
|
||||||
}
|
|
||||||
|
|
||||||
outputDirectory.mkdirs();
|
|
||||||
|
|
||||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
|
||||||
String extension = mimeTypeMap.getExtensionFromMimeType(slide.getContentType());
|
|
||||||
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd-HHmmss");
|
|
||||||
String base = "textsecure-" + dateFormatter.format(timestamp);
|
|
||||||
|
|
||||||
if (extension == null)
|
|
||||||
extension = "attach";
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
File file = new File(outputDirectory, base + "." + extension);
|
|
||||||
while (file.exists()) {
|
|
||||||
file = new File(outputDirectory, base + "-" + (++i) + "." + extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ import android.content.res.TypedArray;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.provider.Contacts.Intents;
|
import android.provider.Contacts.Intents;
|
||||||
@ -345,7 +344,7 @@ public class ConversationItem extends LinearLayout {
|
|||||||
mmsContainer.setVisibility(View.GONE);
|
mmsContainer.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
slideDeck = messageRecord.getSlideDeck();
|
slideDeck = messageRecord.getSlideDeckFuture();
|
||||||
slideDeck.setListener(new FutureTaskListener<SlideDeck>() {
|
slideDeck.setListener(new FutureTaskListener<SlideDeck>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final SlideDeck result) {
|
public void onSuccess(final SlideDeck result) {
|
||||||
@ -457,18 +456,28 @@ public class ConversationItem extends LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType())) {
|
||||||
builder.setTitle(R.string.ConversationItem_view_secure_media_question);
|
Intent intent = new Intent(context, MediaPreviewActivity.class);
|
||||||
builder.setIcon(Dialogs.resolveIcon(context, R.attr.dialog_alert_icon));
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
builder.setCancelable(true);
|
intent.setDataAndType(slide.getUri(), slide.getContentType());
|
||||||
builder.setMessage(R.string.ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning);
|
intent.putExtra(MediaPreviewActivity.MASTER_SECRET_EXTRA, masterSecret);
|
||||||
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
if (!messageRecord.isOutgoing()) intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, messageRecord.getIndividualRecipient());
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, messageRecord.getDateReceived());
|
||||||
fireIntent();
|
context.startActivity(intent);
|
||||||
}
|
} else {
|
||||||
});
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
builder.setNegativeButton(R.string.no, null);
|
builder.setTitle(R.string.ConversationItem_view_secure_media_question);
|
||||||
builder.show();
|
builder.setIcon(Dialogs.resolveIcon(context, R.attr.dialog_alert_icon));
|
||||||
|
builder.setCancelable(true);
|
||||||
|
builder.setMessage(R.string.ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning);
|
||||||
|
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
fireIntent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(R.string.no, null);
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
199
src/org/thoughtcrime/securesms/MediaPreviewActivity.java
Normal file
199
src/org/thoughtcrime/securesms/MediaPreviewActivity.java
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2014 Open 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.actionbarsherlock.view.Menu;
|
||||||
|
import com.actionbarsherlock.view.MenuInflater;
|
||||||
|
import com.actionbarsherlock.view.MenuItem;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||||
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import uk.co.senab.photoview.PhotoViewAttacher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity for displaying media attachments in-app
|
||||||
|
*/
|
||||||
|
public class MediaPreviewActivity extends PassphraseRequiredSherlockActivity {
|
||||||
|
private final static String TAG = MediaPreviewActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
public final static String MASTER_SECRET_EXTRA = "master_secret";
|
||||||
|
public final static String RECIPIENT_EXTRA = "recipient";
|
||||||
|
public final static String DATE_EXTRA = "date";
|
||||||
|
|
||||||
|
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||||
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
|
private MasterSecret masterSecret;
|
||||||
|
|
||||||
|
private ImageView image;
|
||||||
|
private PhotoViewAttacher imageAttacher;
|
||||||
|
private Uri mediaUri;
|
||||||
|
private String mediaType;
|
||||||
|
private Recipient recipient;
|
||||||
|
private long date;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle bundle) {
|
||||||
|
setFullscreenIfPossible();
|
||||||
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
dynamicTheme.onCreate(this);
|
||||||
|
dynamicLanguage.onCreate(this);
|
||||||
|
super.onCreate(bundle);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
setContentView(R.layout.media_preview_activity);
|
||||||
|
initializeResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(VERSION_CODES.HONEYCOMB)
|
||||||
|
private void setFullscreenIfPossible() {
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
dynamicTheme.onResume(this);
|
||||||
|
dynamicLanguage.onResume(this);
|
||||||
|
|
||||||
|
masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA);
|
||||||
|
|
||||||
|
mediaUri = getIntent().getData();
|
||||||
|
mediaType = getIntent().getType();
|
||||||
|
recipient = getIntent().getParcelableExtra(RECIPIENT_EXTRA);
|
||||||
|
date = getIntent().getLongExtra(DATE_EXTRA, -1);
|
||||||
|
|
||||||
|
final CharSequence relativeTimeSpan;
|
||||||
|
if (date > 0) {
|
||||||
|
relativeTimeSpan = DateUtils.getRelativeTimeSpanString(date,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
DateUtils.MINUTE_IN_MILLIS);
|
||||||
|
} else {
|
||||||
|
relativeTimeSpan = null;
|
||||||
|
}
|
||||||
|
getSupportActionBar().setTitle(recipient == null ? getString(R.string.MediaPreviewActivity_you) : recipient.getName());
|
||||||
|
getSupportActionBar().setSubtitle(relativeTimeSpan);
|
||||||
|
|
||||||
|
if (!isContentTypeSupported(mediaType)) {
|
||||||
|
Log.w(TAG, "Unsupported media type sent to MediaPreviewActivity, finishing.");
|
||||||
|
Toast.makeText(getApplicationContext(), "Unsupported media type", Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.w(TAG, "Loading Part URI: " + mediaUri);
|
||||||
|
|
||||||
|
final InputStream is = getInputStream(mediaUri, masterSecret);
|
||||||
|
|
||||||
|
if (mediaType != null && mediaType.startsWith("image/")) {
|
||||||
|
displayImage(is);
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Log.w(TAG, ioe);
|
||||||
|
Toast.makeText(getApplicationContext(), "Could not read the media", Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getInputStream(Uri uri, MasterSecret masterSecret) throws IOException {
|
||||||
|
if (PartProvider.isAuthority(uri)) {
|
||||||
|
return DatabaseFactory.getEncryptingPartDatabase(this, masterSecret).getPartStream(ContentUris.parseId(uri));
|
||||||
|
} else {
|
||||||
|
throw new AssertionError("Given a URI that is not handled by our app.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeResources() {
|
||||||
|
image = (ImageView) findViewById(R.id.image);
|
||||||
|
imageAttacher = new PhotoViewAttacher(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayImage(final InputStream is) {
|
||||||
|
image.setImageBitmap(BitmapFactory.decodeStream(is));
|
||||||
|
image.setVisibility(View.VISIBLE);
|
||||||
|
imageAttacher.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveToDisk() {
|
||||||
|
SaveAttachmentTask.showWarningDialog(this, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this, masterSecret);
|
||||||
|
saveTask.execute(new Attachment(mediaUri, mediaType, date));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
|
super.onPrepareOptionsMenu(menu);
|
||||||
|
|
||||||
|
menu.clear();
|
||||||
|
MenuInflater inflater = this.getSupportMenuInflater();
|
||||||
|
inflater.inflate(R.menu.media_preview, menu);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
super.onOptionsItemSelected(item);
|
||||||
|
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.save: saveToDisk(); return true;
|
||||||
|
case android.R.id.home: finish(); return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isContentTypeSupported(final String contentType) {
|
||||||
|
return contentType != null && contentType.startsWith("image/");
|
||||||
|
}
|
||||||
|
}
|
@ -18,15 +18,19 @@ package org.thoughtcrime.securesms.database.model;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.whispersystems.textsecure.util.ListenableFutureTask;
|
import org.whispersystems.textsecure.util.ListenableFutureTask;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the message record model for MMS messages that contain
|
* Represents the message record model for MMS messages that contain
|
||||||
* media (ie: they've been downloaded).
|
* media (ie: they've been downloaded).
|
||||||
@ -36,10 +40,11 @@ import org.whispersystems.textsecure.util.ListenableFutureTask;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public class MediaMmsMessageRecord extends MessageRecord {
|
public class MediaMmsMessageRecord extends MessageRecord {
|
||||||
|
private final static String TAG = MediaMmsMessageRecord.class.getSimpleName();
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final int partCount;
|
private final int partCount;
|
||||||
private final ListenableFutureTask<SlideDeck> slideDeck;
|
private final ListenableFutureTask<SlideDeck> slideDeckFutureTask;
|
||||||
|
|
||||||
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
|
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
|
||||||
Recipient individualRecipient, int recipientDeviceId,
|
Recipient individualRecipient, int recipientDeviceId,
|
||||||
@ -51,15 +56,48 @@ public class MediaMmsMessageRecord extends MessageRecord {
|
|||||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
||||||
dateSent, dateReceived, threadId, deliveredCount, DELIVERY_STATUS_NONE, mailbox);
|
dateSent, dateReceived, threadId, deliveredCount, DELIVERY_STATUS_NONE, mailbox);
|
||||||
|
|
||||||
this.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
this.partCount = partCount;
|
this.partCount = partCount;
|
||||||
this.slideDeck = slideDeck;
|
this.slideDeckFutureTask = slideDeck;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFutureTask<SlideDeck> getSlideDeck() {
|
public ListenableFutureTask<SlideDeck> getSlideDeckFuture() {
|
||||||
return slideDeck;
|
return slideDeckFutureTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SlideDeck getSlideDeckSync() {
|
||||||
|
try {
|
||||||
|
return slideDeckFutureTask.get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return null;
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsMediaSlide() {
|
||||||
|
SlideDeck deck = getSlideDeckSync();
|
||||||
|
return deck != null && deck.containsMediaSlide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Slide getMediaSlideSync() {
|
||||||
|
SlideDeck deck = getSlideDeckSync();
|
||||||
|
if (deck == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<Slide> slides = deck.getSlides();
|
||||||
|
|
||||||
|
for (Slide slide : slides) {
|
||||||
|
if (slide.hasImage() || slide.hasVideo() || slide.hasAudio()) {
|
||||||
|
return slide;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getPartCount() {
|
public int getPartCount() {
|
||||||
return partCount;
|
return partCount;
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,19 @@ import android.app.ProgressDialog;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
public abstract class ProgressDialogAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
public abstract class ProgressDialogAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
||||||
private final Context context;
|
private final WeakReference<Context> contextReference;
|
||||||
private ProgressDialog progress;
|
private ProgressDialog progress;
|
||||||
private final String title;
|
private final String title;
|
||||||
private final String message;
|
private final String message;
|
||||||
|
|
||||||
public ProgressDialogAsyncTask(Context context, String title, String message) {
|
public ProgressDialogAsyncTask(Context context, String title, String message) {
|
||||||
super();
|
super();
|
||||||
this.context = context;
|
this.contextReference = new WeakReference<Context>(context);
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProgressDialogAsyncTask(Context context, int title, int message) {
|
public ProgressDialogAsyncTask(Context context, int title, int message) {
|
||||||
@ -23,7 +25,8 @@ public abstract class ProgressDialogAsyncTask<Params, Progress, Result> extends
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
progress = ProgressDialog.show(context, title, message, true);
|
final Context context = contextReference.get();
|
||||||
|
if (context != null) progress = ProgressDialog.show(context, title, message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
162
src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java
Normal file
162
src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.media.MediaScannerConnection;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||||
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTask.Attachment, Void, Integer> {
|
||||||
|
private static final String TAG = SaveAttachmentTask.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final int SUCCESS = 0;
|
||||||
|
private static final int FAILURE = 1;
|
||||||
|
private static final int WRITE_ACCESS_FAILURE = 2;
|
||||||
|
|
||||||
|
private final WeakReference<Context> contextReference;
|
||||||
|
private final WeakReference<MasterSecret> masterSecretReference;
|
||||||
|
|
||||||
|
public SaveAttachmentTask(Context context, MasterSecret masterSecret) {
|
||||||
|
super(context, R.string.ConversationFragment_saving_attachment, R.string.ConversationFragment_saving_attachment_to_sd_card);
|
||||||
|
this.contextReference = new WeakReference<Context>(context);
|
||||||
|
this.masterSecretReference = new WeakReference<MasterSecret>(masterSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer doInBackground(SaveAttachmentTask.Attachment... attachments) {
|
||||||
|
if (attachments == null || attachments.length != 1 || attachments[0] == null) {
|
||||||
|
throw new AssertionError("must pass in exactly one attachment");
|
||||||
|
}
|
||||||
|
Attachment attachment = attachments[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
Context context = contextReference.get();
|
||||||
|
MasterSecret masterSecret = masterSecretReference.get();
|
||||||
|
|
||||||
|
if (!Environment.getExternalStorageDirectory().canWrite()) {
|
||||||
|
return WRITE_ACCESS_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
return FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
File mediaFile = constructOutputFile(attachment.contentType, attachment.date);
|
||||||
|
InputStream inputStream = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret).getPartStream(ContentUris.parseId(attachment.uri));
|
||||||
|
OutputStream outputStream = new FileOutputStream(mediaFile);
|
||||||
|
|
||||||
|
org.whispersystems.textsecure.util.Util.copy(inputStream, outputStream);
|
||||||
|
|
||||||
|
MediaScannerConnection.scanFile(context, new String[]{mediaFile.getAbsolutePath()},
|
||||||
|
new String[]{attachment.contentType}, null);
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Log.w(TAG, ioe);
|
||||||
|
return FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Integer result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
Context context = contextReference.get();
|
||||||
|
if (context == null) return;
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case FAILURE:
|
||||||
|
Toast.makeText(context, R.string.ConversationFragment_error_while_saving_attachment_to_sd_card,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
|
case SUCCESS:
|
||||||
|
Toast.makeText(context, R.string.ConversationFragment_success_exclamation,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
|
case WRITE_ACCESS_FAILURE:
|
||||||
|
Toast.makeText(context, R.string.ConversationFragment_unable_to_write_to_sd_card_exclamation,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File constructOutputFile(String contentType, long timestamp) throws IOException {
|
||||||
|
File sdCard = Environment.getExternalStorageDirectory();
|
||||||
|
File outputDirectory;
|
||||||
|
|
||||||
|
if (contentType.startsWith("video/")) {
|
||||||
|
outputDirectory = new File(sdCard.getAbsoluteFile() + File.separator + Environment.DIRECTORY_MOVIES);
|
||||||
|
} else if (contentType.startsWith("audio/")) {
|
||||||
|
outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + Environment.DIRECTORY_MUSIC);
|
||||||
|
} else if (contentType.startsWith("image/")) {
|
||||||
|
outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + Environment.DIRECTORY_PICTURES);
|
||||||
|
} else {
|
||||||
|
outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outputDirectory.mkdirs()) Log.w(TAG, "mkdirs() returned false, attempting to continue");
|
||||||
|
|
||||||
|
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||||
|
String extension = mimeTypeMap.getExtensionFromMimeType(contentType);
|
||||||
|
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd-HHmmss");
|
||||||
|
String base = "textsecure-" + dateFormatter.format(timestamp);
|
||||||
|
|
||||||
|
if (extension == null)
|
||||||
|
extension = "attach";
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
File file = new File(outputDirectory, base + "." + extension);
|
||||||
|
while (file.exists()) {
|
||||||
|
file = new File(outputDirectory, base + "-" + (++i) + "." + extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Attachment {
|
||||||
|
public Uri uri;
|
||||||
|
public String contentType;
|
||||||
|
public long date;
|
||||||
|
|
||||||
|
public Attachment(Uri uri, String contentType, long date) {
|
||||||
|
if (uri == null || contentType == null || date < 0) {
|
||||||
|
throw new AssertionError("uri, content type, and date must all be specified");
|
||||||
|
}
|
||||||
|
if (!PartProvider.isAuthority(uri)) {
|
||||||
|
throw new AssertionError("attachment must be a TextSecure attachment");
|
||||||
|
}
|
||||||
|
this.uri = uri;
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showWarningDialog(Context context, OnClickListener onAcceptListener) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
|
builder.setTitle(R.string.ConversationFragment_save_to_sd_card);
|
||||||
|
builder.setIcon(Dialogs.resolveIcon(context, R.attr.dialog_alert_icon));
|
||||||
|
builder.setCancelable(true);
|
||||||
|
builder.setMessage(R.string.ConversationFragment_this_media_has_been_stored_in_an_encrypted_database_warning);
|
||||||
|
builder.setPositiveButton(R.string.yes, onAcceptListener);
|
||||||
|
builder.setNegativeButton(R.string.no, null);
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user