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