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 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