1 package com.android.exchange.eas; 2 3 import android.content.Context; 4 import android.database.Cursor; 5 6 import com.android.emailcommon.TrafficFlags; 7 import com.android.emailcommon.provider.Account; 8 import com.android.emailcommon.provider.EmailContent.Message; 9 import com.android.emailcommon.provider.EmailContent.MessageColumns; 10 import com.android.emailcommon.provider.EmailContent.SyncColumns; 11 import com.android.emailcommon.provider.Mailbox; 12 import com.android.emailcommon.service.SyncWindow; 13 import com.android.exchange.Eas; 14 import com.android.exchange.adapter.AbstractSyncParser; 15 import com.android.exchange.adapter.EmailSyncParser; 16 import com.android.exchange.adapter.Serializer; 17 import com.android.exchange.adapter.Tags; 18 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.util.ArrayList; 22 23 /** 24 * Subclass to handle sync details for mail collections. 25 */ 26 public 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 getTrafficFlag()38 public int getTrafficFlag() { 39 return TrafficFlags.DATA_EMAIL; 40 } 41 42 @Override setSyncOptions(final Context context, final Serializer s, final double protocolVersion, final Account account, final Mailbox mailbox, final boolean isInitialSync, final int numWindows)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 getParser(final Context context, final Account account, final Mailbox mailbox, final InputStream is)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 */ addToFetchRequestList(final Context context, final Mailbox mailbox)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 */ getEmailFilter(final Account account, final Mailbox mailbox)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