Skip to content
Snippets Groups Projects
Verified Commit 77299a68 authored by Torsten Grote's avatar Torsten Grote
Browse files

[android] Allow the user to save image attachment outside of Briar

parent 5e5705c7
No related branches found
No related tags found
1 merge request!1005Allow the user to save image attachment outside of Briar
...@@ -15,5 +15,6 @@ public interface RequestCodes { ...@@ -15,5 +15,6 @@ public interface RequestCodes {
int REQUEST_UNLOCK = 11; int REQUEST_UNLOCK = 11;
int REQUEST_KEYGUARD_UNLOCK = 12; int REQUEST_KEYGUARD_UNLOCK = 12;
int REQUEST_ATTACH_IMAGE = 13; int REQUEST_ATTACH_IMAGE = 13;
int REQUEST_SAVE_ATTACHMENT = 14;
} }
...@@ -127,12 +127,14 @@ class AttachmentController { ...@@ -127,12 +127,14 @@ class AttachmentController {
} }
// calculate thumbnail size // calculate thumbnail size
Size thumbnailSize = new Size(defaultSize, defaultSize); Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
if (!size.error) { 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, 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 { ...@@ -151,9 +153,9 @@ class AttachmentController {
orientation == ORIENTATION_TRANSVERSE || orientation == ORIENTATION_TRANSVERSE ||
orientation == ORIENTATION_TRANSPOSE) { orientation == ORIENTATION_TRANSPOSE) {
//noinspection SuspiciousNameCombination //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 { ...@@ -165,10 +167,11 @@ class AttachmentController {
BitmapFactory.decodeStream(is, null, options); BitmapFactory.decodeStream(is, null, options);
if (options.outWidth < 1 || options.outHeight < 1) if (options.outWidth < 1 || options.outHeight < 1)
return new Size(); 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 widthPercentage = maxWidth / (float) width;
float heightPercentage = maxHeight / (float) height; float heightPercentage = maxHeight / (float) height;
float scaleFactor = Math.min(widthPercentage, heightPercentage); float scaleFactor = Math.min(widthPercentage, heightPercentage);
...@@ -184,24 +187,27 @@ class AttachmentController { ...@@ -184,24 +187,27 @@ class AttachmentController {
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth; if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight; if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
} }
return new Size(thumbnailWidth, thumbnailHeight); return new Size(thumbnailWidth, thumbnailHeight, mimeType);
} }
private static class Size { private static class Size {
private final int width; private final int width;
private final int height; private final int height;
private final String mimeType;
private final boolean error; private final boolean error;
private Size(int width, int height) { private Size(int width, int height, String mimeType) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.mimeType = mimeType;
this.error = false; this.error = false;
} }
private Size() { private Size() {
this.width = 0; this.width = 0;
this.height = 0; this.height = 0;
this.mimeType = "";
this.error = true; this.error = true;
} }
} }
......
...@@ -14,6 +14,7 @@ public class AttachmentItem implements Parcelable { ...@@ -14,6 +14,7 @@ public class AttachmentItem implements Parcelable {
private final MessageId messageId; private final MessageId messageId;
private final int width, height; private final int width, height;
private final String mimeType;
private final int thumbnailWidth, thumbnailHeight; private final int thumbnailWidth, thumbnailHeight;
private final boolean hasError; private final boolean hasError;
...@@ -30,11 +31,12 @@ public class AttachmentItem implements Parcelable { ...@@ -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) { int thumbnailWidth, int thumbnailHeight, boolean hasError) {
this.messageId = messageId; this.messageId = messageId;
this.width = width; this.width = width;
this.height = height; this.height = height;
this.mimeType = mimeType;
this.thumbnailWidth = thumbnailWidth; this.thumbnailWidth = thumbnailWidth;
this.thumbnailHeight = thumbnailHeight; this.thumbnailHeight = thumbnailHeight;
this.hasError = hasError; this.hasError = hasError;
...@@ -46,6 +48,7 @@ public class AttachmentItem implements Parcelable { ...@@ -46,6 +48,7 @@ public class AttachmentItem implements Parcelable {
messageId = new MessageId(messageIdByte); messageId = new MessageId(messageIdByte);
width = in.readInt(); width = in.readInt();
height = in.readInt(); height = in.readInt();
mimeType = in.readString();
thumbnailWidth = in.readInt(); thumbnailWidth = in.readInt();
thumbnailHeight = in.readInt(); thumbnailHeight = in.readInt();
hasError = in.readByte() != 0; hasError = in.readByte() != 0;
...@@ -63,6 +66,10 @@ public class AttachmentItem implements Parcelable { ...@@ -63,6 +66,10 @@ public class AttachmentItem implements Parcelable {
return height; return height;
} }
String getMimeType() {
return mimeType;
}
int getThumbnailWidth() { int getThumbnailWidth() {
return thumbnailWidth; return thumbnailWidth;
} }
...@@ -90,6 +97,7 @@ public class AttachmentItem implements Parcelable { ...@@ -90,6 +97,7 @@ public class AttachmentItem implements Parcelable {
dest.writeByteArray(messageId.getBytes()); dest.writeByteArray(messageId.getBytes());
dest.writeInt(width); dest.writeInt(width);
dest.writeInt(height); dest.writeInt(height);
dest.writeString(mimeType);
dest.writeInt(thumbnailWidth); dest.writeInt(thumbnailWidth);
dest.writeInt(thumbnailHeight); dest.writeInt(thumbnailHeight);
dest.writeByte((byte) (hasError ? 1 : 0)); dest.writeByte((byte) (hasError ? 1 : 0));
......
package org.briarproject.briar.android.conversation; package org.briarproject.briar.android.conversation;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.support.design.widget.AppBarLayout; 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.support.v7.widget.Toolbar;
import android.transition.Fade; import android.transition.Fade;
import android.transition.Transition; import android.transition.Transition;
import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
...@@ -20,14 +26,34 @@ import com.bumptech.glide.request.RequestListener; ...@@ -20,14 +26,34 @@ import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.Target;
import com.github.chrisbanes.photoview.PhotoView; 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.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.conversation.glide.GlideApp; import org.briarproject.briar.android.conversation.glide.GlideApp;
import org.briarproject.briar.android.view.PullDownLayout; 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.graphics.Color.TRANSPARENT;
import static android.os.Build.VERSION.SDK_INT; 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.GONE;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
...@@ -37,18 +63,32 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; ...@@ -37,18 +63,32 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.widget.ImageView.ScaleType.FIT_START; import static android.widget.ImageView.ScaleType.FIT_START;
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE; import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
import static java.util.Objects.requireNonNull; 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; import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
public class ImageActivity extends BriarActivity public class ImageActivity extends BriarActivity
implements PullDownLayout.Callback { implements PullDownLayout.Callback {
private final static Logger LOG = getLogger(ImageActivity.class.getName());
final static String ATTACHMENT = "attachment"; final static String ATTACHMENT = "attachment";
final static String NAME = "name"; final static String NAME = "name";
final static String DATE = "date"; final static String DATE = "date";
@Inject
MessagingManager messagingManager;
@Inject
@IoExecutor
Executor ioExecutor;
private PullDownLayout layout; private PullDownLayout layout;
private AppBarLayout appBarLayout; private AppBarLayout appBarLayout;
private PhotoView photoView; private PhotoView photoView;
private AttachmentItem attachment;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
...@@ -88,7 +128,7 @@ public class ImageActivity extends BriarActivity ...@@ -88,7 +128,7 @@ public class ImageActivity extends BriarActivity
TextView dateView = toolbar.findViewById(R.id.dateView); TextView dateView = toolbar.findViewById(R.id.dateView);
// Intent Extras // Intent Extras
AttachmentItem attachment = getIntent().getParcelableExtra(ATTACHMENT); attachment = getIntent().getParcelableExtra(ATTACHMENT);
String name = getIntent().getStringExtra(NAME); String name = getIntent().getStringExtra(NAME);
long time = getIntent().getLongExtra(DATE, 0); long time = getIntent().getLongExtra(DATE, 0);
String date = formatDateAbsolute(this, time); String date = formatDateAbsolute(this, time);
...@@ -143,17 +183,37 @@ public class ImageActivity extends BriarActivity ...@@ -143,17 +183,37 @@ public class ImageActivity extends BriarActivity
.into(photoView); .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 @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case android.R.id.home: case android.R.id.home:
onBackPressed(); onBackPressed();
return true; return true;
case R.id.action_save_image:
if (SDK_INT >= 19) startSaveImage();
return true;
default: default:
return super.onOptionsItemSelected(item); 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 @Override
public void onPullStart() { public void onPullStart() {
appBarLayout.animate() appBarLayout.animate()
...@@ -230,4 +290,66 @@ public class ImageActivity extends BriarActivity ...@@ -230,4 +290,66 @@ public class ImageActivity extends BriarActivity
drawableTop != appBarLayout.getTop(); 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();
}
} }
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_save_image"
android:title="@string/save_image"
android:visible="false"
app:showAsAction="never"/>
</menu>
...@@ -139,6 +139,10 @@ ...@@ -139,6 +139,10 @@
<string name="contact_deleted_toast">Contact deleted</string> <string name="contact_deleted_toast">Contact deleted</string>
<!-- This is shown in the action bar when opening an image in fullscreen that the user sent --> <!-- This is shown in the action bar when opening an image in fullscreen that the user sent -->
<string name="you">You</string> <string name="you">You</string>
<string name="save_image">Save image</string>
<string name="dialog_title_save_image">Save Image?</string>
<string name="dialog_message_save_image">Saving this image will allow other apps to access it.\n\nAre you sure you want to save?</string>
<string name="save_image_error">Could not save image</string>
<!-- Adding Contacts --> <!-- Adding Contacts -->
<string name="add_contact_title">Add a Contact</string> <string name="add_contact_title">Add a Contact</string>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment