diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java index 0fc18c60b..3e1351ab3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java @@ -15,5 +15,6 @@ public interface RequestCodes { int REQUEST_UNLOCK = 11; int REQUEST_KEYGUARD_UNLOCK = 12; int REQUEST_ATTACH_IMAGE = 13; + int REQUEST_SAVE_ATTACHMENT = 14; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java index 7c077958e..b8754e755 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java @@ -127,12 +127,14 @@ class AttachmentController { } // calculate thumbnail size - Size thumbnailSize = new Size(defaultSize, defaultSize); + Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType); if (!size.error) { - thumbnailSize = getThumbnailSize(size.width, size.height); + thumbnailSize = + getThumbnailSize(size.width, size.height, size.mimeType); } return new AttachmentItem(messageId, size.width, size.height, - thumbnailSize.width, thumbnailSize.height, size.error); + size.mimeType, thumbnailSize.width, thumbnailSize.height, + size.error); } /** @@ -151,9 +153,9 @@ class AttachmentController { orientation == ORIENTATION_TRANSVERSE || orientation == ORIENTATION_TRANSPOSE) { //noinspection SuspiciousNameCombination - return new Size(height, width); + return new Size(height, width, "image/jpeg"); } - return new Size(width, height); + return new Size(width, height, "image/jpeg"); } /** @@ -165,10 +167,11 @@ class AttachmentController { BitmapFactory.decodeStream(is, null, options); if (options.outWidth < 1 || options.outHeight < 1) return new Size(); - return new Size(options.outWidth, options.outHeight); + return new Size(options.outWidth, options.outHeight, + options.outMimeType); } - private Size getThumbnailSize(int width, int height) { + private Size getThumbnailSize(int width, int height, String mimeType) { float widthPercentage = maxWidth / (float) width; float heightPercentage = maxHeight / (float) height; float scaleFactor = Math.min(widthPercentage, heightPercentage); @@ -184,24 +187,27 @@ class AttachmentController { if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth; if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight; } - return new Size(thumbnailWidth, thumbnailHeight); + return new Size(thumbnailWidth, thumbnailHeight, mimeType); } private static class Size { private final int width; private final int height; + private final String mimeType; private final boolean error; - private Size(int width, int height) { + private Size(int width, int height, String mimeType) { this.width = width; this.height = height; + this.mimeType = mimeType; this.error = false; } private Size() { this.width = 0; this.height = 0; + this.mimeType = ""; this.error = true; } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentItem.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentItem.java index 7f39bfefc..9e36e848a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentItem.java @@ -14,6 +14,7 @@ public class AttachmentItem implements Parcelable { private final MessageId messageId; private final int width, height; + private final String mimeType; private final int thumbnailWidth, thumbnailHeight; private final boolean hasError; @@ -30,11 +31,12 @@ public class AttachmentItem implements Parcelable { } }; - AttachmentItem(MessageId messageId, int width, int height, + AttachmentItem(MessageId messageId, int width, int height, String mimeType, int thumbnailWidth, int thumbnailHeight, boolean hasError) { this.messageId = messageId; this.width = width; this.height = height; + this.mimeType = mimeType; this.thumbnailWidth = thumbnailWidth; this.thumbnailHeight = thumbnailHeight; this.hasError = hasError; @@ -46,6 +48,7 @@ public class AttachmentItem implements Parcelable { messageId = new MessageId(messageIdByte); width = in.readInt(); height = in.readInt(); + mimeType = in.readString(); thumbnailWidth = in.readInt(); thumbnailHeight = in.readInt(); hasError = in.readByte() != 0; @@ -63,6 +66,10 @@ public class AttachmentItem implements Parcelable { return height; } + String getMimeType() { + return mimeType; + } + int getThumbnailWidth() { return thumbnailWidth; } @@ -90,6 +97,7 @@ public class AttachmentItem implements Parcelable { dest.writeByteArray(messageId.getBytes()); dest.writeInt(width); dest.writeInt(height); + dest.writeString(mimeType); dest.writeInt(thumbnailWidth); dest.writeInt(thumbnailHeight); dest.writeByte((byte) (hasError ? 1 : 0)); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java index fb8e8ad4b..6b18a113d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java @@ -1,14 +1,20 @@ package org.briarproject.briar.android.conversation; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.design.widget.AppBarLayout; +import android.support.design.widget.Snackbar; +import android.support.v7.app.AlertDialog.Builder; import android.support.v7.widget.Toolbar; import android.transition.Fade; import android.transition.Transition; +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; @@ -20,14 +26,34 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.github.chrisbanes.photoview.PhotoView; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.conversation.glide.GlideApp; import org.briarproject.briar.android.view.PullDownLayout; +import org.briarproject.briar.api.messaging.Attachment; +import org.briarproject.briar.api.messaging.MessagingManager; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static android.content.Intent.ACTION_CREATE_DOCUMENT; +import static android.content.Intent.CATEGORY_OPENABLE; +import static android.content.Intent.EXTRA_TITLE; import static android.graphics.Color.TRANSPARENT; import static android.os.Build.VERSION.SDK_INT; +import static android.support.design.widget.Snackbar.LENGTH_LONG; import static android.view.View.GONE; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; @@ -37,18 +63,32 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.widget.ImageView.ScaleType.FIT_START; import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE; import static java.util.Objects.requireNonNull; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.IoUtils.copyAndClose; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT; import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute; public class ImageActivity extends BriarActivity implements PullDownLayout.Callback { + private final static Logger LOG = getLogger(ImageActivity.class.getName()); + final static String ATTACHMENT = "attachment"; final static String NAME = "name"; final static String DATE = "date"; + @Inject + MessagingManager messagingManager; + @Inject + @IoExecutor + Executor ioExecutor; + private PullDownLayout layout; private AppBarLayout appBarLayout; private PhotoView photoView; + private AttachmentItem attachment; @Override public void injectActivity(ActivityComponent component) { @@ -88,7 +128,7 @@ public class ImageActivity extends BriarActivity TextView dateView = toolbar.findViewById(R.id.dateView); // Intent Extras - AttachmentItem attachment = getIntent().getParcelableExtra(ATTACHMENT); + attachment = getIntent().getParcelableExtra(ATTACHMENT); String name = getIntent().getStringExtra(NAME); long time = getIntent().getLongExtra(DATE, 0); String date = formatDateAbsolute(this, time); @@ -143,17 +183,37 @@ public class ImageActivity extends BriarActivity .into(photoView); } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.image_actions, menu); + if (SDK_INT >= 19) { + menu.findItem(R.id.action_save_image).setVisible(true); + } + return super.onCreateOptionsMenu(menu); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: onBackPressed(); return true; + case R.id.action_save_image: + if (SDK_INT >= 19) startSaveImage(); + return true; default: return super.onOptionsItemSelected(item); } } + @Override + protected void onActivityResult(int request, int result, Intent data) { + super.onActivityResult(request, result, data); + if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK) { + saveImage(data.getData()); + } + } + @Override public void onPullStart() { appBarLayout.animate() @@ -230,4 +290,66 @@ public class ImageActivity extends BriarActivity drawableTop != appBarLayout.getTop(); } + @RequiresApi(api = 19) + private void startSaveImage() { + OnClickListener okListener = (dialog, which) -> { + Intent intent = getCreationIntent(); + startActivityForResult(intent, REQUEST_SAVE_ATTACHMENT); + }; + Builder builder = new Builder(this, R.style.BriarDialogTheme); + builder.setTitle(getString(R.string.dialog_title_save_image)); + builder.setMessage(getString(R.string.dialog_message_save_image)); + builder.setIcon(R.drawable.emoji_google_1f6af); + builder.setPositiveButton(R.string.save_image, okListener); + builder.setNegativeButton(R.string.cancel, null); + builder.show(); + } + + @RequiresApi(api = 19) + private Intent getCreationIntent() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", + Locale.getDefault()); + String fileName = sdf.format(new Date()); + Intent intent = new Intent(ACTION_CREATE_DOCUMENT); + intent.addCategory(CATEGORY_OPENABLE); + intent.setType(attachment.getMimeType()); + intent.putExtra(EXTRA_TITLE, fileName); + return intent; + } + + private void saveImage(@Nullable Uri uri) { + if (uri == null) return; + MessageId messageId = attachment.getMessageId(); + runOnDbThread(() -> { + try { + Attachment a = messagingManager.getAttachment(messageId); + copyImageFromDb(a, uri); + } catch (DbException e) { + logException(LOG, WARNING, e); + onImageSaveError(); + } + }); + } + + private void copyImageFromDb(Attachment a, Uri uri) { + ioExecutor.execute(() -> { + try { + InputStream is = a.getStream(); + OutputStream os = getContentResolver().openOutputStream(uri); + if (os == null) throw new IOException(); + copyAndClose(is, os); + } catch (IOException e) { + logException(LOG, WARNING, e); + onImageSaveError(); + } + }); + } + + private void onImageSaveError() { + Snackbar s = + Snackbar.make(layout, R.string.save_image_error, LENGTH_LONG); + s.getView().setBackgroundResource(R.color.briar_red); + s.show(); + } + } diff --git a/briar-android/src/main/res/menu/image_actions.xml b/briar-android/src/main/res/menu/image_actions.xml new file mode 100644 index 000000000..86831dbe3 --- /dev/null +++ b/briar-android/src/main/res/menu/image_actions.xml @@ -0,0 +1,11 @@ + +
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index f1e45e2b0..733cb0648 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -139,6 +139,10 @@