1b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee/*
2b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * Copyright (C) 2014 The Android Open Source Project
3b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee *
4b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * Licensed under the Apache License, Version 2.0 (the "License");
5b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * you may not use this file except in compliance with the License.
6b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * You may obtain a copy of the License at
7b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee *
8b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee *      http://www.apache.org/licenses/LICENSE-2.0
9b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee *
10b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * Unless required by applicable law or agreed to in writing, software
11b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * distributed under the License is distributed on an "AS IS" BASIS,
12b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * See the License for the specific language governing permissions and
14b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * limitations under the License.
15b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee */
16b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
17b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leepackage com.android.exchange.eas;
18b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
19b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport android.content.Context;
20b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport android.os.RemoteException;
21b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
22ec620ef7363101f5ce8208d17ac496e882d995fdMartin Hibdonimport com.android.emailcommon.provider.Account;
23b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.emailcommon.provider.EmailContent;
24b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.emailcommon.provider.EmailContent.Attachment;
25b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.emailcommon.service.EmailServiceStatus;
26b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.emailcommon.service.IEmailServiceCallback;
27b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.emailcommon.utility.AttachmentUtilities;
28b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.exchange.Eas;
29b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.exchange.EasResponse;
30b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.exchange.adapter.ItemOperationsParser;
31b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.exchange.adapter.Serializer;
32b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.exchange.adapter.Tags;
33b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.exchange.service.EasService;
34b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.exchange.utility.UriCodec;
35b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport com.android.mail.utils.LogUtils;
36b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
37b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport org.apache.http.HttpEntity;
38b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
39b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport java.io.Closeable;
40b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport java.io.File;
41b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport java.io.FileInputStream;
42b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport java.io.FileNotFoundException;
43b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport java.io.FileOutputStream;
44b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport java.io.IOException;
45b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport java.io.InputStream;
46b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leeimport java.io.OutputStream;
47b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
48b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee/**
49b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * This class performs the heavy lifting of loading attachments from the Exchange server to the
50b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * device in a local file.
51b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee * TODO: Add ability to call back to UI when this failed, and generally better handle error cases.
52b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee */
53b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Leepublic final class EasLoadAttachment extends EasOperation {
54b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
55aeaf8f3a7277eb31d42d31a1df52f900f9ce1ec8Anthony Lee    public static final int RESULT_SUCCESS = 0;
56aeaf8f3a7277eb31d42d31a1df52f900f9ce1ec8Anthony Lee
57b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /** Attachment Loading Errors **/
58b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    public static final int RESULT_LOAD_ATTACHMENT_INFO_ERROR = -100;
59b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    public static final int RESULT_ATTACHMENT_NO_LOCATION_ERROR = -101;
60b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    public static final int RESULT_ATTACHMENT_LOAD_MESSAGE_ERROR = -102;
61b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    public static final int RESULT_ATTACHMENT_INTERNAL_HANDLING_ERROR = -103;
62b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    public static final int RESULT_ATTACHMENT_RESPONSE_PARSING_ERROR = -104;
63b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
64b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    private final IEmailServiceCallback mCallback;
65b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    private final long mAttachmentId;
66b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
67b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    // These members are set in a future point in time outside of the constructor.
68b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    private Attachment mAttachment;
69b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
70b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /**
71b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * Constructor for use with {@link EasService} when performing an actual sync.
72b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * @param context Our {@link Context}.
73ec620ef7363101f5ce8208d17ac496e882d995fdMartin Hibdon     * @param account The account we're loading the attachment for.
74b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * @param attachmentId The local id of the attachment (i.e. its id in the database).
75b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * @param callback The callback for any status updates.
76b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     */
77ec620ef7363101f5ce8208d17ac496e882d995fdMartin Hibdon    public EasLoadAttachment(final Context context, final Account account, final long attachmentId,
78b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            final IEmailServiceCallback callback) {
79b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // The account is loaded before performOperation but it is not guaranteed to be available
80b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // before then.
81ec620ef7363101f5ce8208d17ac496e882d995fdMartin Hibdon        super(context, account);
82b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        mCallback = callback;
83b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        mAttachmentId = attachmentId;
84b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
85b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
86b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /**
87b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * Helper function that makes a callback for us within our implementation.
88b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     */
89b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    private static void doStatusCallback(final IEmailServiceCallback callback,
90b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            final long messageKey, final long attachmentId, final int status, final int progress) {
91b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        if (callback != null) {
92b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            try {
93b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                // loadAttachmentStatus is mart of IEmailService interface.
94b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                callback.loadAttachmentStatus(messageKey, attachmentId, status, progress);
95b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            } catch (final RemoteException e) {
96b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                LogUtils.e(LOG_TAG, "RemoteException in loadAttachment: %s", e.getMessage());
97b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            }
98b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
99b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
100b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
101b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /**
102b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * Helper class that is passed to other objects to perform callbacks for us.
103b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     */
104b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    public static class ProgressCallback {
105b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        private final IEmailServiceCallback mCallback;
106b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        private final EmailContent.Attachment mAttachment;
107b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
108b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        public ProgressCallback(final IEmailServiceCallback callback,
109b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                final EmailContent.Attachment attachment) {
110b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            mCallback = callback;
111b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            mAttachment = attachment;
112b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
113b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
114b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        public void doCallback(final int progress) {
115b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachment.mId,
116b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    EmailServiceStatus.IN_PROGRESS, progress);
117b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
118b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
119b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
120b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /**
121b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * Encoder for Exchange 2003 attachment names.  They come from the server partially encoded,
122b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * but there are still possible characters that need to be encoded (Why, MSFT, why?)
123b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     */
124b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    private static class AttachmentNameEncoder extends UriCodec {
125b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        @Override
126b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        protected boolean isRetained(final char c) {
127b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            // These four characters are commonly received in EAS 2.5 attachment names and are
128b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            // valid (verified by testing); we won't encode them
129b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            return c == '_' || c == ':' || c == '/' || c == '.';
130b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
131b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
132b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
133b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /**
134b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * Finish encoding attachment names for Exchange 2003.
135b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * @param str A partially encoded string.
136b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * @return The fully encoded version of str.
137b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     */
138b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    private static String encodeForExchange2003(final String str) {
139b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        final AttachmentNameEncoder enc = new AttachmentNameEncoder();
140b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        final StringBuilder sb = new StringBuilder(str.length() + 16);
141b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        enc.appendPartiallyEncoded(sb, str);
142b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        return sb.toString();
143b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
144b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
145b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /**
146b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * Finish encoding attachment names for Exchange 2003.
147b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * @return A {@link EmailServiceStatus} code that indicates the result of the operation.
148b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     */
149b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    @Override
1508c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu    public int performOperation() {
151b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        mAttachment = EmailContent.Attachment.restoreAttachmentWithId(mContext, mAttachmentId);
152b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        if (mAttachment == null) {
153b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            LogUtils.e(LOG_TAG, "Could not load attachment %d", mAttachmentId);
154b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            doStatusCallback(mCallback, -1, mAttachmentId, EmailServiceStatus.ATTACHMENT_NOT_FOUND,
155b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    0);
156b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            return RESULT_LOAD_ATTACHMENT_INFO_ERROR;
157b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
158b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        if (mAttachment.mLocation == null) {
159b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            LogUtils.e(LOG_TAG, "Attachment %d lacks a location", mAttachmentId);
160b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            doStatusCallback(mCallback, -1, mAttachmentId, EmailServiceStatus.ATTACHMENT_NOT_FOUND,
161b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    0);
162b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            return RESULT_ATTACHMENT_NO_LOCATION_ERROR;
163b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
164b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        final EmailContent.Message message = EmailContent.Message
165b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                .restoreMessageWithId(mContext, mAttachment.mMessageKey);
166b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        if (message == null) {
167b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            LogUtils.e(LOG_TAG, "Could not load message %d", mAttachment.mMessageKey);
168b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachmentId,
169b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
170b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            return RESULT_ATTACHMENT_LOAD_MESSAGE_ERROR;
171b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
172b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
173b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // First callback to let the client know that we have started the attachment load.
1747848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee        doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachmentId,
175b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                EmailServiceStatus.IN_PROGRESS, 0);
176b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
177aeaf8f3a7277eb31d42d31a1df52f900f9ce1ec8Anthony Lee        final int result = super.performOperation();
178b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
1797848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee        // Last callback to report results.
180aeaf8f3a7277eb31d42d31a1df52f900f9ce1ec8Anthony Lee        if (result < 0) {
1817848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee            // We had an error processing an attachment, let's report a {@link EmailServiceStatus}
1827848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee            // connection error in this case
1837848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee            LogUtils.d(LOG_TAG, "Invoking callback for attachmentId: %d with CONNECTION_ERROR",
1847848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee                    mAttachmentId);
1857848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee            doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachmentId,
1867848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee                    EmailServiceStatus.CONNECTION_ERROR, 0);
1877848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee        } else {
1887848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee            LogUtils.d(LOG_TAG, "Invoking callback for attachmentId: %d with SUCCESS",
1897848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee                    mAttachmentId);
1907848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee            doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachmentId,
1917848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee                    EmailServiceStatus.SUCCESS, 0);
192b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
193aeaf8f3a7277eb31d42d31a1df52f900f9ce1ec8Anthony Lee        return result;
194b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
195b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
196b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    @Override
197b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    protected String getCommand() {
198b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        if (mAttachment == null) {
199b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            LogUtils.wtf(LOG_TAG, "Error, mAttachment is null");
200b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
201b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
202b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        final String cmd;
203b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
204b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            // The operation is different in EAS 14.0 than in earlier versions
205b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            cmd = "ItemOperations";
206b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        } else {
207b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            final String location;
208b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            // For Exchange 2003 (EAS 2.5), we have to look for illegal chars in the file name
209b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            // that EAS sent to us!
210b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
211b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                location = encodeForExchange2003(mAttachment.mLocation);
212b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            } else {
213b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                location = mAttachment.mLocation;
214b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            }
215b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            cmd = "GetAttachment&AttachmentName=" + location;
216b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
217b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        return cmd;
218b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
219b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
220b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    @Override
221b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    protected HttpEntity getRequestEntity() throws IOException {
222b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        if (mAttachment == null) {
223b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            LogUtils.wtf(LOG_TAG, "Error, mAttachment is null");
224b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
225b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
226b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        final HttpEntity entity;
227b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        final Serializer s = new Serializer();
228b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
229b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            s.start(Tags.ITEMS_ITEMS).start(Tags.ITEMS_FETCH);
230b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            s.data(Tags.ITEMS_STORE, "Mailbox");
231b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            s.data(Tags.BASE_FILE_REFERENCE, mAttachment.mLocation);
232b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            s.end().end().done(); // ITEMS_FETCH, ITEMS_ITEMS
233b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            entity = makeEntity(s);
234b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        } else {
235b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            // Older versions of the protocol have the attachment location in the command.
236b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            entity = null;
237b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
238b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        return entity;
239b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
240b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
241b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /**
242b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * Close, ignoring errors (as during cleanup)
243b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * @param c a Closeable
244b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     */
245b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    private static void close(final Closeable c) {
246b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        try {
247b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            c.close();
248b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        } catch (IOException e) {
249b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            LogUtils.e(LOG_TAG, "IOException while cleaning up attachment: %s", e.getMessage());
250b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
251b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
252b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
253b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /**
254b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * Save away the contentUri for this Attachment and notify listeners
255b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     */
256b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    private boolean finishLoadAttachment(final EmailContent.Attachment attachment, final File file) {
257b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        final InputStream in;
258b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        try {
259b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            in = new FileInputStream(file);
260b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        } catch (final FileNotFoundException e) {
261b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            // Unlikely, as we just created it successfully, but log it.
262b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            LogUtils.e(LOG_TAG, "Could not open attachment file: %s", e.getMessage());
263b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            return false;
264b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
265b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        AttachmentUtilities.saveAttachment(mContext, in, attachment);
266b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        close(in);
267b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        return true;
268b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
269b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
270b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    /**
271b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * Read the {@link EasResponse} and extract the attachment data, saving it to the provider.
272b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     * @param response The (successful) {@link EasResponse} containing the attachment data.
2737848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee     * @return A status code, 0 is a success, anything negative is an error outlined by constants
2747848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee     *         in this class or its base class.
275b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee     */
276b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    @Override
2778c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu    protected int handleResponse(final EasResponse response) {
278b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // Some very basic error checking on the response object first.
279b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // Our base class should be responsible for checking these errors but if the error
280b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // checking is done in the override functions, we can be more specific about
281b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // the errors that are being returned to the caller of performOperation().
282b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        if (response.isEmpty()) {
283b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            LogUtils.e(LOG_TAG, "Error, empty response.");
28461ebb38e67421fd122c81f046bf11778b61a2113Martin Hibdon            return RESULT_NETWORK_PROBLEM;
285b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
286b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
287b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // This is a 2 step process.
288b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // 1. Grab what came over the wire and write it to a temp file on disk.
289b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        // 2. Move the attachment to its final location.
290b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        final File tmpFile;
291b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        try {
292b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            tmpFile = File.createTempFile("eas_", "tmp", mContext.getCacheDir());
293b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        } catch (final IOException e) {
294b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            LogUtils.e(LOG_TAG, "Could not open temp file: %s", e.getMessage());
29561ebb38e67421fd122c81f046bf11778b61a2113Martin Hibdon            return RESULT_NETWORK_PROBLEM;
296b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
297b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee
298b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        try {
299b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            final OutputStream os;
300b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            try {
301b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                os = new FileOutputStream(tmpFile);
302b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            } catch (final FileNotFoundException e) {
303b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                LogUtils.e(LOG_TAG, "Temp file not found: %s", e.getMessage());
3047848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee                return RESULT_ATTACHMENT_INTERNAL_HANDLING_ERROR;
305b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            }
306b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            try {
307b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                final InputStream is = response.getInputStream();
308b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                try {
309b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    // TODO: Right now we are explictly loading this from a class
310b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    // that will be deprecated when we move over to EasService. When we start using
311b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    // our internal class instead, there will be rippling side effect changes that
312b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    // need to be made when this time comes.
313b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    final ProgressCallback callback = new ProgressCallback(mCallback, mAttachment);
314b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    final boolean success;
315b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
316b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                        final ItemOperationsParser parser = new ItemOperationsParser(is, os,
317b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                                mAttachment.mSize, callback);
318b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                        parser.parse();
319b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                        success = (parser.getStatusCode() == 1);
320b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    } else {
321b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                        final int length = response.getLength();
322b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                        if (length != 0) {
323b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                            // len > 0 means that Content-Length was set in the headers
324b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                            // len < 0 means "chunked" transfer-encoding
325b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                            ItemOperationsParser.readChunked(is, os,
326b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                                    (length < 0) ? mAttachment.mSize : length, callback);
327b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                        }
328b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                        success = true;
329b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    }
330b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    // Check that we successfully grabbed what came over the wire...
331b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    if (!success) {
332b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                        LogUtils.e(LOG_TAG, "Error parsing server response");
3337848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee                        return RESULT_ATTACHMENT_RESPONSE_PARSING_ERROR;
334b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    }
335b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    // Now finish the process and save to the final destination.
336b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    final boolean loadResult = finishLoadAttachment(mAttachment, tmpFile);
337b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    if (!loadResult) {
338b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                        LogUtils.e(LOG_TAG, "Error post processing attachment file.");
3397848f4e9c1a50fa36ddcce692b11ee713df14cb8Anthony Lee                        return RESULT_ATTACHMENT_INTERNAL_HANDLING_ERROR;
340b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    }
341b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                } catch (final IOException e) {
342b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    LogUtils.e(LOG_TAG, "Error handling attachment: %s", e.getMessage());
343b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    return RESULT_ATTACHMENT_INTERNAL_HANDLING_ERROR;
344b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                } finally {
345b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                    close(is);
346b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                }
347b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            } finally {
348b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee                close(os);
349b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            }
350b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        } finally {
351b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee            tmpFile.delete();
352b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee        }
353aeaf8f3a7277eb31d42d31a1df52f900f9ce1ec8Anthony Lee        return RESULT_SUCCESS;
354b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee    }
355b19dfd15473dfedec2aa481d453b17a24486b05dAnthony Lee}
356