EasSyncService.java revision 726d60d9b758f0383f8f8481190fc1a638427209
1ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/*
2ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Copyright (C) 2008-2009 Marc Blank
3ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed to The Android Open Source Project.
4ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
5ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed under the Apache License, Version 2.0 (the "License");
6ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * you may not use this file except in compliance with the License.
7ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * You may obtain a copy of the License at
8ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
9ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *      http://www.apache.org/licenses/LICENSE-2.0
10ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
11ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Unless required by applicable law or agreed to in writing, software
12ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * distributed under the License is distributed on an "AS IS" BASIS,
13ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * See the License for the specific language governing permissions and
15ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * limitations under the License.
16ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
17ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
18ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankpackage com.android.exchange;
19ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
201b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blankimport com.android.email.codec.binary.Base64;
21ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport com.android.email.mail.AuthenticationFailedException;
22ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport com.android.email.mail.MessagingException;
2367698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Account;
2467698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.AccountColumns;
2567698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Attachment;
2667698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.AttachmentColumns;
2767698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.HostAuth;
2867698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Mailbox;
2967698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.MailboxColumns;
3067698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Message;
317c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.AbstractSyncAdapter;
3248af7392c82262d17700e3fbdccf3a582809d449Marc Blankimport com.android.exchange.adapter.AccountSyncAdapter;
337c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.ContactsSyncAdapter;
347c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.EmailSyncAdapter;
357c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.FolderSyncParser;
367c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.PingParser;
377c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.Serializer;
387c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.Tags;
397c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.Parser.EasParserException;
40ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
418047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.Header;
4200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.HttpEntity;
4300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.HttpResponse;
441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blankimport org.apache.http.HttpStatus;
458047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.HttpClient;
468047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.methods.HttpOptions;
4700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.client.methods.HttpPost;
488047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.methods.HttpRequestBase;
49e44d5875af006f4217718a1c0fc0e235af3863afMarc Blankimport org.apache.http.conn.ClientConnectionManager;
508047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.entity.ByteArrayEntity;
5100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.impl.client.DefaultHttpClient;
528047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.BasicHttpParams;
538047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.HttpConnectionParams;
548047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.HttpParams;
5500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
56ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.ContentResolver;
57ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankimport android.content.ContentUris;
58ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.ContentValues;
59ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.Context;
60ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.database.Cursor;
61d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blankimport android.os.RemoteException;
6274c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blankimport android.os.SystemClock;
63ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
6400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.File;
6500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.FileOutputStream;
6600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.IOException;
6700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.InputStream;
6800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.net.URI;
6900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.net.URLEncoder;
70e44d5875af006f4217718a1c0fc0e235af3863afMarc Blankimport java.security.cert.CertificateException;
7100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.ArrayList;
72ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankimport java.util.HashMap;
7300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
74ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankpublic class EasSyncService extends AbstractSyncService {
751b275b9408d5b856e2482fa3951827489e9585ccMarc Blank    private static final String EMAIL_WINDOW_SIZE = "5";
76726d60d9b758f0383f8f8481190fc1a638427209Marc Blank    public static final String PIM_WINDOW_SIZE = "5";
77ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID =
78ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SERVER_ID + "=?";
799d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private static final String WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING =
809d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank        MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL +
81f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        '=' + Mailbox.CHECK_INTERVAL_PING;
8222bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank    private static final String AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX = " AND " +
83f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        MailboxColumns.SYNC_INTERVAL + " IN (" + Mailbox.CHECK_INTERVAL_PING +
84f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        ',' + Mailbox.CHECK_INTERVAL_PUSH + ") AND " + MailboxColumns.TYPE + "!=\"" +
857c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank        Mailbox.TYPE_EAS_ACCOUNT_MAILBOX + '\"';
869d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private static final String WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX =
879d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank        MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL +
88f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        '=' + Mailbox.CHECK_INTERVAL_PUSH_HOLD;
891b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private static final String[] SYNC_STATUS_PROJECTION =
901b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        new String[] {MailboxColumns.ID, MailboxColumns.SYNC_STATUS};
918047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    static private final int CHUNK_SIZE = 16*1024;
928047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
938047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    static private final String PING_COMMAND = "Ping";
94c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    static private final int COMMAND_TIMEOUT = 20*SECONDS;
95c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
961b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    /**
971b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * We start with an 8 minute timeout, and increase/decrease by 3 minutes at a time.  There's
981b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * no point having a timeout shorter than 5 minutes, I think; at that point, we can just let
991b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * the ping exception out.  The maximum I use is 17 minutes, which is really an empirical
1001b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * choice; too long and we risk silent connection loss and loss of push for that period.  Too
1011b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * short and we lose efficiency/battery life.
1021b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     *
1031b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * If we ever have to drop the ping timeout, we'll never increase it again.  There's no point
1041b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * going into hysteresis; the NAT timeout isn't going to change without a change in connection,
1051b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * which will cause the sync service to be restarted at the starting heartbeat and going through
1061b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * the process again.
1071b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     */
1081b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_MINUTES = 60; // in seconds
1091b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_FUDGE_LOW = 10;
1101b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_STARTING_HEARTBEAT = (8*PING_MINUTES)-PING_FUDGE_LOW;
1111b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_MIN_HEARTBEAT = (5*PING_MINUTES)-PING_FUDGE_LOW;
1121b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_MAX_HEARTBEAT = (17*PING_MINUTES)-PING_FUDGE_LOW;
1131b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_HEARTBEAT_INCREMENT = 3*PING_MINUTES;
1147672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank    static private final int PING_FORCE_HEARTBEAT = 2*PING_MINUTES;
1151b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
1161b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PROTOCOL_PING_STATUS_COMPLETED = 1;
1171b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PROTOCOL_PING_STATUS_CHANGES_FOUND = 2;
11896293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank
119c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    // Fallbacks (in minutes) for ping loop failures
120458c47296f7643342493de8feca5e89f33d0ecccMarc Blank    static private final int MAX_PING_FAILURES = 2;
121c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    static private final int PING_FALLBACK_INBOX = 5;
1220d88d88926b63042e68c4ea0dc67143ed00d52d0Marc Blank    static private final int PING_FALLBACK_PIM = 30;
123d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
124ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // Reasonable default
125ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    String mProtocolVersion = "2.5";
12600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank    public Double mProtocolVersionDouble;
12785f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank    protected String mDeviceId = null;
1289d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private String mDeviceType = "Android";
1291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private String mAuthString = null;
1301b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private String mCmdString = null;
131ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mHostAddress;
132ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mUserName;
133ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mPassword;
1341b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private boolean mSsl = true;
1355843b85178a359446f81770ed7734604a1b2fa7dMarc Blank    private boolean mTrustSsl = false;
136ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public ContentResolver mContentResolver;
1371b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private String[] mBindArguments = new String[2];
1381b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private ArrayList<String> mPingChangeList;
1391b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private HttpPost mPendingPost = null;
1401b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    // The ping time (in seconds)
1411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private int mPingHeartbeat = PING_STARTING_HEARTBEAT;
1421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    // The longest successful ping heartbeat
1431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private int mPingHighWaterMark = 0;
1441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    // Whether we've ever lowered the heartbeat
1451b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private boolean mPingHeartbeatDropped = false;
146ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
147ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public EasSyncService(Context _context, Mailbox _mailbox) {
148ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        super(_context, _mailbox);
149ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mContentResolver = _context.getContentResolver();
150ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv);
151ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
152e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        mTrustSsl = (ha.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES) != 0;
153ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
154ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
155ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private EasSyncService(String prefix) {
156ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        super(prefix);
157ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
158ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
159ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public EasSyncService() {
160ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        this("EAS Validation");
161ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
162ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
163ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
164ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void ping() {
1651b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        userLog("Alarm ping received!");
1661b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        synchronized(getSynchronizer()) {
1671b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (mPendingPost != null) {
1681b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                userLog("Aborting pending POST!");
1691b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                mPendingPost.abort();
1701b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
171ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
172ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
173ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
174ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
175ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void stop() {
176ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mStop = true;
177c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        synchronized(getSynchronizer()) {
178c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            if (mPendingPost != null) {
179c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                mPendingPost.abort();
180c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            }
181c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
1825c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank    }
183ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1841b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    /**
1851b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * Determine whether an HTTP code represents an authentication error
1861b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * @param code the HTTP code returned by the server
1871b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * @return whether or not the code represents an authentication error
1881b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     */
189f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank    protected boolean isAuthError(int code) {
1907ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        return ((code == HttpStatus.SC_UNAUTHORIZED) || (code == HttpStatus.SC_FORBIDDEN));
191368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
192368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
193fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank    @Override
194ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void validateAccount(String hostAddress, String userName, String password, int port,
195e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            boolean ssl, boolean trustCertificates, Context context) throws MessagingException {
196ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
1970a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank            userLog("Testing EAS: ", hostAddress, ", ", userName, ", ssl = ", ssl ? "1" : "0");
198ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            EasSyncService svc = new EasSyncService("%TestAccount%");
1999d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            svc.mContext = context;
200ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mHostAddress = hostAddress;
201ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mUserName = userName;
202ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mPassword = password;
203ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mSsl = ssl;
204e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            svc.mTrustSsl = trustCertificates;
2059d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            svc.mDeviceId = SyncManager.getDeviceId();
2068047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            HttpResponse resp = svc.sendHttpClientOptions();
2078047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            int code = resp.getStatusLine().getStatusCode();
2089d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            userLog("Validation (OPTIONS) response: " + code);
2091b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (code == HttpStatus.SC_OK) {
210ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // No exception means successful validation
211ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                userLog("Validation successful");
212ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return;
213ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
214368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            if (isAuthError(code)) {
215ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                userLog("Authentication failed");
216ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                throw new AuthenticationFailedException("Validation failed");
217ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
218ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // TODO Need to catch other kinds of errors (e.g. policy) For now, report the code.
2190a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("Validation failed, reporting I/O error: ", code);
220ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                throw new MessagingException(MessagingException.IOERROR);
221ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
222ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (IOException e) {
223e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            Throwable cause = e.getCause();
224e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            if (cause != null && cause instanceof CertificateException) {
225e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank                userLog("CertificateException caught: ", e.getMessage());
226e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank                throw new MessagingException(MessagingException.GENERAL_SECURITY);
227e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            }
228e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            userLog("IOException caught: ", e.getMessage());
229ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            throw new MessagingException(MessagingException.IOERROR);
230ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
231ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
232ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
233ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
23481d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank    private void doStatusCallback(long messageId, long attachmentId, int status) {
235d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        try {
236fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().loadAttachmentStatus(messageId, attachmentId, status, 0);
237fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e) {
238d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            // No danger if the client is no longer around
239d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        }
240d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank    }
241ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
24281d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank    private void doProgressCallback(long messageId, long attachmentId, int progress) {
243d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        try {
244fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().loadAttachmentStatus(messageId, attachmentId,
245fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                    EmailServiceStatus.IN_PROGRESS, progress);
246fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e) {
247d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            // No danger if the client is no longer around
248d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        }
249d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank    }
250d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
251842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank    public File createUniqueFileInternal(String dir, String filename) {
252842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        File directory;
253842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (dir == null) {
254842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            directory = mContext.getFilesDir();
255842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        } else {
256842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            directory = new File(dir);
257842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
258842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (!directory.exists()) {
259842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            directory.mkdirs();
260842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
261842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        File file = new File(directory, filename);
262842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (!file.exists()) {
263842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            return file;
264842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
265842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        // Get the extension of the file, if any.
266842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        int index = filename.lastIndexOf('.');
267842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        String name = filename;
268842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        String extension = "";
269842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (index != -1) {
270842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            name = filename.substring(0, index);
271842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            extension = filename.substring(index);
272842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
273842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        for (int i = 2; i < Integer.MAX_VALUE; i++) {
274842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            file = new File(directory, name + '-' + i + extension);
275842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            if (!file.exists()) {
276842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank                return file;
277842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            }
278842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
279842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        return null;
280842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank    }
281842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank
282d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank    /**
283d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * Loads an attachment, based on the PartRequest passed in.  The PartRequest is basically our
284d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * wrapper for Attachment
285d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * @param req the part (attachment) to be retrieved
286d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * @throws IOException
287d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     */
288842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank    protected void getAttachment(PartRequest req) throws IOException {
289d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        Attachment att = req.att;
290b0ce70f8d18dfe14fdd72be528d89eda1ba229feMarc Blank        Message msg = Message.restoreMessageWithId(mContext, att.mMessageKey);
29181d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank        doProgressCallback(msg.mId, att.mId, 0);
292ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        DefaultHttpClient client = new DefaultHttpClient();
293d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        String us = makeUriString("GetAttachment", "&AttachmentName=" + att.mLocation);
294ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HttpPost method = new HttpPost(URI.create(us));
295ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        method.setHeader("Authorization", mAuthString);
296ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
297ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HttpResponse res = client.execute(method);
298ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int status = res.getStatusLine().getStatusCode();
2991b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        if (status == HttpStatus.SC_OK) {
300ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            HttpEntity e = res.getEntity();
301ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int len = (int)e.getContentLength();
302ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String type = e.getContentType().getValue();
303ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            InputStream is = res.getEntity().getContent();
304bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler            File f = (req.destination != null)
305bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                    ? new File(req.destination)
306bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                    : createUniqueFileInternal(req.destination, att.mFileName);
307ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (f != null) {
308bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                // Ensure that the target directory exists
309bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                File destDir = f.getParentFile();
310bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                if (!destDir.exists()) {
311bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                    destDir.mkdirs();
312bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                }
313ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                FileOutputStream os = new FileOutputStream(f);
314ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                if (len > 0) {
315ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    try {
316ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        mPendingPartRequest = req;
317ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        byte[] bytes = new byte[CHUNK_SIZE];
318ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        int length = len;
319ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        while (len > 0) {
320ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            int n = (len > CHUNK_SIZE ? CHUNK_SIZE : len);
321ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            int read = is.read(bytes, 0, n);
322ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            os.write(bytes, 0, read);
323ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            len -= read;
324d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                            int pct = ((length - len) * 100 / length);
32581d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank                            doProgressCallback(msg.mId, att.mId, pct);
326ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
327ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    } finally {
328ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        mPendingPartRequest = null;
329ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
330ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
331ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                os.flush();
332ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                os.close();
333ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
334d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                // EmailProvider will throw an exception if we try to update an unsaved attachment
335d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                if (att.isSaved()) {
336bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                    String contentUriString = (req.contentUriString != null)
337bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                            ? req.contentUriString
338bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                            : "file://" + f.getAbsolutePath();
339d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    ContentValues cv = new ContentValues();
340bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                    cv.put(AttachmentColumns.CONTENT_URI, contentUriString);
341d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    cv.put(AttachmentColumns.MIME_TYPE, type);
342d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    att.update(mContext, cv);
34381d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank                    doStatusCallback(msg.mId, att.mId, EmailServiceStatus.SUCCESS);
344d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
345ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
346d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        } else {
34781d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank            doStatusCallback(msg.mId, att.mId, EmailServiceStatus.MESSAGE_NOT_FOUND);
348ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
349ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
350ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
351147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank    @SuppressWarnings("deprecation")
3529d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private String makeUriString(String cmd, String extra) throws IOException {
353ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank         // Cache the authentication string and the command string
354ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        String safeUserName = URLEncoder.encode(mUserName);
355ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (mAuthString == null) {
356ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String cs = mUserName + ':' + mPassword;
3571b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            mAuthString = "Basic " + new String(Base64.encodeBase64(cs.getBytes()));
358ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType="
359ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    + mDeviceType;
360ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
361e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        String us = (mSsl ? (mTrustSsl ? "httpts" : "https") : "http") + "://" + mHostAddress +
362ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            "/Microsoft-Server-ActiveSync";
363ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (cmd != null) {
364ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            us += "?Cmd=" + cmd + mCmdString;
365ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
366ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (extra != null) {
367ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            us += extra;
368ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
369ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return us;
370ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
371ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
3728047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    private void setHeaders(HttpRequestBase method) {
3738047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("Authorization", mAuthString);
3748047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("MS-ASProtocolVersion", mProtocolVersion);
3758047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("Connection", "keep-alive");
3768047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("User-Agent", mDeviceType + '/' + Eas.VERSION);
3778047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
378ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
379e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank    private ClientConnectionManager getClientConnectionManager() {
380e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        return SyncManager.getClientConnectionManager();
381e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank    }
382e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank
3838047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    private HttpClient getHttpClient(int timeout) {
3848047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpParams params = new BasicHttpParams();
3851b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HttpConnectionParams.setConnectionTimeout(params, 15*SECONDS);
3868047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpConnectionParams.setSoTimeout(params, timeout);
387e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        HttpConnectionParams.setSocketBufferSize(params, 8192);
388e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        HttpClient client = new DefaultHttpClient(getClientConnectionManager(), params);
389e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        return client;
3908047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
391ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
3928047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    protected HttpResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException {
3931b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return sendHttpClientPost(cmd, new ByteArrayEntity(bytes), COMMAND_TIMEOUT);
3949e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    }
3959e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank
3969e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity) throws IOException {
3971b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return sendHttpClientPost(cmd, entity, COMMAND_TIMEOUT);
3981b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    }
3991b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
4001b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    protected HttpResponse sendPing(byte[] bytes, int pingHeartbeat) throws IOException {
4011b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       int timeout = (pingHeartbeat+15)*SECONDS;
4021b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       Thread.currentThread().setName(mAccount.mDisplayName + ": Ping");
4031b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
4041b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       if (Eas.USER_LOG) {
4051b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank           userLog("Sending ping, timeout: " + pingHeartbeat +
4061b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                   "s, high water mark: " + mPingHighWaterMark + 's');
4071b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       }
4081b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
4091b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       SyncManager.runAsleep(mMailboxId, timeout);
4101b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       try {
4111b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank           HttpResponse res = sendHttpClientPost(PING_COMMAND, new ByteArrayEntity(bytes), timeout);
4121b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank           return res;
4131b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       } finally {
4141b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank           SyncManager.runAwake(mMailboxId);
4151b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       }
4161b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    }
4171b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
4181b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity, int timeout)
4191b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            throws IOException {
4201b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HttpClient client = getHttpClient(timeout);
42185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
42285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        // Split the mail sending commands
42385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String extra = null;
42485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        boolean msg = false;
42585f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        if (cmd.startsWith("SmartForward&") || cmd.startsWith("SmartReply&")) {
4265843b85178a359446f81770ed7734604a1b2fa7dMarc Blank            int cmdLength = cmd.indexOf('&');
42785f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            extra = cmd.substring(cmdLength);
42885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            cmd = cmd.substring(0, cmdLength);
42985f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
43085f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        } else if (cmd.startsWith("SendMail&")) {
43185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
43285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        }
43385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
43485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String us = makeUriString(cmd, extra);
4358047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpPost method = new HttpPost(URI.create(us));
43685f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        if (msg) {
4378047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "message/rfc822");
4388047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        } else {
4398047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml");
440ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
4418047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        setHeaders(method);
4429e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        method.setEntity(entity);
443c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        synchronized(getSynchronizer()) {
444c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            mPendingPost = method;
445c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
446c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        try {
447c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            return client.execute(method);
448c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        } finally {
449c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            synchronized(getSynchronizer()) {
450c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                mPendingPost = null;
451c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            }
452c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
4538047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
4548047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
4558047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    protected HttpResponse sendHttpClientOptions() throws IOException {
4568047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpClient client = getHttpClient(COMMAND_TIMEOUT);
4578047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        String us = makeUriString("OPTIONS", null);
4588047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpOptions method = new HttpOptions(URI.create(us));
4598047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        setHeaders(method);
4608047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        return client.execute(method);
461ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
462ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
463ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    String getTargetCollectionClassFromCursor(Cursor c) {
464ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
465ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (type == Mailbox.TYPE_CONTACTS) {
466ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Contacts";
467ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else if (type == Mailbox.TYPE_CALENDAR) {
468ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Calendar";
469ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else {
470ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Email";
471ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
472ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
473ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
474ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
475ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Performs FolderSync
476ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
477ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
478ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws EasParserException
479ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
480a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank    public void runAccountMailbox() throws IOException, EasParserException {
481fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        // Initialize exit status to success
482fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        mExitStatus = EmailServiceStatus.SUCCESS;
483ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
484fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            try {
485fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                SyncManager.callback()
486fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                    .syncMailboxListStatus(mAccount.mId, EmailServiceStatus.IN_PROGRESS, 0);
487fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            } catch (RemoteException e1) {
488fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                // Don't care if this fails
489fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
490fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
491ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (mAccount.mSyncKey == null) {
492ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                mAccount.mSyncKey = "0";
4931b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Account syncKey INIT to 0");
4949387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                ContentValues cv = new ContentValues();
4959387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
4969387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                mAccount.update(mContext, cv);
497ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
498ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
4999d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            boolean firstSync = mAccount.mSyncKey.equals("0");
5009d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            if (firstSync) {
5011b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Initial FolderSync");
5021b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            }
5031b275b9408d5b856e2482fa3951827489e9585ccMarc Blank
5044626078bf9d930b2007162db142b5961b38e2166Marc Blank            // When we first start up, change all mailboxes to push.
505ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ContentValues cv = new ContentValues();
506f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank            cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
507ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
5089d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING,
5099d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    new String[] {Long.toString(mAccount.mId)}) > 0) {
5109d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                SyncManager.kick("change ping boxes to push");
511ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
512ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
5130f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank            // Determine our protocol version, if we haven't already
5140f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank            if (mAccount.mProtocolVersion == null) {
5151b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Determine EAS protocol version");
5168047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                HttpResponse resp = sendHttpClientOptions();
5178047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                int code = resp.getStatusLine().getStatusCode();
5180a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("OPTIONS response: ", code);
5191b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                if (code == HttpStatus.SC_OK) {
520647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    Header header = resp.getFirstHeader("MS-ASProtocolCommands");
521647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    userLog(header.getValue());
522647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    header = resp.getFirstHeader("ms-asprotocolversions");
5238047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    String versions = header.getValue();
5248047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    if (versions != null) {
5258047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        if (versions.contains("12.0")) {
5268047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                            mProtocolVersion = "12.0";
527ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
5288047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
5298047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        mAccount.mProtocolVersion = mProtocolVersion;
5308047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        userLog(versions);
5310a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                        userLog("Using version ", mProtocolVersion);
532ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    } else {
5338047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        errorLog("No protocol versions in OPTIONS response");
534ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        throw new IOException();
535ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
5368047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                } else {
5378047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    errorLog("OPTIONS command failed; throwing IOException");
5388047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    throw new IOException();
5390f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
5400f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank            }
5411b275b9408d5b856e2482fa3951827489e9585ccMarc Blank
54277424af660458104b732bdcb718874b17d0cab3aMarc Blank            // Change all pushable boxes to push when we start the account mailbox
54377424af660458104b732bdcb718874b17d0cab3aMarc Blank            if (mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH) {
54477424af660458104b732bdcb718874b17d0cab3aMarc Blank                cv = new ContentValues();
545f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank                cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
54677424af660458104b732bdcb718874b17d0cab3aMarc Blank                if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
54777424af660458104b732bdcb718874b17d0cab3aMarc Blank                        SyncManager.WHERE_IN_ACCOUNT_AND_PUSHABLE,
54877424af660458104b732bdcb718874b17d0cab3aMarc Blank                        new String[] {Long.toString(mAccount.mId)}) > 0) {
54977424af660458104b732bdcb718874b17d0cab3aMarc Blank                    userLog("Push account; set pushable boxes to push...");
55077424af660458104b732bdcb718874b17d0cab3aMarc Blank                }
55177424af660458104b732bdcb718874b17d0cab3aMarc Blank            }
55277424af660458104b732bdcb718874b17d0cab3aMarc Blank
55377424af660458104b732bdcb718874b17d0cab3aMarc Blank            while (!mStop) {
5540a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                 userLog("Sending Account syncKey: ", mAccount.mSyncKey);
5558047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 Serializer s = new Serializer();
5568047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY)
55777424af660458104b732bdcb718874b17d0cab3aMarc Blank                     .text(mAccount.mSyncKey).end().end().done();
5588047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
5598047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 if (mStop) break;
5608047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 int code = resp.getStatusLine().getStatusCode();
5611b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                 if (code == HttpStatus.SC_OK) {
5628047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                     HttpEntity entity = resp.getEntity();
5638047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                     int len = (int)entity.getContentLength();
5648047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                     if (len > 0) {
5658047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                         InputStream is = entity.getContent();
5668047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                         // Returns true if we need to sync again
56748af7392c82262d17700e3fbdccf3a582809d449Marc Blank                         if (new FolderSyncParser(is, new AccountSyncAdapter(mMailbox, this))
56848af7392c82262d17700e3fbdccf3a582809d449Marc Blank                                 .parse()) {
5698047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                             continue;
5708047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                         }
5718047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                     }
5721b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                 } else if (isAuthError(code)) {
5731b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_LOGIN_FAILURE;
5740f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } else {
5750a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    userLog("FolderSync response error: ", code);
5760f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
577ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
5789d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                // Change all push/hold boxes to push
5799d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                cv = new ContentValues();
5809d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                cv.put(Mailbox.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
5819d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
5829d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX,
5839d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        new String[] {Long.toString(mAccount.mId)}) > 0) {
5849d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    userLog("Set push/hold boxes to push...");
5859d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                }
5869d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
5870f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                try {
5880f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    SyncManager.callback()
5899d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        .syncMailboxListStatus(mAccount.mId, mExitStatus, 0);
5900f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } catch (RemoteException e1) {
5910f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    // Don't care if this fails
5920f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
593fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
5940f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                // Wait for push notifications.
5950f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                String threadName = Thread.currentThread().getName();
5960f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                try {
5970f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    runPingLoop();
5980f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } catch (StaleFolderListException e) {
5990f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    // We break out if we get told about a stale folder list
6000f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    userLog("Ping interrupted; folder list requires sync...");
6010f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } finally {
6020f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    Thread.currentThread().setName(threadName);
6030f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
604ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
6050f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank         } catch (IOException e) {
606fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // We catch this here to send the folder sync status callback
607fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // A folder sync failed callback will get sent from run()
608fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            try {
6094d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                if (!mStop) {
6104d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                    SyncManager.callback()
6114d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                        .syncMailboxListStatus(mAccount.mId,
6124d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                                EmailServiceStatus.CONNECTION_ERROR, 0);
6134d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                }
614fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            } catch (RemoteException e1) {
615fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                // Don't care if this fails
616fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
61796293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank            throw e;
618ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
619ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
620ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
621c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    void pushFallback(long mailboxId) {
622c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
623c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        ContentValues cv = new ContentValues();
6244626078bf9d930b2007162db142b5961b38e2166Marc Blank        int mins = PING_FALLBACK_PIM;
625c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        if (mailbox.mType == Mailbox.TYPE_INBOX) {
6264626078bf9d930b2007162db142b5961b38e2166Marc Blank            mins = PING_FALLBACK_INBOX;
627c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
6284626078bf9d930b2007162db142b5961b38e2166Marc Blank        cv.put(Mailbox.SYNC_INTERVAL, mins);
6294626078bf9d930b2007162db142b5961b38e2166Marc Blank        mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
6304626078bf9d930b2007162db142b5961b38e2166Marc Blank                cv, null, null);
6314626078bf9d930b2007162db142b5961b38e2166Marc Blank        errorLog("*** PING ERROR LOOP: Backing off sync of " + mailbox.mDisplayName + " to " +
6324626078bf9d930b2007162db142b5961b38e2166Marc Blank                mins + " mins");
6334626078bf9d930b2007162db142b5961b38e2166Marc Blank        SyncManager.kick("push fallback");
634368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
635368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
6361b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    /**
6371b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * Check the boxes reporting changes to see if there really were any...
6381b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * We do this because bugs in various Exchange servers can put us into a looping
6391b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * behavior by continually reporting changes in a mailbox, even when there aren't any.
6401b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     *
6411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * This behavior is seemingly random, and therefore we must code defensively by
6421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * backing off of push behavior when it is detected.
6431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     *
6441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * One known cause, on certain Exchange 2003 servers, is acknowledged by Microsoft, and the
6451b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * server hotfix for this case can be found at http://support.microsoft.com/kb/923282
6461b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     */
6471b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
6481b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    void checkPingErrors(HashMap<String, Integer> errorMap) {
6491b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        mBindArguments[0] = Long.toString(mAccount.mId);
6501b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        for (String serverId: mPingChangeList) {
6511b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // Find the id and sync status for each box
6521b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            mBindArguments[1] = serverId;
6531b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, SYNC_STATUS_PROJECTION,
6541b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
6551b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            try {
6561b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                if (c.moveToFirst()) {
6571b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    String status = c.getString(1);
6581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    int type = SyncManager.getStatusType(status);
6591b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // This check should always be true...
6601b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    if (type == SyncManager.SYNC_PING) {
6611b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        int changeCount = SyncManager.getStatusChangeCount(status);
6621b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        if (changeCount > 0) {
6631b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            errorMap.remove(serverId);
6641b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        } else if (changeCount == 0) {
6651b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            // This means that a ping reported changes in error; we keep a count
6661b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            // of consecutive errors of this kind
6671b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            Integer failures = errorMap.get(serverId);
6681b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            if (failures == null) {
6691b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                errorMap.put(serverId, 1);
6701b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            } else if (failures > MAX_PING_FAILURES) {
6711b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                // We'll back off of push for this box
6721b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                pushFallback(c.getLong(0));
6731b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                return;
6741b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            } else {
6751b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                errorMap.put(serverId, failures + 1);
6761b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            }
6771b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        }
6781b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    }
6791b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                }
6801b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            } finally {
6811b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                c.close();
6821b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
6831b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        }
6841b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    }
6851b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
686ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    void runPingLoop() throws IOException, StaleFolderListException {
6871b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        int pingHeartbeat = mPingHeartbeat;
68874c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank        userLog("runPingLoop");
689ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Do push for all sync services here
690c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        long endTime = System.currentTimeMillis() + (30*MINUTES);
6911b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HashMap<String, Integer> pingErrorMap = new HashMap<String, Integer>();
692ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
6937672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank        int pingWaitCount = 0;
6947672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank
6957ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        while ((System.currentTimeMillis() < endTime) && !mStop) {
69674c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank            userLog("runPingLoop, top of loop, pingWaitCount = " + pingWaitCount);
697ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Count of pushable mailboxes
698ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int pushCount = 0;
699ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Count of mailboxes that can be pushed right now
700ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int canPushCount = 0;
7018a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            // Count of uninitialized boxes
7028a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            int uninitCount = 0;
7038a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank
7047c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            Serializer s = new Serializer();
705ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
70622bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank                    MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId +
70722bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank                    AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null, null);
708ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            try {
709ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Loop through our pushed boxes seeing what is available to push
710ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                while (c.moveToNext()) {
711ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    pushCount++;
712ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // Two requirements for push:
713ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // 1) SyncManager tells us the mailbox is syncable (not running, not stopped)
714ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // 2) The syncKey isn't "0" (i.e. it's synced at least once)
715ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank                    long mailboxId = c.getLong(Mailbox.CONTENT_ID_COLUMN);
71677424af660458104b732bdcb718874b17d0cab3aMarc Blank                    int pingStatus = SyncManager.pingStatus(mailboxId);
71777424af660458104b732bdcb718874b17d0cab3aMarc Blank                    String mailboxName = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN);
71877424af660458104b732bdcb718874b17d0cab3aMarc Blank                    if (pingStatus == SyncManager.PING_STATUS_OK) {
719a05c26d8d2cce3faa152096cb8116fce375c6d81Marc Blank
720ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String syncKey = c.getString(Mailbox.CONTENT_SYNC_KEY_COLUMN);
7217ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                        if ((syncKey == null) || syncKey.equals("0")) {
722a05c26d8d2cce3faa152096cb8116fce375c6d81Marc Blank                            // We can't push until the initial sync is done
72377424af660458104b732bdcb718874b17d0cab3aMarc Blank                            pushCount--;
7248a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                            uninitCount++;
725ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            continue;
726ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
727ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank
728ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        if (canPushCount++ == 0) {
729ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            // Initialize the Ping command
73096293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank                            s.start(Tags.PING_PING)
73105381a6662f28609e8005023515abb82af00e1d4Marc Blank                                .data(Tags.PING_HEARTBEAT_INTERVAL,
7321b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                        Integer.toString(pingHeartbeat))
7337c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                                .start(Tags.PING_FOLDERS);
734ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
7351b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
736ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String folderClass = getTargetCollectionClassFromCursor(c);
7377c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        s.start(Tags.PING_FOLDER)
7387c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .data(Tags.PING_ID, c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN))
7397c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .data(Tags.PING_CLASS, folderClass)
7407c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .end();
7410a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                        userLog("Ping ready for: ", folderClass, ", ", mailboxName, " (",
7420a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                                c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN), ")");
7437ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                    } else if ((pingStatus == SyncManager.PING_STATUS_RUNNING) ||
7447ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                            (pingStatus == SyncManager.PING_STATUS_WAITING)) {
7450a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                        userLog(mailboxName, " not ready for ping");
74677424af660458104b732bdcb718874b17d0cab3aMarc Blank                    } else if (pingStatus == SyncManager.PING_STATUS_UNABLE) {
74777424af660458104b732bdcb718874b17d0cab3aMarc Blank                        pushCount--;
7480a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                        userLog(mailboxName, " in error state; ignore");
74977424af660458104b732bdcb718874b17d0cab3aMarc Blank                        continue;
750ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
751ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
752ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } finally {
753ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                c.close();
754ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
755ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
7567672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            // If we've waited 10 seconds or more, just ping with whatever boxes are ready
7577672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            // But use a shorter than normal heartbeat
7587672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            boolean forcePing = (pingWaitCount > 9);
7597672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank
7607672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            if ((canPushCount > 0) && ((canPushCount == pushCount) || forcePing)) {
76174c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                userLog("runPingLoop, about to send ping, setting pingWaitCount = 0");
7627672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                // If all pingable boxes are ready for push, send Ping to the server
7637c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.end().end().done();
7647672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                pingWaitCount = 0;
7657c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank
7661b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                // If we've been stopped, this is a good time to return
7674d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                if (mStop) return;
768368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
76974c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                long pingTime = SystemClock.elapsedRealtime();
7701b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                try {
7711b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // Send the ping, wrapped by appropriate timeout/alarm
7727672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                    if (forcePing) {
7737672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                        userLog("Forcing ping after waiting for all boxes to be ready");
7747672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                    }
7757672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                    HttpResponse res =
7767672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                        sendPing(s.toByteArray(), forcePing ? PING_FORCE_HEARTBEAT : pingHeartbeat);
777c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
7781b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    int code = res.getStatusLine().getStatusCode();
7791b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    userLog("Ping response: ", code);
780368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
7811b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // Return immediately if we've been asked to stop during the ping
7821b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    if (mStop) {
7831b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        userLog("Stopping pingLoop");
7841b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        return;
7851b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    }
7861b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
7871b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    if (code == HttpStatus.SC_OK) {
7881b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        HttpEntity e = res.getEntity();
7891b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        int len = (int)e.getContentLength();
7901b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        InputStream is = res.getEntity().getContent();
7911b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        if (len > 0) {
7921b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            int status = parsePingResult(is, mContentResolver);
7937672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                            // If our ping completed (status = 1), and we weren't forced and we're
7947672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                            // not at the maximum, try increasing timeout by two minutes
7957672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                            if (status == PROTOCOL_PING_STATUS_COMPLETED && !forcePing) {
7967672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                if (pingHeartbeat > mPingHighWaterMark) {
7977672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    mPingHighWaterMark = pingHeartbeat;
7987672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    userLog("Setting high water mark at: ", mPingHighWaterMark);
7997672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                }
8007672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                if ((pingHeartbeat < PING_MAX_HEARTBEAT) &&
8017672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                        !mPingHeartbeatDropped) {
8027672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    pingHeartbeat += PING_HEARTBEAT_INCREMENT;
8037672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    if (pingHeartbeat > PING_MAX_HEARTBEAT) {
8047672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                        pingHeartbeat = PING_MAX_HEARTBEAT;
8057672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    }
8067672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    userLog("Increasing ping heartbeat to ", pingHeartbeat, "s");
8071b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                }
8081b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            } else if (status == PROTOCOL_PING_STATUS_CHANGES_FOUND) {
8091b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                checkPingErrors(pingErrorMap);
8101b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            }
8111b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        } else {
8121b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            userLog("Ping returned empty result; throwing IOException");
8131b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            throw new IOException();
8141b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        }
8151b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    } else if (isAuthError(code)) {
8161b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        mExitStatus = EXIT_LOGIN_FAILURE;
8171b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        userLog("Authorization error during Ping: ", code);
818ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        throw new IOException();
819ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
8201b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                } catch (IOException e) {
8211b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    String msg = e.getMessage();
8221b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // If we get the exception that is indicative of a NAT timeout and if we
8231b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // haven't yet "fixed" the timeout, back off by two minutes and "fix" it
8247ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                    if ((msg != null) && msg.contains("reset by peer")) {
8257ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                        if ((pingHeartbeat > PING_MIN_HEARTBEAT) &&
8267ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                                (pingHeartbeat > mPingHighWaterMark)) {
8271b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            pingHeartbeat -= PING_HEARTBEAT_INCREMENT;
8281b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            mPingHeartbeatDropped = true;
8291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            if (pingHeartbeat < PING_MIN_HEARTBEAT) {
8301b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                pingHeartbeat = PING_MIN_HEARTBEAT;
8311b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            }
8321b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            userLog("Decreased ping heartbeat to ", pingHeartbeat, "s");
83374c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                        } else if ((SystemClock.elapsedRealtime() - pingTime) < 2000) {
83474c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                            userLog("NAT type IOException < 2 seconds; throwing IOException");
83574c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                            throw e;
83674c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                        } else {
83774c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                            userLog("NAT type IOException > 2 seconds?");
8381b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        }
8391b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    } else {
8401b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        userLog("IOException detected in runPingLoop: " +
8411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                ((msg != null) ? msg : "no message"));
8421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        throw e;
8431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    }
844ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
845ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else if (pushCount > 0) {
8461ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank                // If we want to Ping, but can't just yet, wait a little bit
8471ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank                // TODO Change sleep to wait and use notify from SyncManager when a sync ends
8481ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank                sleep(1*SECONDS);
8497672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                pingWaitCount++;
85074c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                userLog("pingLoop waited for: ", (pushCount - canPushCount), " box(es), count = "
85174c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                        + pingWaitCount);
8528a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            } else if (uninitCount > 0) {
8538a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // In this case, we're doing an initial sync of at least one mailbox.  Since this
8548a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // is typically a one-time case, I'm ok with trying again every 10 seconds until
8558a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // we're in one of the other possible states.
8568a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                userLog("pingLoop waiting for ", uninitCount, " box(es) to do an initial sync");
8578a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                sleep(10*SECONDS);
858ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
859c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                // We've got nothing to do, so we'll check again in 30 minutes at which time
8601b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                // we'll update the folder list.  Let the device sleep in the meantime...
8611b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                SyncManager.runAsleep(mMailboxId, (30*MINUTES)+(15*SECONDS));
862c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                sleep(30*MINUTES);
86377424af660458104b732bdcb718874b17d0cab3aMarc Blank                SyncManager.runAwake(mMailboxId);
864ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
865ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
866ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
867ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
868ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    void sleep(long ms) {
869ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
870ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Thread.sleep(ms);
871ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (InterruptedException e) {
872ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Doesn't matter whether we stop early; it's the thought that counts
873ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
874ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
875ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
8768047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    private int parsePingResult(InputStream is, ContentResolver cr)
877ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        throws IOException, StaleFolderListException {
8788047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        PingParser pp = new PingParser(is, this);
879ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (pp.parse()) {
880ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // True indicates some mailboxes need syncing...
881ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // syncList has the serverId's of the mailboxes...
882ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mBindArguments[0] = Long.toString(mAccount.mId);
8831b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            mPingChangeList = pp.getSyncList();
8841b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            for (String serverId: mPingChangeList) {
885d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                mBindArguments[1] = serverId;
886ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
887ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
888ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                try {
889ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (c.moveToFirst()) {
8904d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                        SyncManager.startManualSync(c.getLong(Mailbox.CONTENT_ID_COLUMN),
891ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank                                SyncManager.SYNC_PING, null);
892ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
893ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } finally {
894ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    c.close();
895ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
896ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
897ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
8981b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return pp.getSyncStatus();
899ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
900ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
901368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    private String getFilterType() {
902368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        String filter = Eas.FILTER_1_WEEK;
903368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        switch (mAccount.mSyncLookback) {
904368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_DAY: {
905368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_DAY;
906368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
907368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
908368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_3_DAYS: {
909368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_3_DAYS;
910368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
911368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
912368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_WEEK: {
913368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_WEEK;
914368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
915368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
916368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_2_WEEKS: {
917368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_2_WEEKS;
918368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
919368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
920368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_MONTH: {
921368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_MONTH;
922368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
923368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
924368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_ALL: {
925368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_ALL;
926368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
927368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
928368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        }
929368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        return filter;
930368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
931368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
932ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
933ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Common code to sync E+PIM data
934ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
935ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @param target, an EasMailbox, EasContacts, or EasCalendar object
936ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
9377c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank    public void sync(AbstractSyncAdapter target) throws IOException {
938ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        Mailbox mailbox = target.mMailbox;
939ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
940ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        boolean moreAvailable = true;
941ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        while (!mStop && moreAvailable) {
9421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // If we have no connectivity, just exit cleanly.  SyncManager will start us up again
9431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // when connectivity has returned
9441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (!hasConnectivity()) {
9451b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                userLog("No connectivity in sync; finishing sync");
9461b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                mExitStatus = EXIT_DONE;
9471b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                return;
9481b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
949ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
950d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            while (true) {
951d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                PartRequest req = null;
952d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                synchronized (mPartRequests) {
953d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    if (mPartRequests.isEmpty()) {
954d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        break;
955d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    } else {
956d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        req = mPartRequests.get(0);
957d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    }
958d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
959842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank                getAttachment(req);
960d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                synchronized(mPartRequests) {
961d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    mPartRequests.remove(req);
962d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
963d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            }
964d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
9657c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            Serializer s = new Serializer();
966ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String className = target.getCollectionName();
96748af7392c82262d17700e3fbdccf3a582809d449Marc Blank            String syncKey = target.getSyncKey();
96848af7392c82262d17700e3fbdccf3a582809d449Marc Blank            userLog("Sending ", className, " syncKey: ", syncKey);
9697c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.start(Tags.SYNC_SYNC)
9707c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .start(Tags.SYNC_COLLECTIONS)
9717c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .start(Tags.SYNC_COLLECTION)
9727c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .data(Tags.SYNC_CLASS, className)
97348af7392c82262d17700e3fbdccf3a582809d449Marc Blank                .data(Tags.SYNC_SYNC_KEY, syncKey)
9747c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId)
9757c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .tag(Tags.SYNC_DELETES_AS_MOVES);
976ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
977ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // EAS doesn't like GetChanges if the syncKey is "0"; not documented
97848af7392c82262d17700e3fbdccf3a582809d449Marc Blank            if (!syncKey.equals("0")) {
9797c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.tag(Tags.SYNC_GET_CHANGES);
980ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
9811b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            s.data(Tags.SYNC_WINDOW_SIZE,
9821b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                    className.equals("Email") ? EMAIL_WINDOW_SIZE : PIM_WINDOW_SIZE);
983ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            boolean options = false;
984ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (!className.equals("Contacts")) {
985ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Set the lookback appropriately (EAS calls this a "filter")
986368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                s.start(Tags.SYNC_OPTIONS).data(Tags.SYNC_FILTER_TYPE, getFilterType());
98700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                options = true;
988ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
98900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            if (mProtocolVersionDouble >= 12.0) {
990ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                if (!options) {
991ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    options = true;
9927c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    s.start(Tags.SYNC_OPTIONS);
993ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
9947c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.start(Tags.BASE_BODY_PREFERENCE)
995368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                    // HTML for email; plain text for everything else
996c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                    .data(Tags.BASE_TYPE, (className.equals("Email") ? Eas.BODY_PREFERENCE_HTML
997368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                            : Eas.BODY_PREFERENCE_TEXT))
9987c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    .end();
999ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1000ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (options) {
10017c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.end();
1002ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1003ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1004ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Send our changes up to the server
100548af7392c82262d17700e3fbdccf3a582809d449Marc Blank            target.sendLocalChanges(s);
1006ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
10077c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.end().end().end().done();
10088047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            HttpResponse resp = sendHttpClientPost("Sync", s.toByteArray());
10098047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            int code = resp.getStatusLine().getStatusCode();
10101b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (code == HttpStatus.SC_OK) {
10118047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 InputStream is = resp.getEntity().getContent();
1012ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                if (is != null) {
101348af7392c82262d17700e3fbdccf3a582809d449Marc Blank                    moreAvailable = target.parse(is);
101448af7392c82262d17700e3fbdccf3a582809d449Marc Blank                    target.cleanup();
1015ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1016ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
10170a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("Sync response error: ", code);
1018368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                if (isAuthError(code)) {
10191b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_LOGIN_FAILURE;
10201b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                } else {
10211b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_IO_ERROR;
1022ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1023ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return;
1024ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1025ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
10261b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        mExitStatus = EXIT_DONE;
1027ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1028ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
10297ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    protected void setupService() {
10307ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        // Make sure account and mailbox are always the latest from the database
10317ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
10327ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId);
10337ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank
1034ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mThread = Thread.currentThread();
10357310cbacf2cf614c949330faff3882082054c120Marc Blank        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
1036ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        TAG = mThread.getName();
10379d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
1038ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
1039ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mHostAddress = ha.mAddress;
1040ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mUserName = ha.mLogin;
1041ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mPassword = ha.mPassword;
10427ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    }
10437ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank
10447ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    /* (non-Javadoc)
10457ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank     * @see java.lang.Runnable#run()
10467ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank     */
10477ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    public void run() {
10487ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        setupService();
1049ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1050fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        try {
1051fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().syncMailboxStatus(mMailboxId, EmailServiceStatus.IN_PROGRESS, 0);
1052fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e1) {
1053fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // Don't care if this fails
1054fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        }
1055fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
1056a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank        // Whether or not we're the account mailbox
1057ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
10589d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            mDeviceId = SyncManager.getDeviceId();
10597ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank            if ((mMailbox == null) || (mAccount == null)) {
1060147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank                return;
10617c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            } else if (mMailbox.mType == Mailbox.TYPE_EAS_ACCOUNT_MAILBOX) {
1062a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank                runAccountMailbox();
1063ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
10647c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                AbstractSyncAdapter target;
106500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mProtocolVersion = mAccount.mProtocolVersion;
106600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
10677cf921b830554e52f88a45ca4a290b17d2a1b146Marc Blank                if (mMailbox.mType == Mailbox.TYPE_CONTACTS) {
10687c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    target = new ContactsSyncAdapter(mMailbox, this);
10697cf921b830554e52f88a45ca4a290b17d2a1b146Marc Blank                } else {
10707c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    target = new EmailSyncAdapter(mMailbox, this);
1071ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1072ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // We loop here because someone might have put a request in while we were syncing
1073ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // and we've missed that opportunity...
1074ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                do {
1075ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (mRequestTime != 0) {
1076ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        userLog("Looping for user request...");
1077ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        mRequestTime = 0;
1078ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
1079ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    sync(target);
1080ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } while (mRequestTime != 0);
1081ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1082ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (IOException e) {
1083f9423affa52661c2f552df35f0b9ddeecd8fa8feMarc Blank            String message = e.getMessage();
10841431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank            userLog("Caught IOException: ", ((message == null) ? "No message" : message));
1085ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mExitStatus = EXIT_IO_ERROR;
1086ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (Exception e) {
10871431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank            userLog("Uncaught exception in EasSyncService", e);
1088ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
10894d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank            if (!mStop) {
10900a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog(mMailbox.mDisplayName, ": sync finished");
10914d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                SyncManager.done(this);
10924d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                // If this is the account mailbox, wake up SyncManager
10934d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                // Because this box has a "push" interval, it will be restarted immediately
10944d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                // which will cause the folder list to be reloaded...
10955c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                int status;
10965c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                switch (mExitStatus) {
10975c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_IO_ERROR:
10985c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.CONNECTION_ERROR;
10995c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
11005c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_DONE:
11015c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.SUCCESS;
11025c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
11035c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_LOGIN_FAILURE:
11045c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.LOGIN_FAILED;
11055c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
11065c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    default:
11075c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.REMOTE_EXCEPTION;
110877424af660458104b732bdcb718874b17d0cab3aMarc Blank                        errorLog("Sync ended due to an exception.");
11095c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
11105c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                }
1111c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
11124d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                try {
11134d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                    SyncManager.callback().syncMailboxStatus(mMailboxId, status, 0);
11144d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                } catch (RemoteException e1) {
11154d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                    // Don't care if this fails
11164d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                }
11175c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank
11185c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                // Save the sync time and status
11195c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                ContentValues cv = new ContentValues();
11205c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
11215c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                String s = "S" + mSyncReason + ':' + status + ':' + mChangeCount;
11225c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                cv.put(Mailbox.SYNC_STATUS, s);
11235c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                mContentResolver.update(ContentUris
11245c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        .withAppendedId(Mailbox.CONTENT_URI, mMailboxId), cv, null, null);
11254d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank            } else {
11260a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog(mMailbox.mDisplayName, ": stopped thread finished.");
1127fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
11289d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
1129c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            // Make sure SyncManager knows about this
1130c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            SyncManager.kick("sync finished");
11319d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank       }
1132ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1133ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
1134