EasSyncService.java revision 27cf341571fac3d8dbe866f503c34fc31e02bf85
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
120252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank    static private final int MAX_PING_FAILURES = 1;
121c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    static private final int PING_FALLBACK_INBOX = 5;
12227cf341571fac3d8dbe866f503c34fc31e02bf85Marc Blank    static private final int PING_FALLBACK_PIM = 25;
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
4002033dfc4e2e6e352b34565112266084d72c443f1Marc Blank    protected HttpResponse sendPing(byte[] bytes, int heartbeat) throws IOException {
4011b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       Thread.currentThread().setName(mAccount.mDisplayName + ": Ping");
4021b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       if (Eas.USER_LOG) {
4032033dfc4e2e6e352b34565112266084d72c443f1Marc Blank           userLog("Send ping, timeout: " + heartbeat + "s, high: " + mPingHighWaterMark + 's');
4041b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       }
4052033dfc4e2e6e352b34565112266084d72c443f1Marc Blank       return sendHttpClientPost(PING_COMMAND, new ByteArrayEntity(bytes), (heartbeat+5)*SECONDS);
4061b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    }
4071b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
4081b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity, int timeout)
4091b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            throws IOException {
4101b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HttpClient client = getHttpClient(timeout);
41185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
41285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        // Split the mail sending commands
41385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String extra = null;
41485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        boolean msg = false;
41585f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        if (cmd.startsWith("SmartForward&") || cmd.startsWith("SmartReply&")) {
4165843b85178a359446f81770ed7734604a1b2fa7dMarc Blank            int cmdLength = cmd.indexOf('&');
41785f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            extra = cmd.substring(cmdLength);
41885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            cmd = cmd.substring(0, cmdLength);
41985f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
42085f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        } else if (cmd.startsWith("SendMail&")) {
42185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
42285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        }
42385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
42485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String us = makeUriString(cmd, extra);
4258047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpPost method = new HttpPost(URI.create(us));
42685f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        if (msg) {
4278047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "message/rfc822");
4288047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        } else {
4298047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml");
430ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
4318047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        setHeaders(method);
4329e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        method.setEntity(entity);
433c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        synchronized(getSynchronizer()) {
434c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            mPendingPost = method;
4352033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            SyncManager.runAsleep(mMailboxId, (timeout+10)*SECONDS);
436c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
437c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        try {
438c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            return client.execute(method);
439c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        } finally {
440c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            synchronized(getSynchronizer()) {
4412033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                SyncManager.runAwake(mMailboxId);
442c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                mPendingPost = null;
443c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            }
444c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
4458047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
4468047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
4478047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    protected HttpResponse sendHttpClientOptions() throws IOException {
4488047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpClient client = getHttpClient(COMMAND_TIMEOUT);
4498047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        String us = makeUriString("OPTIONS", null);
4508047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpOptions method = new HttpOptions(URI.create(us));
4518047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        setHeaders(method);
4528047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        return client.execute(method);
453ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
454ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
455ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    String getTargetCollectionClassFromCursor(Cursor c) {
456ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
457ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (type == Mailbox.TYPE_CONTACTS) {
458ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Contacts";
459ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else if (type == Mailbox.TYPE_CALENDAR) {
460ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Calendar";
461ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else {
462ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Email";
463ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
464ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
465ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
466ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
467ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Performs FolderSync
468ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
469ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
470ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws EasParserException
471ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
472a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank    public void runAccountMailbox() throws IOException, EasParserException {
473fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        // Initialize exit status to success
474fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        mExitStatus = EmailServiceStatus.SUCCESS;
475ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
476fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            try {
477fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                SyncManager.callback()
478fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                    .syncMailboxListStatus(mAccount.mId, EmailServiceStatus.IN_PROGRESS, 0);
479fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            } catch (RemoteException e1) {
480fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                // Don't care if this fails
481fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
482fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
483ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (mAccount.mSyncKey == null) {
484ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                mAccount.mSyncKey = "0";
4851b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Account syncKey INIT to 0");
4869387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                ContentValues cv = new ContentValues();
4879387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
4889387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                mAccount.update(mContext, cv);
489ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
490ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
4919d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            boolean firstSync = mAccount.mSyncKey.equals("0");
4929d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            if (firstSync) {
4931b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Initial FolderSync");
4941b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            }
4951b275b9408d5b856e2482fa3951827489e9585ccMarc Blank
4964626078bf9d930b2007162db142b5961b38e2166Marc Blank            // When we first start up, change all mailboxes to push.
497ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ContentValues cv = new ContentValues();
498f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank            cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
499ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
5009d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING,
5019d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    new String[] {Long.toString(mAccount.mId)}) > 0) {
5029d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                SyncManager.kick("change ping boxes to push");
503ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
504ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
5050f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank            // Determine our protocol version, if we haven't already
5060f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank            if (mAccount.mProtocolVersion == null) {
5071b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Determine EAS protocol version");
5088047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                HttpResponse resp = sendHttpClientOptions();
5098047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                int code = resp.getStatusLine().getStatusCode();
5100a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("OPTIONS response: ", code);
5111b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                if (code == HttpStatus.SC_OK) {
512647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    Header header = resp.getFirstHeader("MS-ASProtocolCommands");
513647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    userLog(header.getValue());
514647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    header = resp.getFirstHeader("ms-asprotocolversions");
5158047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    String versions = header.getValue();
5168047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    if (versions != null) {
5178047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        if (versions.contains("12.0")) {
5188047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                            mProtocolVersion = "12.0";
519ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
5208047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
5218047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        mAccount.mProtocolVersion = mProtocolVersion;
5228047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        userLog(versions);
5230a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                        userLog("Using version ", mProtocolVersion);
524ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    } else {
5258047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                        errorLog("No protocol versions in OPTIONS response");
526ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        throw new IOException();
527ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
5288047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                } else {
5298047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    errorLog("OPTIONS command failed; throwing IOException");
5308047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    throw new IOException();
5310f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
5320f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank            }
5331b275b9408d5b856e2482fa3951827489e9585ccMarc Blank
53477424af660458104b732bdcb718874b17d0cab3aMarc Blank            // Change all pushable boxes to push when we start the account mailbox
53577424af660458104b732bdcb718874b17d0cab3aMarc Blank            if (mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH) {
53677424af660458104b732bdcb718874b17d0cab3aMarc Blank                cv = new ContentValues();
537f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank                cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
53877424af660458104b732bdcb718874b17d0cab3aMarc Blank                if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
53977424af660458104b732bdcb718874b17d0cab3aMarc Blank                        SyncManager.WHERE_IN_ACCOUNT_AND_PUSHABLE,
54077424af660458104b732bdcb718874b17d0cab3aMarc Blank                        new String[] {Long.toString(mAccount.mId)}) > 0) {
54177424af660458104b732bdcb718874b17d0cab3aMarc Blank                    userLog("Push account; set pushable boxes to push...");
54277424af660458104b732bdcb718874b17d0cab3aMarc Blank                }
54377424af660458104b732bdcb718874b17d0cab3aMarc Blank            }
54477424af660458104b732bdcb718874b17d0cab3aMarc Blank
54577424af660458104b732bdcb718874b17d0cab3aMarc Blank            while (!mStop) {
5460a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                 userLog("Sending Account syncKey: ", mAccount.mSyncKey);
5478047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 Serializer s = new Serializer();
5488047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY)
54977424af660458104b732bdcb718874b17d0cab3aMarc Blank                     .text(mAccount.mSyncKey).end().end().done();
5508047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
5518047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 if (mStop) break;
5528047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 int code = resp.getStatusLine().getStatusCode();
5531b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                 if (code == HttpStatus.SC_OK) {
5548047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                     HttpEntity entity = resp.getEntity();
5558047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                     int len = (int)entity.getContentLength();
5568047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                     if (len > 0) {
5578047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                         InputStream is = entity.getContent();
5588047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                         // Returns true if we need to sync again
55948af7392c82262d17700e3fbdccf3a582809d449Marc Blank                         if (new FolderSyncParser(is, new AccountSyncAdapter(mMailbox, this))
56048af7392c82262d17700e3fbdccf3a582809d449Marc Blank                                 .parse()) {
5618047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                             continue;
5628047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                         }
5638047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                     }
5641b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                 } else if (isAuthError(code)) {
5651b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_LOGIN_FAILURE;
5660f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } else {
5670a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    userLog("FolderSync response error: ", code);
5680f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
569ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
5709d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                // Change all push/hold boxes to push
5719d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                cv = new ContentValues();
5729d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                cv.put(Mailbox.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
5739d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
5749d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX,
5759d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        new String[] {Long.toString(mAccount.mId)}) > 0) {
5769d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    userLog("Set push/hold boxes to push...");
5779d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                }
5789d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
5790f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                try {
5800f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    SyncManager.callback()
5819d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        .syncMailboxListStatus(mAccount.mId, mExitStatus, 0);
5820f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } catch (RemoteException e1) {
5830f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    // Don't care if this fails
5840f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
585fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
5860f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                // Wait for push notifications.
5870f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                String threadName = Thread.currentThread().getName();
5880f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                try {
5890f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    runPingLoop();
5900f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } catch (StaleFolderListException e) {
5910f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    // We break out if we get told about a stale folder list
5920f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    userLog("Ping interrupted; folder list requires sync...");
5930f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } finally {
5940f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    Thread.currentThread().setName(threadName);
5950f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
596ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
5970f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank         } catch (IOException e) {
598fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // We catch this here to send the folder sync status callback
599fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // A folder sync failed callback will get sent from run()
600fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            try {
6014d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                if (!mStop) {
6024d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                    SyncManager.callback()
6034d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                        .syncMailboxListStatus(mAccount.mId,
6044d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                                EmailServiceStatus.CONNECTION_ERROR, 0);
6054d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                }
606fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            } catch (RemoteException e1) {
607fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                // Don't care if this fails
608fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
60996293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank            throw e;
610ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
611ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
612ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
613c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    void pushFallback(long mailboxId) {
614c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
615c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        ContentValues cv = new ContentValues();
6164626078bf9d930b2007162db142b5961b38e2166Marc Blank        int mins = PING_FALLBACK_PIM;
617c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        if (mailbox.mType == Mailbox.TYPE_INBOX) {
6184626078bf9d930b2007162db142b5961b38e2166Marc Blank            mins = PING_FALLBACK_INBOX;
619c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
6204626078bf9d930b2007162db142b5961b38e2166Marc Blank        cv.put(Mailbox.SYNC_INTERVAL, mins);
6214626078bf9d930b2007162db142b5961b38e2166Marc Blank        mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
6224626078bf9d930b2007162db142b5961b38e2166Marc Blank                cv, null, null);
62327cf341571fac3d8dbe866f503c34fc31e02bf85Marc Blank        errorLog("*** PING ERROR LOOP: Set " + mailbox.mDisplayName + " to " + mins + " min sync");
6244626078bf9d930b2007162db142b5961b38e2166Marc Blank        SyncManager.kick("push fallback");
625368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
626368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
6271b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    /**
6281b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * Check the boxes reporting changes to see if there really were any...
6291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * We do this because bugs in various Exchange servers can put us into a looping
6301b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * behavior by continually reporting changes in a mailbox, even when there aren't any.
6311b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     *
6321b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * This behavior is seemingly random, and therefore we must code defensively by
6331b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * backing off of push behavior when it is detected.
6341b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     *
6351b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * One known cause, on certain Exchange 2003 servers, is acknowledged by Microsoft, and the
6361b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * server hotfix for this case can be found at http://support.microsoft.com/kb/923282
6371b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     */
6381b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
6391b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    void checkPingErrors(HashMap<String, Integer> errorMap) {
6401b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        mBindArguments[0] = Long.toString(mAccount.mId);
6411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        for (String serverId: mPingChangeList) {
6421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // Find the id and sync status for each box
6431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            mBindArguments[1] = serverId;
6441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, SYNC_STATUS_PROJECTION,
6451b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
6461b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            try {
6471b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                if (c.moveToFirst()) {
6481b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    String status = c.getString(1);
6491b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    int type = SyncManager.getStatusType(status);
6501b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // This check should always be true...
6511b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    if (type == SyncManager.SYNC_PING) {
6521b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        int changeCount = SyncManager.getStatusChangeCount(status);
6531b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        if (changeCount > 0) {
6541b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            errorMap.remove(serverId);
6551b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        } else if (changeCount == 0) {
6561b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            // This means that a ping reported changes in error; we keep a count
6571b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            // of consecutive errors of this kind
6581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            Integer failures = errorMap.get(serverId);
6591b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            if (failures == null) {
6601b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                errorMap.put(serverId, 1);
6611b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            } else if (failures > MAX_PING_FAILURES) {
6621b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                // We'll back off of push for this box
6631b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                pushFallback(c.getLong(0));
6641b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                return;
6651b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            } else {
6661b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                errorMap.put(serverId, failures + 1);
6671b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            }
6681b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        }
6691b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    }
6701b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                }
6711b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            } finally {
6721b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                c.close();
6731b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
6741b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        }
6751b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    }
6761b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
677ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    void runPingLoop() throws IOException, StaleFolderListException {
6781b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        int pingHeartbeat = mPingHeartbeat;
67974c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank        userLog("runPingLoop");
680ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Do push for all sync services here
681c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        long endTime = System.currentTimeMillis() + (30*MINUTES);
6821b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HashMap<String, Integer> pingErrorMap = new HashMap<String, Integer>();
6832033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        ArrayList<String> readyMailboxes = new ArrayList<String>();
6842033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        ArrayList<String> notReadyMailboxes = new ArrayList<String>();
6857672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank        int pingWaitCount = 0;
6867672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank
6877ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        while ((System.currentTimeMillis() < endTime) && !mStop) {
688ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Count of pushable mailboxes
689ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int pushCount = 0;
690ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Count of mailboxes that can be pushed right now
691ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int canPushCount = 0;
6928a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            // Count of uninitialized boxes
6938a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            int uninitCount = 0;
6948a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank
6957c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            Serializer s = new Serializer();
696ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
69722bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank                    MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId +
69822bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank                    AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null, null);
6992033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            notReadyMailboxes.clear();
7002033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            readyMailboxes.clear();
701ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            try {
702ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Loop through our pushed boxes seeing what is available to push
703ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                while (c.moveToNext()) {
704ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    pushCount++;
705ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // Two requirements for push:
706ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // 1) SyncManager tells us the mailbox is syncable (not running, not stopped)
707ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // 2) The syncKey isn't "0" (i.e. it's synced at least once)
708ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank                    long mailboxId = c.getLong(Mailbox.CONTENT_ID_COLUMN);
70977424af660458104b732bdcb718874b17d0cab3aMarc Blank                    int pingStatus = SyncManager.pingStatus(mailboxId);
71077424af660458104b732bdcb718874b17d0cab3aMarc Blank                    String mailboxName = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN);
71177424af660458104b732bdcb718874b17d0cab3aMarc Blank                    if (pingStatus == SyncManager.PING_STATUS_OK) {
712a05c26d8d2cce3faa152096cb8116fce375c6d81Marc Blank
713ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String syncKey = c.getString(Mailbox.CONTENT_SYNC_KEY_COLUMN);
7147ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                        if ((syncKey == null) || syncKey.equals("0")) {
715a05c26d8d2cce3faa152096cb8116fce375c6d81Marc Blank                            // We can't push until the initial sync is done
71677424af660458104b732bdcb718874b17d0cab3aMarc Blank                            pushCount--;
7178a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                            uninitCount++;
718ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            continue;
719ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
720ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank
721ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        if (canPushCount++ == 0) {
722ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            // Initialize the Ping command
72396293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank                            s.start(Tags.PING_PING)
72405381a6662f28609e8005023515abb82af00e1d4Marc Blank                                .data(Tags.PING_HEARTBEAT_INTERVAL,
7251b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                        Integer.toString(pingHeartbeat))
7267c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                                .start(Tags.PING_FOLDERS);
727ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
7281b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
729ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String folderClass = getTargetCollectionClassFromCursor(c);
7307c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        s.start(Tags.PING_FOLDER)
7317c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .data(Tags.PING_ID, c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN))
7327c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .data(Tags.PING_CLASS, folderClass)
7337c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .end();
7342033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                        readyMailboxes.add(mailboxName);
7357ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                    } else if ((pingStatus == SyncManager.PING_STATUS_RUNNING) ||
7367ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                            (pingStatus == SyncManager.PING_STATUS_WAITING)) {
7372033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                        notReadyMailboxes.add(mailboxName);
73877424af660458104b732bdcb718874b17d0cab3aMarc Blank                    } else if (pingStatus == SyncManager.PING_STATUS_UNABLE) {
73977424af660458104b732bdcb718874b17d0cab3aMarc Blank                        pushCount--;
7400a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                        userLog(mailboxName, " in error state; ignore");
74177424af660458104b732bdcb718874b17d0cab3aMarc Blank                        continue;
742ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
743ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
744ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } finally {
745ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                c.close();
746ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
747ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
7482033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            if (Eas.USER_LOG) {
7492033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                if (!notReadyMailboxes.isEmpty()) {
7502033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                    userLog("Ping not ready for: " + notReadyMailboxes);
7512033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                }
7522033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                if (!readyMailboxes.isEmpty()) {
7532033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                    userLog("Ping ready for: " + readyMailboxes);
7542033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                }
7552033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            }
7562033dfc4e2e6e352b34565112266084d72c443f1Marc Blank
7577672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            // If we've waited 10 seconds or more, just ping with whatever boxes are ready
7587672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            // But use a shorter than normal heartbeat
7592033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            boolean forcePing = !notReadyMailboxes.isEmpty() && (pingWaitCount > 5);
7607672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank
7617672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            if ((canPushCount > 0) && ((canPushCount == pushCount) || forcePing)) {
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) {
821252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    String message = 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
824252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    boolean hasMessage = message != null;
825252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    userLog("IOException runPingLoop: " + (hasMessage ? message : "[no message]"));
826252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    if (hasMessage && message.contains("reset by peer")) {
8277ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                        if ((pingHeartbeat > PING_MIN_HEARTBEAT) &&
8287ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                                (pingHeartbeat > mPingHighWaterMark)) {
8291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            pingHeartbeat -= PING_HEARTBEAT_INCREMENT;
8301b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            mPingHeartbeatDropped = true;
8311b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            if (pingHeartbeat < PING_MIN_HEARTBEAT) {
8321b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                pingHeartbeat = PING_MIN_HEARTBEAT;
8331b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            }
8341b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            userLog("Decreased ping heartbeat to ", pingHeartbeat, "s");
83574c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                        } else if ((SystemClock.elapsedRealtime() - pingTime) < 2000) {
83674c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                            userLog("NAT type IOException < 2 seconds; throwing IOException");
83774c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                            throw e;
83874c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                        } else {
83974c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                            userLog("NAT type IOException > 2 seconds?");
8401b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        }
8411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    } else {
8421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        throw e;
8431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    }
844ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
8452033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            } else if (forcePing) {
8462033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                // In this case, there aren't any boxes that are pingable, but there are boxes
8472033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                // waiting (for IOExceptions)
8482033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                userLog("pingLoop waiting 60s for any pingable boxes");
8492033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(60*SECONDS, true);
850ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else if (pushCount > 0) {
8511ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank                // If we want to Ping, but can't just yet, wait a little bit
8521ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank                // TODO Change sleep to wait and use notify from SyncManager when a sync ends
8532033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(2*SECONDS, false);
8547672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                pingWaitCount++;
8552033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                //userLog("pingLoop waited 2s for: ", (pushCount - canPushCount), " box(es)");
8568a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            } else if (uninitCount > 0) {
8578a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // In this case, we're doing an initial sync of at least one mailbox.  Since this
8588a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // is typically a one-time case, I'm ok with trying again every 10 seconds until
8598a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // we're in one of the other possible states.
8602033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                userLog("pingLoop waiting for initial sync of ", uninitCount, " box(es)");
8612033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(10*SECONDS, true);
862ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
863c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                // We've got nothing to do, so we'll check again in 30 minutes at which time
8641b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                // we'll update the folder list.  Let the device sleep in the meantime...
8652033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                userLog("pingLoop sleeping for 30m");
8662033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(30*MINUTES, true);
867ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
868ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
869ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
870ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
8712033dfc4e2e6e352b34565112266084d72c443f1Marc Blank    void sleep(long ms, boolean runAsleep) {
8722033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        if (runAsleep) {
8732033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            SyncManager.runAsleep(mMailboxId, ms+(5*SECONDS));
8742033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        }
875ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
876ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Thread.sleep(ms);
877ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (InterruptedException e) {
878ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Doesn't matter whether we stop early; it's the thought that counts
8792033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        } finally {
8802033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            if (runAsleep) {
8812033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                SyncManager.runAwake(mMailboxId);
8822033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            }
883ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
884ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
885ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
8868047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    private int parsePingResult(InputStream is, ContentResolver cr)
887ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        throws IOException, StaleFolderListException {
8888047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        PingParser pp = new PingParser(is, this);
889ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (pp.parse()) {
890ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // True indicates some mailboxes need syncing...
891ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // syncList has the serverId's of the mailboxes...
892ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mBindArguments[0] = Long.toString(mAccount.mId);
8931b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            mPingChangeList = pp.getSyncList();
8941b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            for (String serverId: mPingChangeList) {
895d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                mBindArguments[1] = serverId;
896ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
897ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
898ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                try {
899ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (c.moveToFirst()) {
9004d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                        SyncManager.startManualSync(c.getLong(Mailbox.CONTENT_ID_COLUMN),
901ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank                                SyncManager.SYNC_PING, null);
902ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
903ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } finally {
904ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    c.close();
905ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
906ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
907ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
9081b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return pp.getSyncStatus();
909ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
910ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
911368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    private String getFilterType() {
912368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        String filter = Eas.FILTER_1_WEEK;
913368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        switch (mAccount.mSyncLookback) {
914368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_DAY: {
915368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_DAY;
916368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
917368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
918368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_3_DAYS: {
919368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_3_DAYS;
920368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
921368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
922368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_WEEK: {
923368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_WEEK;
924368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
925368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
926368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_2_WEEKS: {
927368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_2_WEEKS;
928368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
929368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
930368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_MONTH: {
931368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_MONTH;
932368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
933368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
934368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_ALL: {
935368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_ALL;
936368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
937368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
938368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        }
939368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        return filter;
940368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
941368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
942ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
943ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Common code to sync E+PIM data
944ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
945ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @param target, an EasMailbox, EasContacts, or EasCalendar object
946ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
9477c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank    public void sync(AbstractSyncAdapter target) throws IOException {
948ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        Mailbox mailbox = target.mMailbox;
949ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
950ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        boolean moreAvailable = true;
951ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        while (!mStop && moreAvailable) {
9521b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // If we have no connectivity, just exit cleanly.  SyncManager will start us up again
9531b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // when connectivity has returned
9541b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (!hasConnectivity()) {
9551b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                userLog("No connectivity in sync; finishing sync");
9561b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                mExitStatus = EXIT_DONE;
9571b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                return;
9581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
959ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
960d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            while (true) {
961d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                PartRequest req = null;
962d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                synchronized (mPartRequests) {
963d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    if (mPartRequests.isEmpty()) {
964d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        break;
965d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    } else {
966d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        req = mPartRequests.get(0);
967d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    }
968d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
969842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank                getAttachment(req);
970d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                synchronized(mPartRequests) {
971d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    mPartRequests.remove(req);
972d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
973d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            }
974d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
9757c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            Serializer s = new Serializer();
976ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String className = target.getCollectionName();
97748af7392c82262d17700e3fbdccf3a582809d449Marc Blank            String syncKey = target.getSyncKey();
97848af7392c82262d17700e3fbdccf3a582809d449Marc Blank            userLog("Sending ", className, " syncKey: ", syncKey);
9797c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.start(Tags.SYNC_SYNC)
9807c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .start(Tags.SYNC_COLLECTIONS)
9817c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .start(Tags.SYNC_COLLECTION)
9827c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .data(Tags.SYNC_CLASS, className)
98348af7392c82262d17700e3fbdccf3a582809d449Marc Blank                .data(Tags.SYNC_SYNC_KEY, syncKey)
9847c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId)
9857c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .tag(Tags.SYNC_DELETES_AS_MOVES);
986ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
987ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // EAS doesn't like GetChanges if the syncKey is "0"; not documented
98848af7392c82262d17700e3fbdccf3a582809d449Marc Blank            if (!syncKey.equals("0")) {
9897c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.tag(Tags.SYNC_GET_CHANGES);
990ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
9911b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            s.data(Tags.SYNC_WINDOW_SIZE,
9921b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                    className.equals("Email") ? EMAIL_WINDOW_SIZE : PIM_WINDOW_SIZE);
993ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            boolean options = false;
994ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (!className.equals("Contacts")) {
995ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Set the lookback appropriately (EAS calls this a "filter")
996368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                s.start(Tags.SYNC_OPTIONS).data(Tags.SYNC_FILTER_TYPE, getFilterType());
99700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                options = true;
998ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
99900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            if (mProtocolVersionDouble >= 12.0) {
1000ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                if (!options) {
1001ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    options = true;
10027c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    s.start(Tags.SYNC_OPTIONS);
1003ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
10047c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.start(Tags.BASE_BODY_PREFERENCE)
1005368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                    // HTML for email; plain text for everything else
1006c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                    .data(Tags.BASE_TYPE, (className.equals("Email") ? Eas.BODY_PREFERENCE_HTML
1007368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                            : Eas.BODY_PREFERENCE_TEXT))
10087c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    .end();
1009ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1010ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (options) {
10117c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.end();
1012ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1013ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1014ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Send our changes up to the server
101548af7392c82262d17700e3fbdccf3a582809d449Marc Blank            target.sendLocalChanges(s);
1016ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
10177c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.end().end().end().done();
10188047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            HttpResponse resp = sendHttpClientPost("Sync", s.toByteArray());
10198047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            int code = resp.getStatusLine().getStatusCode();
10201b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (code == HttpStatus.SC_OK) {
10218047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                 InputStream is = resp.getEntity().getContent();
1022ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                if (is != null) {
102348af7392c82262d17700e3fbdccf3a582809d449Marc Blank                    moreAvailable = target.parse(is);
102448af7392c82262d17700e3fbdccf3a582809d449Marc Blank                    target.cleanup();
1025ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1026ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
10270a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("Sync response error: ", code);
1028368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                if (isAuthError(code)) {
10291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_LOGIN_FAILURE;
10301b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                } else {
10311b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_IO_ERROR;
1032ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1033ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return;
1034ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1035ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
10361b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        mExitStatus = EXIT_DONE;
1037ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1038ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
10397ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    protected void setupService() {
10407ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        // Make sure account and mailbox are always the latest from the database
10417ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
10427ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId);
10437ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank
1044ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mThread = Thread.currentThread();
10457310cbacf2cf614c949330faff3882082054c120Marc Blank        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
1046ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        TAG = mThread.getName();
10479d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
1048ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
1049ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mHostAddress = ha.mAddress;
1050ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mUserName = ha.mLogin;
1051ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mPassword = ha.mPassword;
10527ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    }
10537ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank
10547ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    /* (non-Javadoc)
10557ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank     * @see java.lang.Runnable#run()
10567ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank     */
10577ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    public void run() {
10587ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        setupService();
1059ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1060fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        try {
1061fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().syncMailboxStatus(mMailboxId, EmailServiceStatus.IN_PROGRESS, 0);
1062fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e1) {
1063fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // Don't care if this fails
1064fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        }
1065fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
1066a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank        // Whether or not we're the account mailbox
1067ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
10689d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            mDeviceId = SyncManager.getDeviceId();
10697ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank            if ((mMailbox == null) || (mAccount == null)) {
1070147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank                return;
10717c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            } else if (mMailbox.mType == Mailbox.TYPE_EAS_ACCOUNT_MAILBOX) {
1072a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank                runAccountMailbox();
1073ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
10747c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                AbstractSyncAdapter target;
107500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mProtocolVersion = mAccount.mProtocolVersion;
107600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
10777cf921b830554e52f88a45ca4a290b17d2a1b146Marc Blank                if (mMailbox.mType == Mailbox.TYPE_CONTACTS) {
10787c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    target = new ContactsSyncAdapter(mMailbox, this);
10797cf921b830554e52f88a45ca4a290b17d2a1b146Marc Blank                } else {
10807c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    target = new EmailSyncAdapter(mMailbox, this);
1081ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1082ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // We loop here because someone might have put a request in while we were syncing
1083ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // and we've missed that opportunity...
1084ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                do {
1085ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (mRequestTime != 0) {
1086ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        userLog("Looping for user request...");
1087ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        mRequestTime = 0;
1088ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
1089ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    sync(target);
1090ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } while (mRequestTime != 0);
1091ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1092ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (IOException e) {
1093f9423affa52661c2f552df35f0b9ddeecd8fa8feMarc Blank            String message = e.getMessage();
10941431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank            userLog("Caught IOException: ", ((message == null) ? "No message" : message));
1095ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mExitStatus = EXIT_IO_ERROR;
1096ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (Exception e) {
10971431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank            userLog("Uncaught exception in EasSyncService", e);
1098ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
10994d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank            if (!mStop) {
11000a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog(mMailbox.mDisplayName, ": sync finished");
11014d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                SyncManager.done(this);
11024d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                // If this is the account mailbox, wake up SyncManager
11034d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                // Because this box has a "push" interval, it will be restarted immediately
11044d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                // which will cause the folder list to be reloaded...
11055c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                int status;
11065c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                switch (mExitStatus) {
11075c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_IO_ERROR:
11085c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.CONNECTION_ERROR;
11095c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
11105c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_DONE:
11115c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.SUCCESS;
11125c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
11135c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_LOGIN_FAILURE:
11145c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.LOGIN_FAILED;
11155c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
11165c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    default:
11175c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.REMOTE_EXCEPTION;
111877424af660458104b732bdcb718874b17d0cab3aMarc Blank                        errorLog("Sync ended due to an exception.");
11195c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
11205c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                }
1121c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
11224d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                try {
11234d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                    SyncManager.callback().syncMailboxStatus(mMailboxId, status, 0);
11244d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                } catch (RemoteException e1) {
11254d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                    // Don't care if this fails
11264d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                }
11275c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank
11285c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                // Save the sync time and status
11295c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                ContentValues cv = new ContentValues();
11305c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
11315c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                String s = "S" + mSyncReason + ':' + status + ':' + mChangeCount;
11325c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                cv.put(Mailbox.SYNC_STATUS, s);
11335c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                mContentResolver.update(ContentUris
11345c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        .withAppendedId(Mailbox.CONTENT_URI, mMailboxId), cv, null, null);
11354d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank            } else {
11360a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog(mMailbox.mDisplayName, ": stopped thread finished.");
1137fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
11389d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
1139c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            // Make sure SyncManager knows about this
1140c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            SyncManager.kick("sync finished");
11419d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank       }
1142ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1143ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
1144