1package com.android.exchange.eas;
2
3import android.content.Context;
4import android.database.Cursor;
5
6import com.android.emailcommon.TrafficFlags;
7import com.android.emailcommon.provider.Account;
8import com.android.emailcommon.provider.EmailContent.Message;
9import com.android.emailcommon.provider.EmailContent.MessageColumns;
10import com.android.emailcommon.provider.EmailContent.SyncColumns;
11import com.android.emailcommon.provider.Mailbox;
12import com.android.emailcommon.service.SyncWindow;
13import com.android.exchange.Eas;
14import com.android.exchange.adapter.AbstractSyncParser;
15import com.android.exchange.adapter.EmailSyncParser;
16import com.android.exchange.adapter.Serializer;
17import com.android.exchange.adapter.Tags;
18
19import java.io.IOException;
20import java.io.InputStream;
21import java.util.ArrayList;
22
23/**
24 * Subclass to handle sync details for mail collections.
25 */
26public class EasSyncMail extends EasSyncCollectionTypeBase {
27
28    /**
29     * The projection used for building the fetch request list.
30     */
31    private static final String[] FETCH_REQUEST_PROJECTION = { SyncColumns.SERVER_ID };
32    private static final int FETCH_REQUEST_SERVER_ID = 0;
33
34    private static final int EMAIL_WINDOW_SIZE = 10;
35
36
37    @Override
38    public int getTrafficFlag() {
39        return TrafficFlags.DATA_EMAIL;
40    }
41
42    @Override
43    public void setSyncOptions(final Context context, final Serializer s,
44            final double protocolVersion, final Account account, final Mailbox mailbox,
45            final boolean isInitialSync, final int numWindows) throws IOException {
46        if (isInitialSync) {
47            // No special options to set for initial mailbox sync.
48            return;
49        }
50
51        // Check for messages that aren't fully loaded.
52        final ArrayList<String> messagesToFetch = addToFetchRequestList(context, mailbox);
53        // The "empty" case is typical; we send a request for changes, and also specify a sync
54        // window, body preference type (HTML for EAS 12.0 and later; MIME for EAS 2.5), and
55        // truncation
56        // If there are fetch requests, we only want the fetches (i.e. no changes from the server)
57        // so we turn MIME support off.  Note that we are always using EAS 2.5 if there are fetch
58        // requests
59        if (messagesToFetch.isEmpty()) {
60            // Permanently delete if in trash mailbox
61            // In Exchange 2003, deletes-as-moves tag = true; no tag = false
62            // In Exchange 2007 and up, deletes-as-moves tag is "0" (false) or "1" (true)
63            final boolean isTrashMailbox = mailbox.mType == Mailbox.TYPE_TRASH;
64            if (protocolVersion < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
65                if (!isTrashMailbox) {
66                    s.tag(Tags.SYNC_DELETES_AS_MOVES);
67                }
68            } else {
69                s.data(Tags.SYNC_DELETES_AS_MOVES, isTrashMailbox ? "0" : "1");
70            }
71            s.tag(Tags.SYNC_GET_CHANGES);
72
73            final int windowSize = numWindows * EMAIL_WINDOW_SIZE;
74            if (windowSize > MAX_WINDOW_SIZE  + EMAIL_WINDOW_SIZE) {
75                throw new IOException("Max window size reached and still no data");
76            }
77            s.data(Tags.SYNC_WINDOW_SIZE,
78                    String.valueOf(windowSize < MAX_WINDOW_SIZE ? windowSize : MAX_WINDOW_SIZE));
79            s.start(Tags.SYNC_OPTIONS);
80            // Set the lookback appropriately (EAS calls this a "filter")
81            s.data(Tags.SYNC_FILTER_TYPE, getEmailFilter(account, mailbox));
82            // Set the truncation amount for all classes
83            if (protocolVersion >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
84                s.start(Tags.BASE_BODY_PREFERENCE);
85                // HTML for email
86                s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML);
87                s.data(Tags.BASE_TRUNCATION_SIZE, Eas.EAS12_TRUNCATION_SIZE);
88                s.end();
89            } else {
90                // Use MIME data for EAS 2.5
91                s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_MIME);
92                s.data(Tags.SYNC_MIME_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
93            }
94            s.end();
95        } else {
96            // If we have any messages that are not fully loaded, ask for plain text rather than
97            // MIME, to guarantee we'll get usable text body. This also means we should NOT ask for
98            // new messages -- we only want data for the message explicitly fetched.
99            s.start(Tags.SYNC_OPTIONS);
100            s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_TEXT);
101            s.data(Tags.SYNC_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
102            s.end();
103
104            // Add FETCH commands for messages that need a body (i.e. we didn't find it during our
105            // earlier sync; this happens only in EAS 2.5 where the body couldn't be found after
106            // parsing the message's MIME data).
107            s.start(Tags.SYNC_COMMANDS);
108            for (final String serverId : messagesToFetch) {
109                s.start(Tags.SYNC_FETCH).data(Tags.SYNC_SERVER_ID, serverId).end();
110            }
111            s.end();
112        }
113    }
114
115    @Override
116    public AbstractSyncParser getParser(final Context context, final Account account,
117            final Mailbox mailbox, final InputStream is) throws IOException {
118        return new EmailSyncParser(context, is, mailbox, account);
119    }
120
121    /**
122     * Query the provider for partially loaded messages.
123     * @return Server ids for partially loaded messages.
124     */
125    private ArrayList<String> addToFetchRequestList(final Context context, final Mailbox mailbox) {
126        final ArrayList<String> messagesToFetch = new ArrayList<String>();
127        final Cursor c = context.getContentResolver().query(Message.CONTENT_URI,
128                FETCH_REQUEST_PROJECTION,  MessageColumns.FLAG_LOADED + "=" +
129                Message.FLAG_LOADED_PARTIAL + " AND " +  MessageColumns.MAILBOX_KEY + "=?",
130                new String[] {Long.toString(mailbox.mId)}, null);
131        if (c != null) {
132            try {
133                while (c.moveToNext()) {
134                    messagesToFetch.add(c.getString(FETCH_REQUEST_SERVER_ID));
135                }
136            } finally {
137                c.close();
138            }
139        }
140        return messagesToFetch;
141    }
142
143    /**
144     * Get the sync window for this collection and translate it to EAS's value for that (EAS refers
145     * to this as the "filter").
146     * @param account The {@link Account} for this sync; its sync window is used if the mailbox
147     *                doesn't specify an override.
148     * @param mailbox The {@link Mailbox} for this sync.
149     * @return The EAS string value for the sync window specified for this mailbox.
150     */
151    private String getEmailFilter(final Account account, final Mailbox mailbox) {
152        final int syncLookback = mailbox.mSyncLookback == SyncWindow.SYNC_WINDOW_ACCOUNT
153                ? account.mSyncLookback : mailbox.mSyncLookback;
154        switch (syncLookback) {
155            case SyncWindow.SYNC_WINDOW_1_DAY:
156                return Eas.FILTER_1_DAY;
157            case SyncWindow.SYNC_WINDOW_3_DAYS:
158                return Eas.FILTER_3_DAYS;
159            case SyncWindow.SYNC_WINDOW_1_WEEK:
160                return Eas.FILTER_1_WEEK;
161            case SyncWindow.SYNC_WINDOW_2_WEEKS:
162                return Eas.FILTER_2_WEEKS;
163            case SyncWindow.SYNC_WINDOW_1_MONTH:
164                return Eas.FILTER_1_MONTH;
165            case SyncWindow.SYNC_WINDOW_ALL:
166                return Eas.FILTER_ALL;
167            default:
168                // Auto window is deprecated and will also use the default.
169                return Eas.FILTER_1_WEEK;
170        }
171    }
172}
173