mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-27 11:42:16 -05:00
Fetch attachments while MessageCompose activity is running
Android allows other apps to access protected content of an app without requesting the necessary permission when the app returns an Intent with FLAG_GRANT_READ_URI_PERMISSION. This regularly happens as a result of ACTION_GET_CONTENT, i.e. what we use to pick content to be attached to a message. Accessing that content only works while the receiving activity is running. Afterwards accessing the content throws a SecurityException because of the missing permission. This commit changes K-9 Mail's behavior to copy the content to a temporary file in K-9's cache directory while the activity is still running. Fixes issue 4847, 5821 This also fixes bugs related to the fact that K-9 Mail didn't save a copy of attached content in the message database. Fixes issue 1187, 3330, 4930
This commit is contained in:
parent
8d0f697e36
commit
62aa1b87d0
@ -1,36 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="54dip"
|
||||
android:paddingRight="6dip"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingBottom="6dip">
|
||||
<ImageButton
|
||||
android:id="@+id/attachment_delete"
|
||||
android:src="@drawable/ic_delete"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_height="42dip"
|
||||
android:layout_width="42dip" />
|
||||
<TextView
|
||||
android:id="@+id/attachment_name"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_width="1dip"
|
||||
android:layout_height="42dip"
|
||||
android:background="?attr/messageViewAttachmentBackground"
|
||||
android:paddingLeft="36dip"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="start"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginLeft="6dip"
|
||||
android:layout_marginRight="4dip"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_toLeftOf="@id/attachment_delete" />
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_email_attachment"
|
||||
android:layout_marginLeft="1dip"
|
||||
android:layout_centerVertical="true" />
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="54dip"
|
||||
android:paddingRight="6dip"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingBottom="6dip">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/attachment_delete"
|
||||
android:src="@drawable/ic_delete"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_height="42dip"
|
||||
android:layout_width="42dip" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="1dip"
|
||||
android:layout_height="42dip"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_toLeftOf="@id/attachment_delete"
|
||||
android:layout_marginLeft="6dip"
|
||||
android:layout_marginRight="4dip"
|
||||
android:paddingLeft="36dip"
|
||||
android:gravity="center_vertical"
|
||||
android:background="?attr/messageViewAttachmentBackground"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attachment_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="start"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleSmall"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_marginLeft="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_email_attachment"
|
||||
android:layout_marginLeft="1dip"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
@ -1147,4 +1147,6 @@ Please submit bug reports, contribute new features and ask questions at
|
||||
<string name="preposition_for_date">on <xliff:g id="date">%s</xliff:g></string>
|
||||
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
|
||||
<string name="loading_attachment">Loading attachment…</string>
|
||||
</resources>
|
||||
|
@ -3,12 +3,12 @@ package com.fsck.k9.activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockActivity;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.fsck.k9.activity.K9ActivityCommon.K9ActivityMagic;
|
||||
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
|
||||
|
||||
|
||||
public class K9Activity extends SherlockActivity implements K9ActivityMagic {
|
||||
public class K9Activity extends SherlockFragmentActivity implements K9ActivityMagic {
|
||||
|
||||
private K9ActivityCommon mBase;
|
||||
|
||||
|
@ -5,19 +5,18 @@ import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.app.Dialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.util.Rfc822Tokenizer;
|
||||
import android.util.Log;
|
||||
@ -55,6 +54,9 @@ import com.fsck.k9.Identity;
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.activity.loader.AttachmentContentLoader;
|
||||
import com.fsck.k9.activity.loader.AttachmentInfoLoader;
|
||||
import com.fsck.k9.activity.misc.Attachment;
|
||||
import com.fsck.k9.controller.MessagingController;
|
||||
import com.fsck.k9.controller.MessagingListener;
|
||||
import com.fsck.k9.crypto.CryptoProvider;
|
||||
@ -86,14 +88,13 @@ import org.htmlcleaner.CleanerProperties;
|
||||
import org.htmlcleaner.HtmlCleaner;
|
||||
import org.htmlcleaner.SimpleHtmlSerializer;
|
||||
import org.htmlcleaner.TagNode;
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -147,6 +148,8 @@ public class MessageCompose extends K9Activity implements OnClickListener {
|
||||
private static final String STATE_KEY_QUOTED_TEXT_FORMAT =
|
||||
"com.fsck.k9.activity.MessageCompose.quotedTextFormat";
|
||||
|
||||
private static final String LOADER_ARG_ATTACHMENT = "attachment";
|
||||
|
||||
private static final int MSG_PROGRESS_ON = 1;
|
||||
private static final int MSG_PROGRESS_OFF = 2;
|
||||
private static final int MSG_SKIPPED_ATTACHMENTS = 3;
|
||||
@ -218,6 +221,7 @@ public class MessageCompose extends K9Activity implements OnClickListener {
|
||||
* have already been added from the restore of the view state.
|
||||
*/
|
||||
private boolean mSourceMessageProcessed = false;
|
||||
private int mMaxLoaderId = 0;
|
||||
|
||||
enum Action {
|
||||
COMPOSE,
|
||||
@ -365,14 +369,6 @@ public class MessageCompose extends K9Activity implements OnClickListener {
|
||||
private ContextThemeWrapper mThemeContext;
|
||||
|
||||
|
||||
static class Attachment implements Serializable {
|
||||
private static final long serialVersionUID = 3642382876618963734L;
|
||||
public String name;
|
||||
public String contentType;
|
||||
public long size;
|
||||
public Uri uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a new message using the given account. If account is null the default account
|
||||
* will be used.
|
||||
@ -1077,12 +1073,13 @@ public class MessageCompose extends K9Activity implements OnClickListener {
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
ArrayList<Uri> attachments = new ArrayList<Uri>();
|
||||
ArrayList<Attachment> attachments = new ArrayList<Attachment>();
|
||||
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
|
||||
View view = mAttachments.getChildAt(i);
|
||||
Attachment attachment = (Attachment) view.getTag();
|
||||
attachments.add(attachment.uri);
|
||||
attachments.add(attachment);
|
||||
}
|
||||
|
||||
outState.putParcelableArrayList(STATE_KEY_ATTACHMENTS, attachments);
|
||||
outState.putBoolean(STATE_KEY_CC_SHOWN, mCcWrapper.getVisibility() == View.VISIBLE);
|
||||
outState.putBoolean(STATE_KEY_BCC_SHOWN, mBccWrapper.getVisibility() == View.VISIBLE);
|
||||
@ -1104,11 +1101,22 @@ public class MessageCompose extends K9Activity implements OnClickListener {
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
ArrayList<Parcelable> attachments = savedInstanceState.getParcelableArrayList(STATE_KEY_ATTACHMENTS);
|
||||
|
||||
mAttachments.removeAllViews();
|
||||
for (Parcelable p : attachments) {
|
||||
Uri uri = (Uri) p;
|
||||
addAttachment(uri);
|
||||
mMaxLoaderId = 0;
|
||||
|
||||
ArrayList<Attachment> attachments = savedInstanceState.getParcelableArrayList(STATE_KEY_ATTACHMENTS);
|
||||
for (Attachment attachment : attachments) {
|
||||
addAttachmentView(attachment);
|
||||
if (attachment.loaderId > mMaxLoaderId) {
|
||||
mMaxLoaderId = attachment.loaderId;
|
||||
}
|
||||
|
||||
if (attachment.state == Attachment.LoadingState.URI_ONLY) {
|
||||
initAttachmentInfoLoader(attachment);
|
||||
} else if (attachment.state == Attachment.LoadingState.METADATA) {
|
||||
initAttachmentContentLoader(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
mReadReceipt = savedInstanceState
|
||||
@ -1474,8 +1482,11 @@ public class MessageCompose extends K9Activity implements OnClickListener {
|
||||
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
|
||||
Attachment attachment = (Attachment) mAttachments.getChildAt(i).getTag();
|
||||
|
||||
MimeBodyPart bp = new MimeBodyPart(
|
||||
new LocalStore.LocalAttachmentBody(attachment.uri, getApplication()));
|
||||
if (attachment.state != Attachment.LoadingState.COMPLETE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MimeBodyPart bp = new MimeBodyPart(new LocalStore.TempFileBody(attachment.filename));
|
||||
|
||||
/*
|
||||
* Correctly encode the filename here. Otherwise the whole
|
||||
@ -1911,71 +1922,131 @@ public class MessageCompose extends K9Activity implements OnClickListener {
|
||||
}
|
||||
|
||||
private void addAttachment(Uri uri, String contentType) {
|
||||
long size = -1;
|
||||
String name = null;
|
||||
|
||||
ContentResolver contentResolver = getContentResolver();
|
||||
|
||||
Cursor metadataCursor = contentResolver.query(
|
||||
uri,
|
||||
new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE },
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
|
||||
if (metadataCursor != null) {
|
||||
try {
|
||||
if (metadataCursor.moveToFirst()) {
|
||||
name = metadataCursor.getString(0);
|
||||
size = metadataCursor.getInt(1);
|
||||
}
|
||||
} finally {
|
||||
metadataCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
name = uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
String usableContentType = contentType;
|
||||
if ((usableContentType == null) || (usableContentType.indexOf('*') != -1)) {
|
||||
usableContentType = contentResolver.getType(uri);
|
||||
}
|
||||
if (usableContentType == null) {
|
||||
usableContentType = MimeUtility.getMimeTypeByExtension(name);
|
||||
}
|
||||
|
||||
if (size <= 0) {
|
||||
String uriString = uri.toString();
|
||||
if (uriString.startsWith("file://")) {
|
||||
Log.v(K9.LOG_TAG, uriString.substring("file://".length()));
|
||||
File f = new File(uriString.substring("file://".length()));
|
||||
size = f.length();
|
||||
} else {
|
||||
Log.v(K9.LOG_TAG, "Not a file: " + uriString);
|
||||
}
|
||||
} else {
|
||||
Log.v(K9.LOG_TAG, "old attachment.size: " + size);
|
||||
}
|
||||
Log.v(K9.LOG_TAG, "new attachment.size: " + size);
|
||||
|
||||
Attachment attachment = new Attachment();
|
||||
attachment.state = Attachment.LoadingState.URI_ONLY;
|
||||
attachment.uri = uri;
|
||||
attachment.contentType = usableContentType;
|
||||
attachment.name = name;
|
||||
attachment.size = size;
|
||||
attachment.contentType = contentType;
|
||||
attachment.loaderId = ++mMaxLoaderId;
|
||||
|
||||
addAttachmentView(attachment);
|
||||
|
||||
initAttachmentInfoLoader(attachment);
|
||||
}
|
||||
|
||||
private void initAttachmentInfoLoader(Attachment attachment) {
|
||||
LoaderManager loaderManager = getSupportLoaderManager();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(LOADER_ARG_ATTACHMENT, attachment);
|
||||
loaderManager.initLoader(attachment.loaderId, bundle, mAttachmentInfoLoaderCallback);
|
||||
}
|
||||
|
||||
private void initAttachmentContentLoader(Attachment attachment) {
|
||||
LoaderManager loaderManager = getSupportLoaderManager();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(LOADER_ARG_ATTACHMENT, attachment);
|
||||
loaderManager.initLoader(attachment.loaderId, bundle, mAttachmentContentLoaderCallback);
|
||||
}
|
||||
|
||||
private void addAttachmentView(Attachment attachment) {
|
||||
boolean hasMetadata = (attachment.state != Attachment.LoadingState.URI_ONLY);
|
||||
boolean isLoadingComplete = (attachment.state == Attachment.LoadingState.COMPLETE);
|
||||
|
||||
View view = getLayoutInflater().inflate(R.layout.message_compose_attachment, mAttachments, false);
|
||||
TextView nameView = (TextView)view.findViewById(R.id.attachment_name);
|
||||
ImageButton delete = (ImageButton)view.findViewById(R.id.attachment_delete);
|
||||
nameView.setText(attachment.name);
|
||||
delete.setOnClickListener(this);
|
||||
TextView nameView = (TextView) view.findViewById(R.id.attachment_name);
|
||||
View progressBar = view.findViewById(R.id.progressBar);
|
||||
|
||||
if (hasMetadata) {
|
||||
nameView.setText(attachment.name);
|
||||
} else {
|
||||
nameView.setText(R.string.loading_attachment);
|
||||
}
|
||||
|
||||
progressBar.setVisibility(isLoadingComplete ? View.GONE : View.VISIBLE);
|
||||
|
||||
ImageButton delete = (ImageButton) view.findViewById(R.id.attachment_delete);
|
||||
delete.setOnClickListener(MessageCompose.this);
|
||||
delete.setTag(view);
|
||||
|
||||
view.setTag(attachment);
|
||||
mAttachments.addView(view);
|
||||
}
|
||||
|
||||
private View getAttachmentView(int loaderId) {
|
||||
for (int i = 0, childCount = mAttachments.getChildCount(); i < childCount; i++) {
|
||||
View view = mAttachments.getChildAt(i);
|
||||
Attachment tag = (Attachment) view.getTag();
|
||||
if (tag != null && tag.loaderId == loaderId) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks<Attachment> mAttachmentInfoLoaderCallback =
|
||||
new LoaderManager.LoaderCallbacks<Attachment>() {
|
||||
@Override
|
||||
public Loader<Attachment> onCreateLoader(int id, Bundle args) {
|
||||
Attachment attachment = args.getParcelable(LOADER_ARG_ATTACHMENT);
|
||||
return new AttachmentInfoLoader(MessageCompose.this, attachment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Attachment> loader, Attachment attachment) {
|
||||
int loaderId = loader.getId();
|
||||
|
||||
View view = getAttachmentView(loaderId);
|
||||
if (view != null) {
|
||||
view.setTag(attachment);
|
||||
|
||||
TextView nameView = (TextView) view.findViewById(R.id.attachment_name);
|
||||
nameView.setText(attachment.name);
|
||||
|
||||
attachment.loaderId = ++mMaxLoaderId;
|
||||
initAttachmentContentLoader(attachment);
|
||||
}
|
||||
|
||||
getSupportLoaderManager().destroyLoader(loaderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Attachment> loader) {
|
||||
}
|
||||
};
|
||||
|
||||
private LoaderManager.LoaderCallbacks<Attachment> mAttachmentContentLoaderCallback =
|
||||
new LoaderManager.LoaderCallbacks<Attachment>() {
|
||||
@Override
|
||||
public Loader<Attachment> onCreateLoader(int id, Bundle args) {
|
||||
Attachment attachment = args.getParcelable(LOADER_ARG_ATTACHMENT);
|
||||
return new AttachmentContentLoader(MessageCompose.this, attachment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Attachment> loader, Attachment attachment) {
|
||||
int loaderId = loader.getId();
|
||||
|
||||
View view = getAttachmentView(loaderId);
|
||||
if (view != null) {
|
||||
if (attachment.state == Attachment.LoadingState.COMPLETE) {
|
||||
view.setTag(attachment);
|
||||
|
||||
View progressBar = view.findViewById(R.id.progressBar);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
} else {
|
||||
mAttachments.removeView(view);
|
||||
}
|
||||
}
|
||||
|
||||
getSupportLoaderManager().destroyLoader(loaderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Attachment> loader) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// if a CryptoSystem activity is returning, then mPreventDraftSaving was set to true
|
||||
|
81
src/com/fsck/k9/activity/loader/AttachmentContentLoader.java
Normal file
81
src/com/fsck/k9/activity/loader/AttachmentContentLoader.java
Normal file
@ -0,0 +1,81 @@
|
||||
package com.fsck.k9.activity.loader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.util.Log;
|
||||
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.activity.misc.Attachment;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Loader to fetch the content of an attachment.
|
||||
*
|
||||
* This will copy the data to a temporary file in our app's cache directory.
|
||||
*/
|
||||
public class AttachmentContentLoader extends AsyncTaskLoader<Attachment> {
|
||||
private static final String FILENAME_PREFIX = "attachment";
|
||||
|
||||
private final Attachment mAttachment;
|
||||
|
||||
public AttachmentContentLoader(Context context, Attachment attachment) {
|
||||
super(context);
|
||||
mAttachment = attachment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mAttachment.state == Attachment.LoadingState.COMPLETE) {
|
||||
deliverResult(mAttachment);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mAttachment.state == Attachment.LoadingState.METADATA) {
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment loadInBackground() {
|
||||
Context context = getContext();
|
||||
|
||||
try {
|
||||
File file = File.createTempFile(FILENAME_PREFIX, null, context.getCacheDir());
|
||||
file.deleteOnExit();
|
||||
|
||||
if (K9.DEBUG) {
|
||||
Log.v(K9.LOG_TAG, "Saving attachment to " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
InputStream in = context.getContentResolver().openInputStream(mAttachment.uri);
|
||||
try {
|
||||
FileOutputStream out = new FileOutputStream(file);
|
||||
try {
|
||||
IOUtils.copy(in, out);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
|
||||
mAttachment.filename = file.getAbsolutePath();
|
||||
mAttachment.state = Attachment.LoadingState.COMPLETE;
|
||||
|
||||
return mAttachment;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
mAttachment.filename = null;
|
||||
mAttachment.state = Attachment.LoadingState.CANCELLED;
|
||||
|
||||
return mAttachment;
|
||||
}
|
||||
}
|
100
src/com/fsck/k9/activity/loader/AttachmentInfoLoader.java
Normal file
100
src/com/fsck/k9/activity/loader/AttachmentInfoLoader.java
Normal file
@ -0,0 +1,100 @@
|
||||
package com.fsck.k9.activity.loader;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.util.Log;
|
||||
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.activity.misc.Attachment;
|
||||
import com.fsck.k9.mail.internet.MimeUtility;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Loader to fetch metadata of an attachment.
|
||||
*/
|
||||
public class AttachmentInfoLoader extends AsyncTaskLoader<Attachment> {
|
||||
private final Attachment mAttachment;
|
||||
|
||||
public AttachmentInfoLoader(Context context, Attachment attachment) {
|
||||
super(context);
|
||||
mAttachment = attachment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mAttachment.state == Attachment.LoadingState.METADATA) {
|
||||
deliverResult(mAttachment);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mAttachment.state == Attachment.LoadingState.URI_ONLY) {
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment loadInBackground() {
|
||||
Uri uri = mAttachment.uri;
|
||||
String contentType = mAttachment.contentType;
|
||||
|
||||
long size = -1;
|
||||
String name = null;
|
||||
|
||||
ContentResolver contentResolver = getContext().getContentResolver();
|
||||
|
||||
Cursor metadataCursor = contentResolver.query(
|
||||
uri,
|
||||
new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE },
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
|
||||
if (metadataCursor != null) {
|
||||
try {
|
||||
if (metadataCursor.moveToFirst()) {
|
||||
name = metadataCursor.getString(0);
|
||||
size = metadataCursor.getInt(1);
|
||||
}
|
||||
} finally {
|
||||
metadataCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
name = uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
String usableContentType = contentType;
|
||||
if ((usableContentType == null) || (usableContentType.indexOf('*') != -1)) {
|
||||
usableContentType = contentResolver.getType(uri);
|
||||
}
|
||||
if (usableContentType == null) {
|
||||
usableContentType = MimeUtility.getMimeTypeByExtension(name);
|
||||
}
|
||||
|
||||
if (size <= 0) {
|
||||
String uriString = uri.toString();
|
||||
if (uriString.startsWith("file://")) {
|
||||
Log.v(K9.LOG_TAG, uriString.substring("file://".length()));
|
||||
File f = new File(uriString.substring("file://".length()));
|
||||
size = f.length();
|
||||
} else {
|
||||
Log.v(K9.LOG_TAG, "Not a file: " + uriString);
|
||||
}
|
||||
} else {
|
||||
Log.v(K9.LOG_TAG, "old attachment.size: " + size);
|
||||
}
|
||||
Log.v(K9.LOG_TAG, "new attachment.size: " + size);
|
||||
|
||||
mAttachment.contentType = usableContentType;
|
||||
mAttachment.name = name;
|
||||
mAttachment.size = size;
|
||||
mAttachment.state = Attachment.LoadingState.METADATA;
|
||||
|
||||
return mAttachment;
|
||||
}
|
||||
}
|
129
src/com/fsck/k9/activity/misc/Attachment.java
Normal file
129
src/com/fsck/k9/activity/misc/Attachment.java
Normal file
@ -0,0 +1,129 @@
|
||||
package com.fsck.k9.activity.misc;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Container class for information about an attachment.
|
||||
*
|
||||
* This is used by {@link com.fsck.k9.activity.MessageCompose} to fetch and manage attachments.
|
||||
*/
|
||||
public class Attachment implements Parcelable {
|
||||
/**
|
||||
* The URI pointing to the source of the attachment.
|
||||
*
|
||||
* In most cases this will be a {@code content://}-URI.
|
||||
*/
|
||||
public Uri uri;
|
||||
|
||||
/**
|
||||
* The current loading state.
|
||||
*/
|
||||
public LoadingState state;
|
||||
|
||||
/**
|
||||
* The ID of the loader that is used to load the metadata or contents.
|
||||
*/
|
||||
public int loaderId;
|
||||
|
||||
/**
|
||||
* The content type of the attachment.
|
||||
*
|
||||
* Only valid when {@link #state} is {@link LoadingState#METADATA} or
|
||||
* {@link LoadingState#COMPLETE}.
|
||||
*/
|
||||
public String contentType;
|
||||
|
||||
/**
|
||||
* The (file)name of the attachment.
|
||||
*
|
||||
* Only valid when {@link #state} is {@link LoadingState#METADATA} or
|
||||
* {@link LoadingState#COMPLETE}.
|
||||
*/
|
||||
public String name;
|
||||
|
||||
/**
|
||||
* The size of the attachment.
|
||||
*
|
||||
* Only valid when {@link #state} is {@link LoadingState#METADATA} or
|
||||
* {@link LoadingState#COMPLETE}.
|
||||
*/
|
||||
public long size;
|
||||
|
||||
/**
|
||||
* The name of the temporary file containing the local copy of the attachment.
|
||||
*
|
||||
* Only valid when {@link #state} is {@link LoadingState#COMPLETE}.
|
||||
*/
|
||||
public String filename;
|
||||
|
||||
|
||||
public Attachment() {}
|
||||
|
||||
public static enum LoadingState {
|
||||
/**
|
||||
* The only thing we know about this attachment is {@link #uri}.
|
||||
*/
|
||||
URI_ONLY,
|
||||
|
||||
/**
|
||||
* The metadata of this attachment have been loaded.
|
||||
*
|
||||
* {@link #contentType}, {@link #name}, and {@link #size} should contain usable values.
|
||||
*/
|
||||
METADATA,
|
||||
|
||||
/**
|
||||
* The contents of the attachments have been copied to the temporary file {@link #filename}.
|
||||
*/
|
||||
COMPLETE,
|
||||
|
||||
/**
|
||||
* Something went wrong while trying to fetch the attachment's contents.
|
||||
*/
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
|
||||
// === Parcelable ===
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeParcelable(uri, flags);
|
||||
dest.writeSerializable(state);
|
||||
dest.writeInt(loaderId);
|
||||
dest.writeString(contentType);
|
||||
dest.writeString(name);
|
||||
dest.writeLong(size);
|
||||
dest.writeString(filename);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Attachment> CREATOR =
|
||||
new Parcelable.Creator<Attachment>() {
|
||||
@Override
|
||||
public Attachment createFromParcel(Parcel in) {
|
||||
return new Attachment(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment[] newArray(int size) {
|
||||
return new Attachment[size];
|
||||
}
|
||||
};
|
||||
|
||||
public Attachment(Parcel in) {
|
||||
uri = in.readParcelable(Uri.class.getClassLoader());
|
||||
state = (LoadingState) in.readSerializable();
|
||||
loaderId = in.readInt();
|
||||
contentType = in.readString();
|
||||
name = in.readString();
|
||||
size = in.readLong();
|
||||
filename = in.readString();
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package com.fsck.k9.mail.store;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -90,6 +91,7 @@ public class LocalStore extends Store implements Serializable {
|
||||
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
|
||||
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||
|
||||
/*
|
||||
* a String containing the columns getMessages expects to work with
|
||||
@ -3984,8 +3986,39 @@ public class LocalStore extends Store implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
public static class TempFileBody implements Body {
|
||||
private final File mFile;
|
||||
|
||||
public TempFileBody(String filename) {
|
||||
mFile = new File(filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws MessagingException {
|
||||
try {
|
||||
return new FileInputStream(mFile);
|
||||
} catch (FileNotFoundException e) {
|
||||
return new ByteArrayInputStream(EMPTY_BYTE_ARRAY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
InputStream in = getInputStream();
|
||||
try {
|
||||
Base64OutputStream base64Out = new Base64OutputStream(out);
|
||||
try {
|
||||
IOUtils.copy(in, base64Out);
|
||||
} finally {
|
||||
base64Out.close();
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocalAttachmentBody implements Body {
|
||||
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||
private Application mApplication;
|
||||
private Uri mUri;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user