mirror of
https://github.com/moparisthebest/k-9
synced 2025-01-10 05:08:18 -05:00
Merge branch 'master' into issue-162
This commit is contained in:
commit
14a0fdf27d
@ -1033,6 +1033,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
|
|||||||
<string name="save_or_discard_draft_message_dlg_title">Save draft message?</string>
|
<string name="save_or_discard_draft_message_dlg_title">Save draft message?</string>
|
||||||
<string name="save_or_discard_draft_message_instructions_fmt">Save or Discard this message?</string>
|
<string name="save_or_discard_draft_message_instructions_fmt">Save or Discard this message?</string>
|
||||||
|
|
||||||
|
<string name="confirm_discard_draft_message_title">Discard message?</string>
|
||||||
|
<string name="confirm_discard_draft_message">Are you sure you want to discard this message?</string>
|
||||||
|
|
||||||
<string name="refuse_to_save_draft_marked_encrypted_dlg_title">Refuse to save draft message.</string>
|
<string name="refuse_to_save_draft_marked_encrypted_dlg_title">Refuse to save draft message.</string>
|
||||||
<string name="refuse_to_save_draft_marked_encrypted_instructions_fmt">Refuse to save draft message marked encrypted.</string>
|
<string name="refuse_to_save_draft_marked_encrypted_instructions_fmt">Refuse to save draft message marked encrypted.</string>
|
||||||
|
|
||||||
|
@ -170,6 +170,10 @@
|
|||||||
<incoming uri="pop3://incoming.verizon.net" username="$user" />
|
<incoming uri="pop3://incoming.verizon.net" username="$user" />
|
||||||
<outgoing uri="smtp://outgoing.verizon.net" username="$user" />
|
<outgoing uri="smtp://outgoing.verizon.net" username="$user" />
|
||||||
</provider>
|
</provider>
|
||||||
|
<provider id="montclair.edu" label="MSU" domain="montclair.edu">
|
||||||
|
<incoming uri="imap+ssl+://mail.montclair.edu" username="$user" />
|
||||||
|
<outgoing uri="smtp+tls+://smtp.montclair.edu" username="$user" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
<!-- Yahoo! Mail Variants -->
|
<!-- Yahoo! Mail Variants -->
|
||||||
<provider id="yahoo" label="Yahoo" domain="yahoo.com">
|
<provider id="yahoo" label="Yahoo" domain="yahoo.com">
|
||||||
|
@ -83,6 +83,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
|
|||||||
private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1;
|
private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1;
|
||||||
private static final int DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED = 2;
|
private static final int DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED = 2;
|
||||||
private static final int DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY = 3;
|
private static final int DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY = 3;
|
||||||
|
private static final int DIALOG_CONFIRM_DISCARD_ON_BACK = 4;
|
||||||
|
|
||||||
private static final long INVALID_DRAFT_ID = MessagingController.INVALID_MESSAGE_ID;
|
private static final long INVALID_DRAFT_ID = MessagingController.INVALID_MESSAGE_ID;
|
||||||
|
|
||||||
@ -2051,13 +2052,21 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (mEncryptCheckbox.isChecked()) {
|
if (mDraftNeedsSaving) {
|
||||||
showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
|
if (mEncryptCheckbox.isChecked()) {
|
||||||
} else if (!mDraftNeedsSaving || isDraftsFolderDisabled()) {
|
showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
|
||||||
Toast.makeText(MessageCompose.this, getString(R.string.message_discarded_toast), Toast.LENGTH_LONG).show();
|
} else if (isDraftsFolderDisabled()) {
|
||||||
super.onBackPressed();
|
showDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
|
||||||
|
} else {
|
||||||
|
showDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
|
// Check if editing an existing draft.
|
||||||
|
if (mDraftId == INVALID_DRAFT_ID) {
|
||||||
|
onDiscard();
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2118,6 +2127,27 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
|
case DIALOG_CONFIRM_DISCARD_ON_BACK:
|
||||||
|
return new AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.confirm_discard_draft_message_title)
|
||||||
|
.setMessage(R.string.confirm_discard_draft_message)
|
||||||
|
.setPositiveButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int whichButton) {
|
||||||
|
dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int whichButton) {
|
||||||
|
dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
|
||||||
|
Toast.makeText(MessageCompose.this,
|
||||||
|
getString(R.string.message_discarded_toast),
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
onDiscard();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.create();
|
||||||
}
|
}
|
||||||
return super.onCreateDialog(id);
|
return super.onCreateDialog(id);
|
||||||
}
|
}
|
||||||
@ -2988,9 +3018,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
|
|||||||
}
|
}
|
||||||
|
|
||||||
MessagingController.getInstance(getApplication()).sendMessage(mAccount, message, null);
|
MessagingController.getInstance(getApplication()).sendMessage(mAccount, message, null);
|
||||||
if (mDraftId != INVALID_DRAFT_ID) {
|
long draftId = mDraftId;
|
||||||
MessagingController.getInstance(getApplication()).deleteDraft(mAccount, mDraftId);
|
if (draftId != INVALID_DRAFT_ID) {
|
||||||
mDraftId = INVALID_DRAFT_ID;
|
mDraftId = INVALID_DRAFT_ID;
|
||||||
|
MessagingController.getInstance(getApplication()).deleteDraft(mAccount, draftId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -20,6 +20,7 @@ import com.fsck.k9.crypto.PgpData;
|
|||||||
import com.fsck.k9.helper.FileBrowserHelper;
|
import com.fsck.k9.helper.FileBrowserHelper;
|
||||||
import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback;
|
import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback;
|
||||||
import com.fsck.k9.mail.*;
|
import com.fsck.k9.mail.*;
|
||||||
|
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
||||||
import com.fsck.k9.mail.store.StorageManager;
|
import com.fsck.k9.mail.store.StorageManager;
|
||||||
import com.fsck.k9.view.AttachmentView;
|
import com.fsck.k9.view.AttachmentView;
|
||||||
import com.fsck.k9.view.ToggleScrollView;
|
import com.fsck.k9.view.ToggleScrollView;
|
||||||
@ -712,10 +713,15 @@ public class MessageView extends K9Activity implements OnClickListener {
|
|||||||
|
|
||||||
private void onFlag() {
|
private void onFlag() {
|
||||||
if (mMessage != null) {
|
if (mMessage != null) {
|
||||||
mController.setFlag(mAccount,
|
boolean newState = !mMessage.isSet(Flag.FLAGGED);
|
||||||
mMessage.getFolder().getName(), new String[] {mMessage.getUid()}, Flag.FLAGGED, !mMessage.isSet(Flag.FLAGGED));
|
mController.setFlag(mAccount, mMessage.getFolder().getName(),
|
||||||
|
new String[] {mMessage.getUid()}, Flag.FLAGGED, newState);
|
||||||
try {
|
try {
|
||||||
mMessage.setFlag(Flag.FLAGGED, !mMessage.isSet(Flag.FLAGGED));
|
// FIXME: This is a hack to change the flagged state of our message object. We
|
||||||
|
// can't call Message.setFlag() because that would "adjust" the flagged count
|
||||||
|
// another time (first time by MessagingController.setFlag(...)).
|
||||||
|
((LocalMessage)mMessage).setFlagInternal(Flag.FLAGGED, newState);
|
||||||
|
|
||||||
mMessageView.setHeaders(mMessage, mAccount);
|
mMessageView.setHeaders(mMessage, mAccount);
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
Log.e(K9.LOG_TAG, "Could not set flag on local message", me);
|
Log.e(K9.LOG_TAG, "Could not set flag on local message", me);
|
||||||
@ -848,9 +854,13 @@ public class MessageView extends K9Activity implements OnClickListener {
|
|||||||
|
|
||||||
private void onMarkAsUnread() {
|
private void onMarkAsUnread() {
|
||||||
if (mMessage != null) {
|
if (mMessage != null) {
|
||||||
// (Issue 3319) mController.setFlag(mAccount, mMessageReference.folderName, new String[] { mMessage.getUid() }, Flag.SEEN, false);
|
mController.setFlag(mAccount, mMessage.getFolder().getName(),
|
||||||
|
new String[] { mMessage.getUid() }, Flag.SEEN, false);
|
||||||
try {
|
try {
|
||||||
mMessage.setFlag(Flag.SEEN, false);
|
// FIXME: This is a hack to mark our message object as unread. We can't call
|
||||||
|
// Message.setFlag() because that would "adjust" the unread count twice.
|
||||||
|
((LocalMessage)mMessage).setFlagInternal(Flag.SEEN, false);
|
||||||
|
|
||||||
mMessageView.setHeaders(mMessage, mAccount);
|
mMessageView.setHeaders(mMessage, mAccount);
|
||||||
String subject = mMessage.getSubject();
|
String subject = mMessage.getSubject();
|
||||||
setTitle(subject);
|
setTitle(subject);
|
||||||
|
@ -3371,9 +3371,11 @@ public class MessagingController implements Runnable {
|
|||||||
localFolder = localStore.getFolder(account.getDraftsFolderName());
|
localFolder = localStore.getFolder(account.getDraftsFolderName());
|
||||||
localFolder.open(OpenMode.READ_WRITE);
|
localFolder.open(OpenMode.READ_WRITE);
|
||||||
String uid = localFolder.getMessageUidById(id);
|
String uid = localFolder.getMessageUidById(id);
|
||||||
Message message = localFolder.getMessage(uid);
|
if (uid != null) {
|
||||||
if (message != null) {
|
Message message = localFolder.getMessage(uid);
|
||||||
deleteMessages(new Message[] { message }, null);
|
if (message != null) {
|
||||||
|
deleteMessages(new Message[] { message }, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
addErrorMessage(account, null, me);
|
addErrorMessage(account, null, me);
|
||||||
@ -3528,12 +3530,13 @@ public class MessagingController implements Runnable {
|
|||||||
putBackground("emptyTrash", listener, new Runnable() {
|
putBackground("emptyTrash", listener, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Folder localFolder = null;
|
LocalFolder localFolder = null;
|
||||||
try {
|
try {
|
||||||
Store localStore = account.getLocalStore();
|
Store localStore = account.getLocalStore();
|
||||||
localFolder = localStore.getFolder(account.getTrashFolderName());
|
localFolder = (LocalFolder) localStore.getFolder(account.getTrashFolderName());
|
||||||
localFolder.open(OpenMode.READ_WRITE);
|
localFolder.open(OpenMode.READ_WRITE);
|
||||||
localFolder.setFlags(new Flag[] { Flag.DELETED }, true);
|
localFolder.setFlags(new Flag[] { Flag.DELETED }, true);
|
||||||
|
localFolder.resetUnreadAndFlaggedCounts();
|
||||||
|
|
||||||
for (MessagingListener l : getListeners()) {
|
for (MessagingListener l : getListeners()) {
|
||||||
l.emptyTrashCompleted(account);
|
l.emptyTrashCompleted(account);
|
||||||
|
@ -2725,7 +2725,7 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly);
|
|||||||
setVisibleLimit(mAccount.getDisplayCount());
|
setVisibleLimit(mAccount.getDisplayCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetUnreadAndFlaggedCounts() {
|
public void resetUnreadAndFlaggedCounts() {
|
||||||
try {
|
try {
|
||||||
int newUnread = 0;
|
int newUnread = 0;
|
||||||
int newFlagged = 0;
|
int newFlagged = 0;
|
||||||
@ -2799,22 +2799,34 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly);
|
|||||||
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
|
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
|
||||||
Cursor attachmentsCursor = null;
|
Cursor attachmentsCursor = null;
|
||||||
try {
|
try {
|
||||||
attachmentsCursor = db.query("attachments", new String[]
|
String accountUuid = mAccount.getUuid();
|
||||||
{ "id" }, "message_id = ?", new String[]
|
Context context = mApplication;
|
||||||
{ Long.toString(messageId) }, null, null, null);
|
|
||||||
|
// Get attachment IDs
|
||||||
|
String[] whereArgs = new String[] { Long.toString(messageId) };
|
||||||
|
attachmentsCursor = db.query("attachments", new String[] { "id" },
|
||||||
|
"message_id = ?", whereArgs, null, null, null);
|
||||||
|
|
||||||
final File attachmentDirectory = StorageManager.getInstance(mApplication)
|
final File attachmentDirectory = StorageManager.getInstance(mApplication)
|
||||||
.getAttachmentDirectory(uUid, database.getStorageProviderId());
|
.getAttachmentDirectory(uUid, database.getStorageProviderId());
|
||||||
|
|
||||||
while (attachmentsCursor.moveToNext()) {
|
while (attachmentsCursor.moveToNext()) {
|
||||||
long attachmentId = attachmentsCursor.getLong(0);
|
String attachmentId = Long.toString(attachmentsCursor.getLong(0));
|
||||||
try {
|
try {
|
||||||
File file = new File(attachmentDirectory, Long.toString(attachmentId));
|
// Delete stored attachment
|
||||||
|
File file = new File(attachmentDirectory, attachmentId);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
file.delete();
|
file.delete();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
// Delete thumbnail file
|
||||||
|
AttachmentProvider.deleteThumbnail(context, accountUuid,
|
||||||
|
attachmentId);
|
||||||
|
} catch (Exception e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete attachment metadata from the database
|
||||||
|
db.delete("attachments", "message_id = ?", whereArgs);
|
||||||
} finally {
|
} finally {
|
||||||
Utility.closeQuietly(attachmentsCursor);
|
Utility.closeQuietly(attachmentsCursor);
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,12 @@ import java.io.*;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple ContentProvider that allows file access to Email's attachments.<br/>
|
* A simple ContentProvider that allows file access to attachments.
|
||||||
* Warning! We make heavy assumptions about the Uris used by the {@link LocalStore} for an {@link Account} here.
|
*
|
||||||
|
* <p>
|
||||||
|
* Warning! We make heavy assumptions about the Uris used by the {@link LocalStore} for an
|
||||||
|
* {@link Account} here.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class AttachmentProvider extends ContentProvider {
|
public class AttachmentProvider extends ContentProvider {
|
||||||
public static final Uri CONTENT_URI = Uri.parse("content://com.fsck.k9.attachmentprovider");
|
public static final Uri CONTENT_URI = Uri.parse("content://com.fsck.k9.attachmentprovider");
|
||||||
@ -33,6 +37,11 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
private static final String FORMAT_VIEW = "VIEW";
|
private static final String FORMAT_VIEW = "VIEW";
|
||||||
private static final String FORMAT_THUMBNAIL = "THUMBNAIL";
|
private static final String FORMAT_THUMBNAIL = "THUMBNAIL";
|
||||||
|
|
||||||
|
private static final String[] DEFAULT_PROJECTION = new String[] {
|
||||||
|
AttachmentProviderColumns._ID,
|
||||||
|
AttachmentProviderColumns.DATA,
|
||||||
|
};
|
||||||
|
|
||||||
public static class AttachmentProviderColumns {
|
public static class AttachmentProviderColumns {
|
||||||
public static final String _ID = "_id";
|
public static final String _ID = "_id";
|
||||||
public static final String DATA = "_data";
|
public static final String DATA = "_data";
|
||||||
@ -40,6 +49,7 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
public static final String SIZE = "_size";
|
public static final String SIZE = "_size";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Uri getAttachmentUri(Account account, long id) {
|
public static Uri getAttachmentUri(Account account, long id) {
|
||||||
return getAttachmentUri(account.getUuid(), id, true);
|
return getAttachmentUri(account.getUuid(), id, true);
|
||||||
}
|
}
|
||||||
@ -66,6 +76,47 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void clear(Context context) {
|
||||||
|
/*
|
||||||
|
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
|
||||||
|
* on startup we'll clean up any .tmp files from the last run.
|
||||||
|
*/
|
||||||
|
File[] files = context.getCacheDir().listFiles();
|
||||||
|
for (File file : files) {
|
||||||
|
try {
|
||||||
|
if (K9.DEBUG) {
|
||||||
|
Log.d(K9.LOG_TAG, "Deleting file " + file.getCanonicalPath());
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) { /* No need to log failure to log */ }
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the thumbnail of an attachment.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* The application context.
|
||||||
|
* @param accountUuid
|
||||||
|
* The UUID of the account the attachment belongs to.
|
||||||
|
* @param attachmentId
|
||||||
|
* The ID of the attachment the thumbnail was created for.
|
||||||
|
*/
|
||||||
|
public static void deleteThumbnail(Context context, String accountUuid, String attachmentId) {
|
||||||
|
File file = getThumbnailFile(context, accountUuid, attachmentId);
|
||||||
|
if (file.exists()) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getThumbnailFile(Context context, String accountUuid,
|
||||||
|
String attachmentId) {
|
||||||
|
String filename = "thmb_" + accountUuid + "_" + attachmentId + ".tmp";
|
||||||
|
File dir = context.getCacheDir();
|
||||||
|
return new File(dir, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
/*
|
/*
|
||||||
@ -89,21 +140,6 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clear(Context lContext) {
|
|
||||||
/*
|
|
||||||
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
|
|
||||||
* on startup we'll clean up any .tmp files from the last run.
|
|
||||||
*/
|
|
||||||
File[] files = lContext.getCacheDir().listFiles();
|
|
||||||
for (File file : files) {
|
|
||||||
try {
|
|
||||||
if (K9.DEBUG)
|
|
||||||
Log.d(K9.LOG_TAG, "Deleting file " + file.getCanonicalPath());
|
|
||||||
} catch (IOException ioe) {} // No need to log failure to log
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType(Uri uri) {
|
public String getType(Uri uri) {
|
||||||
List<String> segments = uri.getPathSegments();
|
List<String> segments = uri.getPathSegments();
|
||||||
@ -114,67 +150,24 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
return getType(dbName, id, format);
|
return getType(dbName, id, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getType(String dbName, String id, String format) {
|
|
||||||
if (FORMAT_THUMBNAIL.equals(format)) {
|
|
||||||
return "image/png";
|
|
||||||
} else {
|
|
||||||
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
|
|
||||||
|
|
||||||
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
|
|
||||||
if (FORMAT_VIEW.equals(format)) {
|
|
||||||
return MimeUtility.getMimeTypeForViewing(attachmentInfo.type, attachmentInfo.name);
|
|
||||||
} else {
|
|
||||||
// When accessing the "raw" message we deliver the original MIME type.
|
|
||||||
return attachmentInfo.type;
|
|
||||||
}
|
|
||||||
} catch (MessagingException e) {
|
|
||||||
Log.e(K9.LOG_TAG, "Unable to retrieve LocalStore for " + account, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getFile(String dbName, String id)
|
|
||||||
throws FileNotFoundException {
|
|
||||||
try {
|
|
||||||
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
|
||||||
final File attachmentsDir;
|
|
||||||
attachmentsDir = StorageManager.getInstance(K9.app).getAttachmentDirectory(dbName,
|
|
||||||
account.getLocalStorageProviderId());
|
|
||||||
final File file = new File(attachmentsDir, id);
|
|
||||||
if (!file.exists()) {
|
|
||||||
throw new FileNotFoundException(file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(K9.LOG_TAG, null, e);
|
|
||||||
throw new FileNotFoundException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||||
|
File file;
|
||||||
|
|
||||||
List<String> segments = uri.getPathSegments();
|
List<String> segments = uri.getPathSegments();
|
||||||
String dbName = segments.get(0); // "/sdcard/..." is URL-encoded and makes up only 1 segment
|
String accountUuid = segments.get(0);
|
||||||
String id = segments.get(1);
|
String attachmentId = segments.get(1);
|
||||||
String format = segments.get(2);
|
String format = segments.get(2);
|
||||||
|
|
||||||
if (FORMAT_THUMBNAIL.equals(format)) {
|
if (FORMAT_THUMBNAIL.equals(format)) {
|
||||||
int width = Integer.parseInt(segments.get(3));
|
int width = Integer.parseInt(segments.get(3));
|
||||||
int height = Integer.parseInt(segments.get(4));
|
int height = Integer.parseInt(segments.get(4));
|
||||||
String filename = "thmb_" + dbName + "_" + id + ".tmp";
|
|
||||||
int index = dbName.lastIndexOf('/');
|
file = getThumbnailFile(getContext(), accountUuid, attachmentId);
|
||||||
if (index >= 0) {
|
|
||||||
filename = /*dbName.substring(0, index + 1) + */"thmb_" + dbName.substring(index + 1) + "_" + id + ".tmp";
|
|
||||||
}
|
|
||||||
File dir = getContext().getCacheDir();
|
|
||||||
File file = new File(dir, filename);
|
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
String type = getType(dbName, id, FORMAT_VIEW);
|
String type = getType(accountUuid, attachmentId, FORMAT_VIEW);
|
||||||
try {
|
try {
|
||||||
FileInputStream in = new FileInputStream(getFile(dbName, id));
|
FileInputStream in = new FileInputStream(getFile(accountUuid, attachmentId));
|
||||||
try {
|
try {
|
||||||
Bitmap thumbnail = createThumbnail(type, in);
|
Bitmap thumbnail = createThumbnail(type, in);
|
||||||
if (thumbnail != null) {
|
if (thumbnail != null) {
|
||||||
@ -187,40 +180,24 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
try { in.close(); } catch (Throwable ignore) {}
|
try { in.close(); } catch (Throwable ignore) { /* ignore */ }
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
||||||
} else {
|
} else {
|
||||||
return ParcelFileDescriptor.open(
|
file = getFile(accountUuid, attachmentId);
|
||||||
getFile(dbName, id),
|
|
||||||
ParcelFileDescriptor.MODE_READ_ONLY);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
public int delete(Uri uri, String arg1, String[] arg2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri insert(Uri uri, ContentValues values) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||||
String sortOrder) {
|
String sortOrder) {
|
||||||
if (projection == null) {
|
|
||||||
projection =
|
String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection;
|
||||||
new String[] {
|
|
||||||
AttachmentProviderColumns._ID,
|
|
||||||
AttachmentProviderColumns.DATA,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> segments = uri.getPathSegments();
|
List<String> segments = uri.getPathSegments();
|
||||||
String dbName = segments.get(0);
|
String dbName = segments.get(0);
|
||||||
@ -232,7 +209,6 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
dbName = dbName.substring(0, dbName.length() - 3);
|
dbName = dbName.substring(0, dbName.length() - 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
//String format = segments.get(2);
|
|
||||||
final AttachmentInfo attachmentInfo;
|
final AttachmentInfo attachmentInfo;
|
||||||
try {
|
try {
|
||||||
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
||||||
@ -242,10 +218,10 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixCursor ret = new MatrixCursor(projection);
|
MatrixCursor ret = new MatrixCursor(columnNames);
|
||||||
Object[] values = new Object[projection.length];
|
Object[] values = new Object[columnNames.length];
|
||||||
for (int i = 0, count = projection.length; i < count; i++) {
|
for (int i = 0, count = columnNames.length; i < count; i++) {
|
||||||
String column = projection[i];
|
String column = columnNames[i];
|
||||||
if (AttachmentProviderColumns._ID.equals(column)) {
|
if (AttachmentProviderColumns._ID.equals(column)) {
|
||||||
values[i] = id;
|
values[i] = id;
|
||||||
} else if (AttachmentProviderColumns.DATA.equals(column)) {
|
} else if (AttachmentProviderColumns.DATA.equals(column)) {
|
||||||
@ -265,6 +241,56 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(Uri uri, String arg1, String[] arg2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri insert(Uri uri, ContentValues values) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getType(String dbName, String id, String format) {
|
||||||
|
String type;
|
||||||
|
if (FORMAT_THUMBNAIL.equals(format)) {
|
||||||
|
type = "image/png";
|
||||||
|
} else {
|
||||||
|
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
|
||||||
|
|
||||||
|
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
|
||||||
|
if (FORMAT_VIEW.equals(format)) {
|
||||||
|
type = MimeUtility.getMimeTypeForViewing(attachmentInfo.type, attachmentInfo.name);
|
||||||
|
} else {
|
||||||
|
// When accessing the "raw" message we deliver the original MIME type.
|
||||||
|
type = attachmentInfo.type;
|
||||||
|
}
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
Log.e(K9.LOG_TAG, "Unable to retrieve LocalStore for " + account, e);
|
||||||
|
type = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getFile(String dbName, String id) throws FileNotFoundException {
|
||||||
|
Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
||||||
|
|
||||||
|
File attachmentsDir = StorageManager.getInstance(K9.app).getAttachmentDirectory(dbName,
|
||||||
|
account.getLocalStorageProviderId());
|
||||||
|
|
||||||
|
File file = new File(attachmentsDir, id);
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw new FileNotFoundException(file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
private Bitmap createThumbnail(String type, InputStream data) {
|
private Bitmap createThumbnail(String type, InputStream data) {
|
||||||
if (MimeUtility.mimeTypeMatches(type, "image/*")) {
|
if (MimeUtility.mimeTypeMatches(type, "image/*")) {
|
||||||
return createImageThumbnail(data);
|
return createImageThumbnail(data);
|
||||||
|
@ -39,6 +39,26 @@ import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
|
|||||||
import com.fsck.k9.provider.AttachmentProvider;
|
import com.fsck.k9.provider.AttachmentProvider;
|
||||||
|
|
||||||
public class AttachmentView extends FrameLayout {
|
public class AttachmentView extends FrameLayout {
|
||||||
|
/**
|
||||||
|
* Regular expression that represents characters we won't allow in file names.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Allowed are:
|
||||||
|
* <ul>
|
||||||
|
* <li>word characters (letters, digits, and underscores): {@code \w}</li>
|
||||||
|
* <li>spaces: {@code " "}</li>
|
||||||
|
* <li>special characters: {@code !}, {@code #}, {@code $}, {@code %}, {@code &}, {@code '},
|
||||||
|
* {@code (}, {@code )}, {@code -}, {@code @}, {@code ^}, {@code `}, <code>{</code>,
|
||||||
|
* <code>}</code>, {@code ~}, {@code .}, {@code ,}</li>
|
||||||
|
* </ul></p>
|
||||||
|
*/
|
||||||
|
private static final String INVALID_CHARACTERS = "[^\\w !#$%&'()\\-@\\^`{}~.,]+";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid characters in a file name are replaced by this character.
|
||||||
|
*/
|
||||||
|
private static final String REPLACEMENT_CHARACTER = "_";
|
||||||
|
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
public Button viewButton;
|
public Button viewButton;
|
||||||
@ -196,7 +216,8 @@ public class AttachmentView extends FrameLayout {
|
|||||||
*/
|
*/
|
||||||
public void writeFile(File directory) {
|
public void writeFile(File directory) {
|
||||||
try {
|
try {
|
||||||
File file = Utility.createUniqueFile(directory, name);
|
String filename = sanitizeFilename(name);
|
||||||
|
File file = Utility.createUniqueFile(directory, filename);
|
||||||
Uri uri = AttachmentProvider.getAttachmentUri(mAccount, part.getAttachmentId());
|
Uri uri = AttachmentProvider.getAttachmentUri(mAccount, part.getAttachmentId());
|
||||||
InputStream in = mContext.getContentResolver().openInputStream(uri);
|
InputStream in = mContext.getContentResolver().openInputStream(uri);
|
||||||
OutputStream out = new FileOutputStream(file);
|
OutputStream out = new FileOutputStream(file);
|
||||||
@ -207,9 +228,25 @@ public class AttachmentView extends FrameLayout {
|
|||||||
attachmentSaved(file.toString());
|
attachmentSaved(file.toString());
|
||||||
new MediaScannerNotifier(mContext, file);
|
new MediaScannerNotifier(mContext, file);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
|
if (K9.DEBUG) {
|
||||||
|
Log.e(K9.LOG_TAG, "Error saving attachment", ioe);
|
||||||
|
}
|
||||||
attachmentNotSaved();
|
attachmentNotSaved();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace characters we don't allow in file names with a replacement character.
|
||||||
|
*
|
||||||
|
* @param filename
|
||||||
|
* The original file name.
|
||||||
|
*
|
||||||
|
* @return The sanitized file name containing only allowed characters.
|
||||||
|
*/
|
||||||
|
private String sanitizeFilename(String filename) {
|
||||||
|
return filename.replaceAll(INVALID_CHARACTERS, REPLACEMENT_CHARACTER);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* saves the file to the defaultpath setting in the config, or if the config
|
* saves the file to the defaultpath setting in the config, or if the config
|
||||||
* is not set => to the Environment
|
* is not set => to the Environment
|
||||||
|
Loading…
Reference in New Issue
Block a user