Repository: thundernest/k-9
Issue: forward a mail with attachment failed 

### Actual  behaviour

there's two cases:
1.get a mail with inline images
2.get a mail with attachment

Two mail has been down fully in my k9 client.  
When forward,both toast:"Some attachments cannot be forwarded because they have not been downloaded.".
 If ignore this error to forward,the inline images or attachment can not be send to others.
### Expected behaviour

The two case should forward correctly
### Steps to reproduce

1.forward a mail to somebody with inline images or with attachment.
2.both toast "Some attachments cannot be forwarded because they have not been downloaded.".
3.click forward btn ok
4.login in somebody's mail to get these two mails
5.images inline can not show and also without attachements
### Environment

K-9 Mail version:
check out from github  on 28.9.2015

Android version:
4.3
Account type (IMAP, POP3, WebDAV/Exchange):
POP3


Changed files:
k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java
k9mail/src/main/java/com/fsck/k9/activity/compose/AttachmentPresenter.java
k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java
k9mail/src/test/java/com/fsck/k9/mailstore/AttachmentResolverTest.java
k9mail/src/main/java/com/fsck/k9/mailstore/AttachmentViewInfo.java
k9mail/src/test/java/com/fsck/k9/message/extractors/AttachmentInfoExtractorTest.java
k9mail/src/main/java/com/fsck/k9/message/extractors/AttachmentInfoExtractor.java

diff --git a/k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java b/k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java
index 563d9cc96..c40ce1622 100644
--- a/k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java
+++ b/k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java
@@ -1276,7 +1276,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
         }
 
         if (!mSourceMessageProcessed) {
-            attachmentPresenter.loadAttachments(message, 0);
+            attachmentPresenter.loadNonInlineAttachments(messageViewInfo);
         }
 
         // Decode the identity header when loading a draft.
diff --git a/k9mail/src/main/java/com/fsck/k9/activity/compose/AttachmentPresenter.java b/k9mail/src/main/java/com/fsck/k9/activity/compose/AttachmentPresenter.java
index 6535391dd..b5a569b28 100644
--- a/k9mail/src/main/java/com/fsck/k9/activity/compose/AttachmentPresenter.java
+++ b/k9mail/src/main/java/com/fsck/k9/activity/compose/AttachmentPresenter.java
@@ -21,15 +21,8 @@ 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.activity.misc.Attachment.LoadingState;
-import com.fsck.k9.mail.Flag;
-import com.fsck.k9.mail.MessagingException;
-import com.fsck.k9.mail.Multipart;
-import com.fsck.k9.mail.Part;
-import com.fsck.k9.mail.internet.MimeUtility;
 import com.fsck.k9.mailstore.AttachmentViewInfo;
-import com.fsck.k9.mailstore.LocalBodyPart;
 import com.fsck.k9.mailstore.MessageViewInfo;
