1 package com.android.exchange.eas; 2 3 import android.content.Context; 4 import android.net.TrafficStats; 5 import android.text.format.DateUtils; 6 7 import com.android.emailcommon.TrafficFlags; 8 import com.android.emailcommon.provider.Account; 9 import com.android.emailcommon.provider.EmailContent; 10 import com.android.emailcommon.provider.Mailbox; 11 import com.android.exchange.CommandStatusException; 12 import com.android.exchange.Eas; 13 import com.android.exchange.EasResponse; 14 import com.android.exchange.adapter.AbstractSyncParser; 15 import com.android.exchange.adapter.Parser; 16 import com.android.exchange.adapter.Serializer; 17 import com.android.exchange.adapter.Tags; 18 import com.android.mail.utils.LogUtils; 19 20 import org.apache.http.HttpEntity; 21 22 import java.io.IOException; 23 24 /** 25 * Performs an EAS sync operation for one folder (excluding mail upsync). 26 * TODO: Merge with {@link EasSync}, which currently handles mail upsync. 27 */ 28 public class EasSyncBase extends EasOperation { 29 30 private static final String TAG = Eas.LOG_TAG; 31 32 public static final int RESULT_DONE = 0; 33 public static final int RESULT_MORE_AVAILABLE = 1; 34 35 private boolean mInitialSync; 36 private final Mailbox mMailbox; 37 private EasSyncCollectionTypeBase mCollectionTypeHandler; 38 39 private int mNumWindows; 40 41 // TODO: Convert to accountId when ready to convert to EasService. 42 public EasSyncBase(final Context context, final Account account, final Mailbox mailbox) { 43 super(context, account); 44 mMailbox = mailbox; 45 } 46 47 /** 48 * Get the sync key for this mailbox. 49 * @return The sync key for the object being synced. "0" means this is the first sync. If 50 * there is an error in getting the sync key, this function returns null. 51 */ 52 protected String getSyncKey() { 53 if (mMailbox == null) { 54 return null; 55 } 56 if (mMailbox.mSyncKey == null) { 57 mMailbox.mSyncKey = "0"; 58 } 59 return mMailbox.mSyncKey; 60 } 61 62 @Override 63 protected String getCommand() { 64 return "Sync"; 65 } 66 67 @Override 68 public boolean init() { 69 mCollectionTypeHandler = getCollectionTypeHandler(mMailbox.mType); 70 if (mCollectionTypeHandler == null) { 71 return false; 72 } 73 // Set up traffic stats bookkeeping. 74 final int trafficFlags = TrafficFlags.getSyncFlags(mContext, mAccount); 75 TrafficStats.setThreadStatsTag(trafficFlags | mCollectionTypeHandler.getTrafficFlag()); 76 return true; 77 } 78 79 @Override 80 protected HttpEntity getRequestEntity() throws IOException { 81 final String className = Eas.getFolderClass(mMailbox.mType); 82 final String syncKey = getSyncKey(); 83 LogUtils.d(TAG, "Syncing account %d mailbox %d (class %s) with syncKey %s", mAccount.mId, 84 mMailbox.mId, className, syncKey); 85 mInitialSync = EmailContent.isInitialSyncKey(syncKey); 86 final Serializer s = new Serializer(); 87 s.start(Tags.SYNC_SYNC); 88 s.start(Tags.SYNC_COLLECTIONS); 89 s.start(Tags.SYNC_COLLECTION); 90 // The "Class" element is removed in EAS 12.1 and later versions 91 if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) { 92 s.data(Tags.SYNC_CLASS, className); 93 } 94 s.data(Tags.SYNC_SYNC_KEY, syncKey); 95 s.data(Tags.SYNC_COLLECTION_ID, mMailbox.mServerId); 96 mCollectionTypeHandler.setSyncOptions(mContext, s, getProtocolVersion(), mAccount, mMailbox, 97 mInitialSync, mNumWindows); 98 s.end().end().end().done(); 99 100 return makeEntity(s); 101 } 102 103 @Override 104 protected int handleResponse(final EasResponse response) 105 throws IOException, CommandStatusException { 106 try { 107 final AbstractSyncParser parser = mCollectionTypeHandler.getParser(mContext, mAccount, 108 mMailbox, response.getInputStream()); 109 final boolean moreAvailable = parser.parse(); 110 if (moreAvailable) { 111 return RESULT_MORE_AVAILABLE; 112 } 113 } catch (final Parser.EmptyStreamException e) { 114 // This indicates a compressed response which was empty, which is OK. 115 } 116 return RESULT_DONE; 117 } 118 119 @Override 120 public int performOperation() { 121 int result = RESULT_MORE_AVAILABLE; 122 mNumWindows = 1; 123 final String key = getSyncKey(); 124 while (result == RESULT_MORE_AVAILABLE) { 125 result = super.performOperation(); 126 if (result == RESULT_MORE_AVAILABLE || result == RESULT_DONE) { 127 mCollectionTypeHandler.cleanup(mContext, mAccount); 128 } 129 // TODO: Clear pending request queue. 130 final String newKey = getSyncKey(); 131 if (result == RESULT_MORE_AVAILABLE && key.equals(newKey)) { 132 LogUtils.e(TAG, 133 "Server has more data but we have the same key: %s numWindows: %d", 134 key, mNumWindows); 135 mNumWindows++; 136 } else { 137 mNumWindows = 1; 138 } 139 } 140 return result; 141 } 142 143 @Override 144 protected long getTimeout() { 145 if (mInitialSync) { 146 return 120 * DateUtils.SECOND_IN_MILLIS; 147 } 148 return super.getTimeout(); 149 } 150 151 /** 152 * Get an instance of the correct {@link EasSyncCollectionTypeBase} for a specific collection 153 * type. 154 * @param type The type of the {@link Mailbox} that we're trying to sync. 155 * @return An {@link EasSyncCollectionTypeBase} appropriate for this type. 156 */ 157 private EasSyncCollectionTypeBase getCollectionTypeHandler(final int type) { 158 switch (type) { 159 case Mailbox.TYPE_MAIL: 160 case Mailbox.TYPE_INBOX: 161 case Mailbox.TYPE_DRAFTS: 162 case Mailbox.TYPE_SENT: 163 case Mailbox.TYPE_TRASH: 164 case Mailbox.TYPE_JUNK: 165 return new EasSyncMail(); 166 case Mailbox.TYPE_CALENDAR: { 167 return new EasSyncCalendar(mContext, mAccount, mMailbox); 168 } 169 case Mailbox.TYPE_CONTACTS: 170 return new EasSyncContacts(mAccount.mEmailAddress); 171 default: 172 LogUtils.e(LOG_TAG, "unexpected collectiontype %d", type); 173 return null; 174 } 175 } 176 } 177