Allow saving all attachments of a thread

Closes #3975
This commit is contained in:
Andreas Fehn
2015-08-22 13:03:07 +02:00
committed by Moxie Marlinspike
parent 170a4291de
commit 238471b847
10 changed files with 136 additions and 25 deletions

View File

@@ -348,7 +348,9 @@ public class ConversationFragment extends Fragment
}
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();
Toast.makeText(getActivity(),
getResources().getQuantityString(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, 1),
Toast.LENGTH_LONG).show();
}
});
}

View File

@@ -18,6 +18,7 @@ package org.thoughtcrime.securesms;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.database.Cursor;
import android.os.Build.VERSION;
@@ -29,6 +30,8 @@ import android.support.v4.content.Loader;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
@@ -37,11 +40,17 @@ import android.widget.TextView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ImageDatabase.ImageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import java.util.ArrayList;
import java.util.List;
/**
* Activity for displaying media attachments in-app
@@ -137,12 +146,62 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
}
}
private void saveToDisk() {
final Context c = this;
SaveAttachmentTask.showWarningDialog(this, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
new ProgressDialogAsyncTask<Void, Void, List<SaveAttachmentTask.Attachment>>(c,
R.string.ConversationFragment_collecting_attahments,
R.string.please_wait) {
@Override
protected List<SaveAttachmentTask.Attachment> doInBackground(Void... params) {
Cursor cursor = DatabaseFactory.getImageDatabase(c).getImagesForThread(threadId);
List<SaveAttachmentTask.Attachment> attachments = new ArrayList<>(cursor.getCount());
while (cursor != null && cursor.moveToNext()) {
ImageRecord record = ImageRecord.from(cursor);
attachments.add(new SaveAttachmentTask.Attachment(record.getAttachment().getDataUri(),
record.getContentType(),
record.getDate()));
}
return attachments;
}
@Override
protected void onPostExecute(List<SaveAttachmentTask.Attachment> attachments) {
super.onPostExecute(attachments);
SaveAttachmentTask saveTask = new SaveAttachmentTask(c, masterSecret, attachments.size());
saveTask.execute(attachments.toArray(new SaveAttachmentTask.Attachment[attachments.size()]));
}
}.execute();
}
}, gridView.getAdapter().getItemCount());
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
if (gridView.getAdapter() != null && gridView.getAdapter().getItemCount() > 0) {
MenuInflater inflater = this.getMenuInflater();
inflater.inflate(R.menu.media_overview, menu);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home: finish(); return true;
case R.id.save: saveToDisk(); return true;
case android.R.id.home: finish(); return true;
}
return false;
@@ -158,6 +217,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
Log.w(TAG, "onLoadFinished()");
gridView.setAdapter(new ImageMediaAdapter(this, masterSecret, cursor));
noImages.setVisibility(gridView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE);
invalidateOptionsMenu();
}
@Override

View File

@@ -33,18 +33,26 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
private final WeakReference<Context> contextReference;
private final WeakReference<MasterSecret> masterSecretReference;
private final int attachmentCount;
public SaveAttachmentTask(Context context, MasterSecret masterSecret) {
super(context, R.string.ConversationFragment_saving_attachment, R.string.ConversationFragment_saving_attachment_to_sd_card);
this(context, masterSecret, 1);
}
public SaveAttachmentTask(Context context, MasterSecret masterSecret, int count) {
super(context,
context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_attachments, count, count),
context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_attachments_to_sd_card, count, count));
this.contextReference = new WeakReference<>(context);
this.masterSecretReference = new WeakReference<>(masterSecret);
this.attachmentCount = count;
}
@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");
if (attachments == null || attachments.length == 0) {
throw new AssertionError("must pass in at least one attachment");
}
Attachment attachment = attachments[0];
try {
Context context = contextReference.get();
@@ -58,20 +66,12 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
return FAILURE;
}
String contentType = MediaUtil.getCorrectedMimeType(attachment.contentType);
File mediaFile = constructOutputFile(contentType, attachment.date);
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, attachment.uri);
if (inputStream == null) {
return FAILURE;
for (Attachment attachment : attachments) {
if (attachment != null && !saveAttachment(context, masterSecret, attachment)) {
return FAILURE;
}
}
OutputStream outputStream = new FileOutputStream(mediaFile);
Util.copy(inputStream, outputStream);
MediaScannerConnection.scanFile(context, new String[]{mediaFile.getAbsolutePath()},
new String[]{contentType}, null);
return SUCCESS;
} catch (IOException ioe) {
Log.w(TAG, ioe);
@@ -79,6 +79,24 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
}
}
private boolean saveAttachment(Context context, MasterSecret masterSecret, Attachment attachment) throws IOException {
String contentType = MediaUtil.getCorrectedMimeType(attachment.contentType);
File mediaFile = constructOutputFile(contentType, attachment.date);
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, attachment.uri);
if (inputStream == null) {
return false;
}
OutputStream outputStream = new FileOutputStream(mediaFile);
Util.copy(inputStream, outputStream);
MediaScannerConnection.scanFile(context, new String[]{mediaFile.getAbsolutePath()},
new String[]{contentType}, null);
return true;
}
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
@@ -87,8 +105,10 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
switch (result) {
case FAILURE:
Toast.makeText(context, R.string.ConversationFragment_error_while_saving_attachment_to_sd_card,
Toast.LENGTH_LONG).show();
Toast.makeText(context,
context.getResources().getQuantityText(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card,
attachmentCount),
Toast.LENGTH_LONG).show();
break;
case SUCCESS:
Toast.makeText(context, R.string.ConversationFragment_success_exclamation,
@@ -149,11 +169,16 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
}
public static void showWarningDialog(Context context, OnClickListener onAcceptListener) {
showWarningDialog(context, onAcceptListener, 1);
}
public static void showWarningDialog(Context context, OnClickListener onAcceptListener, int count) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.ConversationFragment_save_to_sd_card);
builder.setIconAttribute(R.attr.dialog_alert_icon);
builder.setCancelable(true);
builder.setMessage(R.string.ConversationFragment_saving_this_media_to_storage_warning);
builder.setMessage(context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_media_to_storage_warning,
count, count));
builder.setPositiveButton(R.string.yes, onAcceptListener);
builder.setNegativeButton(R.string.no, null);
builder.show();