-import com.fsck.k9.provider.AttachmentProvider;
 
 
 public class AttachmentPresenter {
@@ -158,16 +151,27 @@ public class AttachmentPresenter {
         addAttachmentAndStartLoader(attachment);
     }
 
-    public void processMessageToForward(MessageViewInfo messageViewInfo) {
-        if (messageViewInfo.message.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
-            attachmentMvpView.showMissingAttachmentsPartialMessageWarning();
-            return;
-        }
+    public boolean loadNonInlineAttachments(MessageViewInfo messageViewInfo) {
+        boolean allPartsAvailable = true;
 
         for (AttachmentViewInfo attachmentViewInfo : messageViewInfo.attachments) {
-            if (!attachmentViewInfo.inlineAttachment) {
-                addAttachment(attachmentViewInfo);
+            if (attachmentViewInfo.inlineAttachment) {
+                continue;
+            }
+            if (!attachmentViewInfo.isContentAvailable) {
+                allPartsAvailable = false;
+                continue;
             }
+            addAttachment(attachmentViewInfo);
+        }
+
+        return allPartsAvailable;
+    }
+
+    public void processMessageToForward(MessageViewInfo messageViewInfo) {
+        boolean isMissingParts = !loadNonInlineAttachments(messageViewInfo);
+        if (isMissingParts) {
+            attachmentMvpView.showMissingAttachmentsPartialMessageWarning();
         }
     }
 
@@ -300,45 +304,6 @@ public class AttachmentPresenter {
         }
     }
 
-    /**
-     * Add all attachments of an existing message as if they were added by hand.
-     *
-     * @param part
-     *         The message part to check for being an attachment. This method will recurse if it's
-     *         a multipart part.
-     * @param depth
-     *         The recursion depth. Currently unused.
-     *
-     * @return {@code true} if all attachments were able to be attached, {@code false} otherwise.
-     */
-    public boolean loadAttachments(Part part, int depth) {
-        if (part.getBody() instanceof Multipart) {
-            Multipart mp = (Multipart) part.getBody();
-            boolean ret = true;
-            for (int i = 0, count = mp.getCount(); i < count; i++) {
-                if (!loadAttachments(mp.getBodyPart(i), depth + 1)) {
-                    ret = false;
-                }
-            }
-            return ret;
-        }
-
-        String contentType = part.getContentType();
-        String name = MimeUtility.getHeaderParameter(contentType, "name");
-        if (name != null) {
-            if (part instanceof LocalBodyPart) {
-                LocalBodyPart localBodyPart = (LocalBodyPart) part;
-                String accountUuid = localBodyPart.getAccountUuid();
-                long attachmentId = localBodyPart.getId();
-                Uri uri = AttachmentProvider.getAttachmentUri(accountUuid, attachmentId);
-                addAttachment(uri);
-                return true;
-            }
-            return false;
-        }
-        return true;
-    }
-
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     void addAttachmentsFromResultIntent(Intent data) {
         // TODO draftNeedsSaving = true
diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/AttachmentViewInfo.java b/k9mail/src/main/java/com/fsck/k9/mailstore/AttachmentViewInfo.java
index cd1d0ef49..585f549f4 100644
--- a/k9mail/src/main/java/com/fsck/k9/mailstore/AttachmentViewInfo.java
+++ b/k9mail/src/main/java/com/fsck/k9/mailstore/AttachmentViewInfo.java
@@ -23,14 +23,16 @@ public class AttachmentViewInfo {
     public final Uri uri;
     public final boolean inlineAttachment;
     public final Part part;
+    public final boolean isContentAvailable;
 
     public AttachmentViewInfo(String mimeType, String displayName, long size, Uri uri, boolean inlineAttachment,
-            Part part) {
+            Part part, boolean isContentAvailable) {
         this.mimeType = mimeType;
         this.displayName = displayName;
         this.size = size;
         this.uri = uri;
         this.inlineAttachment = inlineAttachment;
         this.part = part;
+        this.isContentAvailable = isContentAvailable;
     }
 }
diff --git a/k9mail/src/main/java/com/fsck/k9/message/extractors/AttachmentInfoExtractor.java b/k9mail/src/main/java/com/fsck/k9/message/extractors/AttachmentInfoExtractor.java
index 9c024b1b4..05bb270e6 100644
--- a/k9mail/src/main/java/com/fsck/k9/message/extractors/AttachmentInfoExtractor.java
+++ b/k9mail/src/main/java/com/fsck/k9/message/extractors/AttachmentInfoExtractor.java
@@ -61,17 +61,21 @@ public class AttachmentInfoExtractor {
     public AttachmentViewInfo extractAttachmentInfo(Part part) throws MessagingException {
         Uri uri;
         long size;
+        boolean isContentAvailable;
+
         if (part instanceof LocalPart) {
             LocalPart localPart = (LocalPart) part;
             String accountUuid = localPart.getAccountUuid();
             long messagePartId = localPart.getId();
             size = localPart.getSize();
+            isContentAvailable = part.getBody() != null;
             uri = AttachmentProvider.getAttachmentUri(accountUuid, messagePartId);
         } else if (part instanceof LocalMessage) {
             LocalMessage localMessage = (LocalMessage) part;
             String accountUuid = localMessage.getAccount().getUuid();
             long messagePartId = localMessage.getMessagePartId();
             size = localMessage.getSize();
+            isContentAvailable = part.getBody() != null;
             uri = AttachmentProvider.getAttachmentUri(accountUuid, messagePartId);
         } else {
             Body body = part.getBody();
@@ -79,13 +83,13 @@ public class AttachmentInfoExtractor {
                 DeferredFileBody decryptedTempFileBody = (DeferredFileBody) body;
                 size = decryptedTempFileBody.getSize();
                 uri = getDecryptedFileProviderUri(decryptedTempFileBody, part.getMimeType());
-                return extractAttachmentInfo(part, uri, size);
+                isContentAvailable = true;
             } else {
                 throw new IllegalArgumentException("Unsupported part type provided");
             }
         }
 
-        return extractAttachmentInfo(part, uri, size);
+        return extractAttachmentInfo(part, uri, size, isContentAvailable);
     }
 
     @Nullable
@@ -104,11 +108,13 @@ public class AttachmentInfoExtractor {
     }
 
     public AttachmentViewInfo extractAttachmentInfoForDatabase(Part part) throws MessagingException {
-        return extractAttachmentInfo(part, Uri.EMPTY, AttachmentViewInfo.UNKNOWN_SIZE);
+        boolean isContentAvailable = part.getBody() != null;
+        return extractAttachmentInfo(part, Uri.EMPTY, AttachmentViewInfo.UNKNOWN_SIZE, isContentAvailable);
     }
 
     @WorkerThread
-    private AttachmentViewInfo extractAttachmentInfo(Part part, Uri uri, long size) throws MessagingException {
+    private AttachmentViewInfo extractAttachmentInfo(Part part, Uri uri, long size, boolean isContentAvailable)
+            throws MessagingException {
         boolean inlineAttachment = false;
 
         String mimeType = part.getMimeType();
@@ -139,7 +145,7 @@ public class AttachmentInfoExtractor {
 
         long attachmentSize = extractAttachmentSize(contentDisposition, size);
 
-        return new AttachmentViewInfo(mimeType, name, attachmentSize, uri, inlineAttachment, part);
+        return new AttachmentViewInfo(mimeType, name, attachmentSize, uri, inlineAttachment, part, isContentAvailable);
     }
 
     @WorkerThread
diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java
index a9ae28bb2..38d22695d 100644
--- a/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java
+++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java
@@ -55,7 +55,7 @@ public class AttachmentController {
     }
 
     public void viewAttachment() {
-        if (needsDownloading()) {
+        if (!attachment.isContentAvailable) {
             downloadAndViewAttachment((LocalPart) attachment.part);
         } else {
             viewLocalAttachment();
@@ -70,18 +70,6 @@ public class AttachmentController {
         saveAttachmentTo(new File(directory));
     }
 
-    private boolean needsDownloading() {
-        return isPartMissing() && isLocalPart();
-    }
-
-    private boolean isPartMissing() {
-        return attachment.part.getBody() == null;
-    }
-
-    private boolean isLocalPart() {
-        return attachment.part instanceof LocalPart;
-    }
-
     private void downloadAndViewAttachment(LocalPart localPart) {
         downloadAttachment(localPart, new Runnable() {
             @Override
@@ -133,7 +121,7 @@ public class AttachmentController {
             return;
         }
 
-        if (needsDownloading()) {
+        if (!attachment.isContentAvailable) {
             downloadAndSaveAttachmentTo((LocalPart) attachment.part, directory);
         } else {
             saveLocalAttachmentTo(directory);
diff --git a/k9mail/src/test/java/com/fsck/k9/mailstore/AttachmentResolverTest.java b/k9mail/src/test/java/com/fsck/k9/mailstore/AttachmentResolverTest.java
index da792d333..7dd1d2b12 100644
--- a/k9mail/src/test/java/com/fsck/k9/mailstore/AttachmentResolverTest.java
+++ b/k9mail/src/test/java/com/fsck/k9/mailstore/AttachmentResolverTest.java
@@ -86,14 +86,15 @@ public class AttachmentResolverTest {
         subPart1.setHeader(MimeHeader.HEADER_CONTENT_ID, "cid-1");
         subPart2.setHeader(MimeHeader.HEADER_CONTENT_ID, "cid-2");
 
-        when(attachmentInfoExtractor.extractAttachmentInfo(subPart1)).thenReturn(
-                new AttachmentViewInfo(null, null, AttachmentViewInfo.UNKNOWN_SIZE, ATTACHMENT_TEST_URI_1, false, subPart1));
-        when(attachmentInfoExtractor.extractAttachmentInfo(subPart2)).thenReturn(
-                new AttachmentViewInfo(null, null, AttachmentViewInfo.UNKNOWN_SIZE, ATTACHMENT_TEST_URI_2, false, subPart2));
+        when(attachmentInfoExtractor.extractAttachmentInfo(subPart1)).thenReturn(new AttachmentViewInfo(
+                        null, null, AttachmentViewInfo.UNKNOWN_SIZE, ATTACHMENT_TEST_URI_1, false, subPart1, true));
+        when(attachmentInfoExtractor.extractAttachmentInfo(subPart2)).thenReturn(new AttachmentViewInfo(
+                        null, null, AttachmentViewInfo.UNKNOWN_SIZE, ATTACHMENT_TEST_URI_2, false, subPart2, true));
 
 
         Map<String,Uri> result = AttachmentResolver.buildCidToAttachmentUriMap(attachmentInfoExtractor, multipartPart);
 
+
         assertEquals(2, result.size());
         assertEquals(ATTACHMENT_TEST_URI_1, result.get("cid-1"));
         assertEquals(ATTACHMENT_TEST_URI_2, result.get("cid-2"));
diff --git a/k9mail/src/test/java/com/fsck/k9/message/extractors/AttachmentInfoExtractorTest.java b/k9mail/src/test/java/com/fsck/k9/message/extractors/AttachmentInfoExtractorTest.java
index ff283ae01..a5912715f 100644
--- a/k9mail/src/test/java/com/fsck/k9/message/extractors/AttachmentInfoExtractorTest.java
+++ b/k9mail/src/test/java/com/fsck/k9/message/extractors/AttachmentInfoExtractorTest.java
@@ -8,6 +8,7 @@ import android.support.annotation.Nullable;
 import com.fsck.k9.mail.Part;
 import com.fsck.k9.mail.internet.MimeBodyPart;
 import com.fsck.k9.mail.internet.MimeHeader;
+import com.fsck.k9.mail.internet.TextBody;
 import com.fsck.k9.mailstore.AttachmentViewInfo;
 import com.fsck.k9.mailstore.LocalBodyPart;
 import com.fsck.k9.mailstore.DeferredFileBody;
@@ -162,6 +163,25 @@ public class AttachmentInfoExtractorTest {
         assertEquals(AttachmentViewInfo.UNKNOWN_SIZE, attachmentViewInfo.size);
     }
 
+    @Test
+    public void extractInfoForDb__withNoBody__shouldReturnContentNotAvailable() throws Exception {
+        MimeBodyPart part = new MimeBodyPart();
+
+        AttachmentViewInfo attachmentViewInfo = attachmentInfoExtractor.extractAttachmentInfoForDatabase(part);
+
+        assertFalse(attachmentViewInfo.isContentAvailable);
+    }
+
+    @Test
+    public void extractInfoForDb__withNoBody__shouldReturnContentAvailable() throws Exception {
+        MimeBodyPart part = new MimeBodyPart();
+        part.setBody(new TextBody("data"));
+
+        AttachmentViewInfo attachmentViewInfo = attachmentInfoExtractor.extractAttachmentInfoForDatabase(part);
+
+        assertTrue(attachmentViewInfo.isContentAvailable);
+    }
+
     @Test
     public void extractInfo__withDeferredFileBody() throws Exception {
         attachmentInfoExtractor = new AttachmentInfoExtractor(context) {
@@ -187,5 +207,6 @@ public class AttachmentInfoExtractorTest {
         assertEquals(TEST_SIZE, attachmentViewInfo.size);
         assertEquals(TEST_MIME_TYPE, attachmentViewInfo.mimeType);
         assertFalse(attachmentViewInfo.inlineAttachment);
+        assertTrue(attachmentViewInfo.isContentAvailable);
     }
 }