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(final boolean allowReload) { 69 final boolean result = super.init(allowReload); 70 if (result) { 71 mCollectionTypeHandler = getCollectionTypeHandler(mMailbox.mType); 72 if (mCollectionTypeHandler == null) { 73 return false; 74 } 75 // Set up traffic stats bookkeeping. 76 final int trafficFlags = TrafficFlags.getSyncFlags(mContext, mAccount); 77 TrafficStats.setThreadStatsTag(trafficFlags | mCollectionTypeHandler.getTrafficFlag()); 78 } 79 return result; 80 } 81 82 @Override 83 protected HttpEntity getRequestEntity() throws IOException { 84 final String className = Eas.getFolderClass(mMailbox.mType); 85 final String syncKey = getSyncKey(); 86 LogUtils.d(TAG, "Syncing account %d mailbox %d (class %s) with syncKey %s", mAccount.mId, 87 mMailbox.mId, className, syncKey); 88 mInitialSync = EmailContent.isInitialSyncKey(syncKey); 89 final Serializer s = new Serializer(); 90 s.start(Tags.SYNC_SYNC); 91 s.start(Tags.SYNC_COLLECTIONS); 92 s.start(Tags.SYNC_COLLECTION); 93 // The "Class" element is removed in EAS 12.1 and later versions 94 if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) { 95 s.data(Tags.SYNC_CLASS, className); 96 } 97 s.data(Tags.SYNC_SYNC_KEY, syncKey); 98 s.data(Tags.SYNC_COLLECTION_ID, mMailbox.mServerId); 99 mCollectionTypeHandler.setSyncOptions(mContext, s, getProtocolVersion(), mAccount, mMailbox, 100 mInitialSync, mNumWindows); 101 s.end().end().end().done(); 102 103 return makeEntity(s); 104 } 105 106 @Override 107 protected int handleResponse(final EasResponse response) 108 throws IOException, CommandStatusException { 109 try { 110 final AbstractSyncParser parser = mCollectionTypeHandler.getParser(mContext, mAccount, 111 mMailbox, response.getInputStream()); 112 final boolean moreAvailable = parser.parse(); 113 if (moreAvailable) { 114 return RESULT_MORE_AVAILABLE; 115 } 116 } catch (final Parser.EmptyStreamException e) { 117 // This indicates a compressed response which was empty, which is OK. 118 } 119 return RESULT_DONE; 120 } 121 122 @Override 123 public int performOperation() { 124 int result = RESULT_MORE_AVAILABLE; 125 mNumWindows = 1; 126 final String key = getSyncKey(); 127 while (result == RESULT_MORE_AVAILABLE) { 128 result = super.performOperation(); 129 if (result == RESULT_MORE_AVAILABLE || result == RESULT_DONE) { 130 mCollectionTypeHandler.cleanup(mContext, mAccount); 131 } 132 // TODO: Clear pending request queue. 133 final String newKey = getSyncKey(); 134 if (result == RESULT_MORE_AVAILABLE && key.equals(newKey)) { 135 LogUtils.e(TAG, 136 "Server has more data but we have the same key: %s numWindows: %d", 137 key, mNumWindows); 138 mNumWindows++; 139 } else { 140 mNumWindows = 1; 141 } 142 } 143 return result; 144 } 145 146 @Override 147 protected long getTimeout() { 148 if (mInitialSync) { 149 return 120 * DateUtils.SECOND_IN_MILLIS; 150 } 151 return super.getTimeout(); 152 } 153 154 /** 155 * Get an instance of the correct {@link EasSyncCollectionTypeBase} for a specific collection 156 * type. 157 * @param type The type of the {@link Mailbox} that we're trying to sync. 158 * @return An {@link EasSyncCollectionTypeBase} appropriate for this type. 159 */ 160 private EasSyncCollectionTypeBase getCollectionTypeHandler(final int type) { 161 switch (type) { 162 case Mailbox.TYPE_MAIL: 163 case Mailbox.TYPE_INBOX: 164 case Mailbox.TYPE_DRAFTS: 165 case Mailbox.TYPE_SENT: 166 case Mailbox.TYPE_TRASH: 167 case Mailbox.TYPE_JUNK: 168 return new EasSyncMail(); 169 case Mailbox.TYPE_CALENDAR: { 170 return new EasSyncCalendar(mContext, mAccount, mMailbox); 171 } 172 case Mailbox.TYPE_CONTACTS: 173 return new EasSyncContacts(mAccount.mEmailAddress); 174 default: 175 LogUtils.e(LOG_TAG, "unexpected collectiontype %d", type); 176 return null; 177 } 178 } 179 } 180