EasSyncService.java revision 5660b2e69436f2839de0659ada4880570c02b4a6
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
20f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongkerimport com.android.email.SecurityPolicy;
218e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Predaimport com.android.email.Utility;
229c300767e8f6ab7a700aa1064791e75ff1a0bf3dMarc Blankimport com.android.email.SecurityPolicy.PolicySet;
235c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport com.android.email.mail.Address;
24ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport com.android.email.mail.AuthenticationFailedException;
255c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport com.android.email.mail.MeetingInfo;
26ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport com.android.email.mail.MessagingException;
275c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport com.android.email.mail.PackedString;
2867698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Account;
2967698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.AccountColumns;
3067698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Attachment;
3167698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.AttachmentColumns;
3267698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.HostAuth;
3367698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Mailbox;
3467698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.MailboxColumns;
3567698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Message;
36346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blankimport com.android.email.service.EmailServiceConstants;
374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport com.android.email.service.EmailServiceProxy;
3828d918b477d9a6ab790e872aa4170b1ae566cf42Makoto Onukiimport com.android.email.service.EmailServiceStatus;
397c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.AbstractSyncAdapter;
4048af7392c82262d17700e3fbdccf3a582809d449Marc Blankimport com.android.exchange.adapter.AccountSyncAdapter;
415862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport com.android.exchange.adapter.CalendarSyncAdapter;
427c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.ContactsSyncAdapter;
437c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.EmailSyncAdapter;
447c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.FolderSyncParser;
4596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadlerimport com.android.exchange.adapter.GalParser;
467531be7774769c84b499b1de5dc46da3a9468316Marc Blankimport com.android.exchange.adapter.MeetingResponseParser;
477c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.PingParser;
488692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blankimport com.android.exchange.adapter.ProvisionParser;
497c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.Serializer;
507c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.Tags;
514f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blankimport com.android.exchange.adapter.Parser.EasParserException;
5296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadlerimport com.android.exchange.provider.GalResult;
535c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport com.android.exchange.utility.CalendarUtilities;
54ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
558047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.Header;
5600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.HttpEntity;
5700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.HttpResponse;
581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blankimport org.apache.http.HttpStatus;
598047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.HttpClient;
608047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.methods.HttpOptions;
6100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.client.methods.HttpPost;
628047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.methods.HttpRequestBase;
63e44d5875af006f4217718a1c0fc0e235af3863afMarc Blankimport org.apache.http.conn.ClientConnectionManager;
648047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.entity.ByteArrayEntity;
654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.apache.http.entity.StringEntity;
6600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.impl.client.DefaultHttpClient;
678047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.BasicHttpParams;
688047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.HttpConnectionParams;
698047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.HttpParams;
704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlPullParser;
714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlPullParserException;
724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlPullParserFactory;
734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlSerializer;
7400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
75ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.ContentResolver;
76ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankimport android.content.ContentUris;
77ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.ContentValues;
78ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.Context;
795c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport android.content.Entity;
80ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.database.Cursor;
814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport android.os.Bundle;
82d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blankimport android.os.RemoteException;
8374c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blankimport android.os.SystemClock;
845c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport android.provider.Calendar.Attendees;
855c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport android.provider.Calendar.Events;
86b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blankimport android.text.TextUtils;
879972a855025df83129b9c7de4010024188219bd3Marc Blankimport android.util.Base64;
884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport android.util.Log;
894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport android.util.Xml;
90ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport java.io.ByteArrayOutputStream;
9200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.File;
9300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.FileOutputStream;
9400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.IOException;
9500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.InputStream;
965b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blankimport java.lang.Thread.State;
9700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.net.URI;
9800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.net.URLEncoder;
99e44d5875af006f4217718a1c0fc0e235af3863afMarc Blankimport java.security.cert.CertificateException;
10000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.ArrayList;
101ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankimport java.util.HashMap;
10200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
103ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankpublic class EasSyncService extends AbstractSyncService {
104a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler    // STOPSHIP - DO NOT RELEASE AS 'TRUE'
105a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler    public static final boolean DEBUG_GAL_SERVICE = true;
106a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler
1071b275b9408d5b856e2482fa3951827489e9585ccMarc Blank    private static final String EMAIL_WINDOW_SIZE = "5";
108726d60d9b758f0383f8f8481190fc1a638427209Marc Blank    public static final String PIM_WINDOW_SIZE = "5";
109ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID =
110ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SERVER_ID + "=?";
1119d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private static final String WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING =
1129d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank        MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL +
113f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        '=' + Mailbox.CHECK_INTERVAL_PING;
11422bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank    private static final String AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX = " AND " +
115f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        MailboxColumns.SYNC_INTERVAL + " IN (" + Mailbox.CHECK_INTERVAL_PING +
116f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        ',' + Mailbox.CHECK_INTERVAL_PUSH + ") AND " + MailboxColumns.TYPE + "!=\"" +
1177c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank        Mailbox.TYPE_EAS_ACCOUNT_MAILBOX + '\"';
1189d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private static final String WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX =
1199d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank        MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL +
120f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        '=' + Mailbox.CHECK_INTERVAL_PUSH_HOLD;
1218047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    static private final int CHUNK_SIZE = 16*1024;
1228047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
1238047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    static private final String PING_COMMAND = "Ping";
12416b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // Command timeout is the the time allowed for reading data from an open connection before an
12516b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // IOException is thrown.  After a small added allowance, our watchdog alarm goes off (allowing
12616b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // us to detect a silently dropped connection).  The allowance is defined below.
127c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    static private final int COMMAND_TIMEOUT = 20*SECONDS;
12816b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // Connection timeout is the time given to connect to the server before reporting an IOException
1299e029e58959e581637a1288a1ff9ef44cad63576Marc Blank    static private final int CONNECTION_TIMEOUT = 20*SECONDS;
13016b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // The extra time allowed beyond the COMMAND_TIMEOUT before which our watchdog alarm triggers
1315660b2e69436f2839de0659ada4880570c02b4a6Marc Blank    static private final int WATCHDOG_TIMEOUT_ALLOWANCE = 30*SECONDS;
132c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
1334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    static private final String AUTO_DISCOVER_SCHEMA_PREFIX =
1344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        "http://schemas.microsoft.com/exchange/autodiscover/mobilesync/";
1354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    static private final String AUTO_DISCOVER_PAGE = "/autodiscover/autodiscover.xml";
1364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    static private final int AUTO_DISCOVER_REDIRECT_CODE = 451;
1374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
138b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    static public final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML";
139b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    static public final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML";
140b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
1411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    /**
1421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * We start with an 8 minute timeout, and increase/decrease by 3 minutes at a time.  There's
1431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * no point having a timeout shorter than 5 minutes, I think; at that point, we can just let
1441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * the ping exception out.  The maximum I use is 17 minutes, which is really an empirical
1451b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * choice; too long and we risk silent connection loss and loss of push for that period.  Too
1461b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * short and we lose efficiency/battery life.
1471b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     *
1481b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * If we ever have to drop the ping timeout, we'll never increase it again.  There's no point
1491b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * going into hysteresis; the NAT timeout isn't going to change without a change in connection,
1501b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * which will cause the sync service to be restarted at the starting heartbeat and going through
1511b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * the process again.
1521b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     */
1531b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_MINUTES = 60; // in seconds
1541b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_FUDGE_LOW = 10;
1551b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_STARTING_HEARTBEAT = (8*PING_MINUTES)-PING_FUDGE_LOW;
1561b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_MIN_HEARTBEAT = (5*PING_MINUTES)-PING_FUDGE_LOW;
1571b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_MAX_HEARTBEAT = (17*PING_MINUTES)-PING_FUDGE_LOW;
1581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_HEARTBEAT_INCREMENT = 3*PING_MINUTES;
1597672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank    static private final int PING_FORCE_HEARTBEAT = 2*PING_MINUTES;
1601b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
1611b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PROTOCOL_PING_STATUS_COMPLETED = 1;
16296293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank
1635b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank    // The amount of time we allow for a thread to release its post lock after receiving an alert
1645b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank    static private final int POST_LOCK_TIMEOUT = 10*SECONDS;
1655b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
166c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    // Fallbacks (in minutes) for ping loop failures
167252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank    static private final int MAX_PING_FAILURES = 1;
168c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    static private final int PING_FALLBACK_INBOX = 5;
16927cf341571fac3d8dbe866f503c34fc31e02bf85Marc Blank    static private final int PING_FALLBACK_PIM = 25;
170d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
1718692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    // MSFT's custom HTTP result code indicating the need to provision
1728692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    static private final int HTTP_NEED_PROVISIONING = 449;
1738692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
174ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // Reasonable default
175d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank    public String mProtocolVersion = Eas.DEFAULT_PROTOCOL_VERSION;
17600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank    public Double mProtocolVersionDouble;
17785f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank    protected String mDeviceId = null;
1789d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private String mDeviceType = "Android";
1791b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private String mAuthString = null;
1801b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private String mCmdString = null;
181ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mHostAddress;
182ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mUserName;
183ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mPassword;
1841b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private boolean mSsl = true;
1855843b85178a359446f81770ed7734604a1b2fa7dMarc Blank    private boolean mTrustSsl = false;
186ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public ContentResolver mContentResolver;
1871b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private String[] mBindArguments = new String[2];
1881b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private ArrayList<String> mPingChangeList;
1895b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank    // The HttpPost in progress
1905b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank    private volatile HttpPost mPendingPost = null;
1911b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    // The ping time (in seconds)
1921b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private int mPingHeartbeat = PING_STARTING_HEARTBEAT;
1931b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    // The longest successful ping heartbeat
1941b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private int mPingHighWaterMark = 0;
1951b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    // Whether we've ever lowered the heartbeat
1961b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private boolean mPingHeartbeatDropped = false;
197e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    // Whether a POST was aborted due to alarm (watchdog alarm)
198ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank    private boolean mPostAborted = false;
199e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    // Whether a POST was aborted due to reset
200e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    private boolean mPostReset = false;
201ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank    // Whether or not the sync service is valid (usable)
202ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank    public boolean mIsValid = true;
203ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
204ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public EasSyncService(Context _context, Mailbox _mailbox) {
205ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        super(_context, _mailbox);
206ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mContentResolver = _context.getContentResolver();
207ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        if (mAccount == null) {
208ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            mIsValid = false;
209ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            return;
210ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        }
211ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv);
212ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        if (ha == null) {
213ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            mIsValid = false;
214ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            return;
215ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        }
216ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
217e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        mTrustSsl = (ha.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES) != 0;
218ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
219ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
220ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private EasSyncService(String prefix) {
221ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        super(prefix);
222ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
223ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
224ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public EasSyncService() {
225ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        this("EAS Validation");
226ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
227ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
228ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
2295b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank    /**
2305b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     * Try to wake up a sync thread that is waiting on an HttpClient POST and has waited past its
2315b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     * socket timeout without having thrown an Exception
2325b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     *
2335b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     * @return true if the POST was successfully stopped; false if we've failed and interrupted
2345b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     * the thread
2355b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     */
2365b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank    public boolean alarm() {
2375b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        HttpPost post;
2385b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        String threadName = mThread.getName();
2395b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
2405b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        // Synchronize here so that we are guaranteed to have valid mPendingPost and mPostLock
2415b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        // executePostWithTimeout (which executes the HttpPost) also uses this lock
2421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        synchronized(getSynchronizer()) {
2435b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            // Get a reference to the current post lock
2445b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            post = mPendingPost;
2455b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            if (post != null) {
2465b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                if (Eas.USER_LOG) {
2475b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                    URI uri = post.getURI();
2485b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                    if (uri != null) {
2495b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        String query = uri.getQuery();
2505b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        if (query == null) {
2515b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                            query = "POST";
2525b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        }
2535b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        userLog(threadName, ": Alert, aborting ", query);
2545b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                    } else {
2555b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        userLog(threadName, ": Alert, no URI?");
256e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    }
257e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                }
2585b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                // Abort the POST
259ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank                mPostAborted = true;
2605b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                post.abort();
261e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank            } else {
2625b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                // If there's no POST, we're done
263e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                userLog("Alert, no pending POST");
2645b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                return true;
2655b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            }
2665b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        }
2675b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
2685b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        // Wait for the POST to finish
2695b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        try {
2705b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            Thread.sleep(POST_LOCK_TIMEOUT);
2715b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        } catch (InterruptedException e) {
2725b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        }
2735b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
2745b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        State s = mThread.getState();
2755b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        if (Eas.USER_LOG) {
2765b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            userLog(threadName + ": State = " + s.name());
2775b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        }
2785b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
2795b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        synchronized (getSynchronizer()) {
2805b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            // If the thread is still hanging around and the same post is pending, let's try to
2815b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            // stop the thread with an interrupt.
2825b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            if ((s != State.TERMINATED) && (mPendingPost != null) && (mPendingPost == post)) {
2835b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                mStop = true;
2845b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                mThread.interrupt();
2855b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                userLog("Interrupting...");
2865b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                // Let the caller know we had to interrupt the thread
2875b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                return false;
288e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank            }
289e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank        }
2905b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        // Let the caller know that the alarm was handled normally
2915b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        return true;
292e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    }
293e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank
294e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    @Override
295e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    public void reset() {
296e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank        synchronized(getSynchronizer()) {
297e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank            if (mPendingPost != null) {
298e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                URI uri = mPendingPost.getURI();
299e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                if (uri != null) {
300e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    String query = uri.getQuery();
301e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    if (query.startsWith("Cmd=Ping")) {
302e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        userLog("Reset, aborting Ping");
303e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        mPostReset = true;
304e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        mPendingPost.abort();
305e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    }
306e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                }
3071b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
308ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
309ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
310ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
311ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
312ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void stop() {
313ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mStop = true;
314c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        synchronized(getSynchronizer()) {
315c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            if (mPendingPost != null) {
316c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                mPendingPost.abort();
317c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            }
318c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
3195c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank    }
320ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
3211b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    /**
3221b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * Determine whether an HTTP code represents an authentication error
3231b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * @param code the HTTP code returned by the server
3241b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * @return whether or not the code represents an authentication error
3251b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     */
326f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank    protected boolean isAuthError(int code) {
3274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return (code == HttpStatus.SC_UNAUTHORIZED) || (code == HttpStatus.SC_FORBIDDEN);
328368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
329368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
33020da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
33120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Determine whether an HTTP code represents a provisioning error
33220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param code the HTTP code returned by the server
33320da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @return whether or not the code represents an provisioning error
33420da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
33520da011530088036d2bf45d3836d6986a4b5d423Marc Blank    protected boolean isProvisionError(int code) {
33620da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return (code == HTTP_NEED_PROVISIONING) || (code == HttpStatus.SC_FORBIDDEN);
33720da011530088036d2bf45d3836d6986a4b5d423Marc Blank    }
33820da011530088036d2bf45d3836d6986a4b5d423Marc Blank
339d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank    private void setupProtocolVersion(EasSyncService service, Header versionHeader)
340d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            throws MessagingException {
341d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // The string is a comma separated list of EAS versions in ascending order
342d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // e.g. 1.0,2.0,2.5,12.0,12.1
343d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        String supportedVersions = versionHeader.getValue();
344d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        userLog("Server supports versions: ", supportedVersions);
345d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        String[] supportedVersionsArray = supportedVersions.split(",");
346d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        String ourVersion = null;
347d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // Find the most recent version we support
348d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        for (String version: supportedVersionsArray) {
349d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            if (version.equals(Eas.SUPPORTED_PROTOCOL_EX2003) ||
350d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    version.equals(Eas.SUPPORTED_PROTOCOL_EX2007)) {
351d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                ourVersion = version;
3528692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            }
353d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        }
354d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // If we don't support any of the servers supported versions, throw an exception here
355d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // This will cause validation to fail
356d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        if (ourVersion == null) {
357d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            Log.w(TAG, "No supported EAS versions: " + supportedVersions);
358d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            throw new MessagingException(MessagingException.PROTOCOL_VERSION_UNSUPPORTED);
359d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        } else {
360d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            service.mProtocolVersion = ourVersion;
361d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            service.mProtocolVersionDouble = Double.parseDouble(ourVersion);
3628692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            if (service.mAccount != null) {
363d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                service.mAccount.mProtocolVersion = ourVersion;
3648692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            }
3658692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        }
3668692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    }
3678692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
368fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank    @Override
369ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void validateAccount(String hostAddress, String userName, String password, int port,
370e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            boolean ssl, boolean trustCertificates, Context context) throws MessagingException {
371ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
3720a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank            userLog("Testing EAS: ", hostAddress, ", ", userName, ", ssl = ", ssl ? "1" : "0");
373ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            EasSyncService svc = new EasSyncService("%TestAccount%");
3749d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            svc.mContext = context;
375ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mHostAddress = hostAddress;
376ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mUserName = userName;
377ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mPassword = password;
378ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mSsl = ssl;
379e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            svc.mTrustSsl = trustCertificates;
380704cb199ced39727d84103c7170fc888a54f6c97Marc Blank            // We mustn't use the "real" device id or we'll screw up current accounts
381704cb199ced39727d84103c7170fc888a54f6c97Marc Blank            // Any string will do, but we'll go for "validate"
382704cb199ced39727d84103c7170fc888a54f6c97Marc Blank            svc.mDeviceId = "validate";
3838047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            HttpResponse resp = svc.sendHttpClientOptions();
3848047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            int code = resp.getStatusLine().getStatusCode();
3859d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            userLog("Validation (OPTIONS) response: " + code);
3861b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (code == HttpStatus.SC_OK) {
387ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // No exception means successful validation
3883b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                Header commands = resp.getFirstHeader("MS-ASProtocolCommands");
3893b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                Header versions = resp.getFirstHeader("ms-asprotocolversions");
3903b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                if (commands == null || versions == null) {
3913b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                    userLog("OPTIONS response without commands or versions; reporting I/O error");
3923b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                    throw new MessagingException(MessagingException.IOERROR);
3933b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                }
394eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank
3958692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                // Make sure we've got the right protocol version set up
3968692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                setupProtocolVersion(svc, versions);
3978692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
398eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                // Run second test here for provisioning failures...
399eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                Serializer s = new Serializer();
400eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                userLog("Try folder sync");
401eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text("0")
402eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                    .end().end().done();
403eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                resp = svc.sendHttpClientPost("FolderSync", s.toByteArray());
404eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                code = resp.getStatusLine().getStatusCode();
4058692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                // We'll get one of the following responses if policies are required by the server
4068692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                if (code == HttpStatus.SC_FORBIDDEN || code == HTTP_NEED_PROVISIONING) {
4078692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                    // Get the policies and see if we are able to support them
40820da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    if (svc.canProvision() != null) {
4098692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                        // If so, send the advisory Exception (the account may be created later)
4108692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                        throw new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED);
4118692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                    } else
4128692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                        // If not, send the unsupported Exception (the account won't be created)
4138692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                        throw new MessagingException(
4148692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                                MessagingException.SECURITY_POLICIES_UNSUPPORTED);
4156e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                } else if (code == HttpStatus.SC_NOT_FOUND) {
4166e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    // We get a 404 from OWA addresses (which are NOT EAS addresses)
4176e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    throw new MessagingException(MessagingException.PROTOCOL_VERSION_UNSUPPORTED);
4186e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                } else if (code != HttpStatus.SC_OK) {
4196e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    // Fail generically with anything other than success
4206e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    userLog("Unexpected response for FolderSync: ", code);
4216e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION);
422eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                }
423ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                userLog("Validation successful");
424ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return;
425ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
426368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            if (isAuthError(code)) {
427ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                userLog("Authentication failed");
428ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                throw new AuthenticationFailedException("Validation failed");
429ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
430ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // TODO Need to catch other kinds of errors (e.g. policy) For now, report the code.
4310a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("Validation failed, reporting I/O error: ", code);
432ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                throw new MessagingException(MessagingException.IOERROR);
433ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
434ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (IOException e) {
435e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            Throwable cause = e.getCause();
436e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            if (cause != null && cause instanceof CertificateException) {
437e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank                userLog("CertificateException caught: ", e.getMessage());
438e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank                throw new MessagingException(MessagingException.GENERAL_SECURITY);
439e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            }
440e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            userLog("IOException caught: ", e.getMessage());
441ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            throw new MessagingException(MessagingException.IOERROR);
442ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
443ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
444ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
445ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
4464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    /**
4474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * Gets the redirect location from the HTTP headers and uses that to modify the HttpPost so that
4484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * it can be reused
4494d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     *
4504d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param resp the HttpResponse that indicates a redirect (451)
4514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param post the HttpPost that was originally sent to the server
4524d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @return the HttpPost, updated with the redirect location
4534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     */
4544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    private HttpPost getRedirect(HttpResponse resp, HttpPost post) {
4554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        Header locHeader = resp.getFirstHeader("X-MS-Location");
4564d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        if (locHeader != null) {
4574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            String loc = locHeader.getValue();
4584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // If we've gotten one and it shows signs of looking like an address, we try
4594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // sending our request there
4604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (loc != null && loc.startsWith("http")) {
4614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                post.setURI(URI.create(loc));
4624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                return post;
4634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
4644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
4654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return null;
4664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
4674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
4684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    /**
4694d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * Send the POST command to the autodiscover server, handling a redirect, if necessary, and
4704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * return the HttpResponse
4714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     *
4724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param client the HttpClient to be used for the request
4734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param post the HttpPost we're going to send
4744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @return an HttpResponse from the original or redirect server
4754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @throws IOException on any IOException within the HttpClient code
4764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @throws MessagingException
4774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     */
4784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    private HttpResponse postAutodiscover(HttpClient client, HttpPost post)
4794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws IOException, MessagingException {
4804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        userLog("Posting autodiscover to: " + post.getURI());
481adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        HttpResponse resp = executePostWithTimeout(client, post, COMMAND_TIMEOUT);
4824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        int code = resp.getStatusLine().getStatusCode();
4834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        // On a redirect, try the new location
4844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        if (code == AUTO_DISCOVER_REDIRECT_CODE) {
4854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            post = getRedirect(resp, post);
4864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (post != null) {
4874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                userLog("Posting autodiscover to redirect: " + post.getURI());
488adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                return executePostWithTimeout(client, post, COMMAND_TIMEOUT);
4894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
49052f7f7effdd320594cd1c5c8c67443ad51eca3afMarc Blank        } else if (code == HttpStatus.SC_UNAUTHORIZED) {
49152f7f7effdd320594cd1c5c8c67443ad51eca3afMarc Blank            // 401 (Unauthorized) is for true auth errors when used in Autodiscover
49252f7f7effdd320594cd1c5c8c67443ad51eca3afMarc Blank            // 403 (and others) we'll just punt on
4934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throw new MessagingException(MessagingException.AUTHENTICATION_FAILED);
4944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        } else if (code != HttpStatus.SC_OK) {
4954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // We'll try the next address if this doesn't work
4964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("Code: " + code + ", throwing IOException");
4974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throw new IOException();
4984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
4994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return resp;
5004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
5014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    /**
5034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * Use the Exchange 2007 AutoDiscover feature to try to retrieve server information using
5044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * only an email address and the password
5054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     *
5064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param userName the user's email address
5074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param password the user's password
5084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @return a HostAuth ready to be saved in an Account or null (failure)
5094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     */
5104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    public Bundle tryAutodiscover(String userName, String password) throws RemoteException {
5114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        XmlSerializer s = Xml.newSerializer();
5124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
5134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        HostAuth hostAuth = new HostAuth();
5144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        Bundle bundle = new Bundle();
5154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
5164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                MessagingException.NO_ERROR);
5174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        try {
5184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Build the XML document that's sent to the autodiscover server(s)
5194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.setOutput(os, "UTF-8");
5204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startDocument("UTF-8", false);
5214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "Autodiscover");
5224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.attribute(null, "xmlns", AUTO_DISCOVER_SCHEMA_PREFIX + "requestschema/2006");
5234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "Request");
5244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "EMailAddress").text(userName).endTag(null, "EMailAddress");
5254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "AcceptableResponseSchema");
5264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.text(AUTO_DISCOVER_SCHEMA_PREFIX + "responseschema/2006");
5274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endTag(null, "AcceptableResponseSchema");
5284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endTag(null, "Request");
5294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endTag(null, "Autodiscover");
5304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endDocument();
5314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            String req = os.toString();
5324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Initialize the user name and password
5344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            mUserName = userName;
5354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            mPassword = password;
5364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Make sure the authentication string is created (mAuthString)
5374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            makeUriString("foo", null);
5384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Split out the domain name
5404d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int amp = userName.indexOf('@');
5414d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // The UI ensures that userName is a valid email address
5424d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (amp < 0) {
5434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                throw new RemoteException();
5444d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
5454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            String domain = userName.substring(amp + 1);
5464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // There are up to four attempts here; the two URLs that we're supposed to try per the
5484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // specification, and up to one redirect for each (handled in postAutodiscover)
5494d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5504d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Try the domain first and see if we can get a response
5514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpPost post = new HttpPost("https://" + domain + AUTO_DISCOVER_PAGE);
55220da011530088036d2bf45d3836d6986a4b5d423Marc Blank            setHeaders(post, false);
5534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            post.setHeader("Content-Type", "text/xml");
5544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            post.setEntity(new StringEntity(req));
5554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpClient client = getHttpClient(COMMAND_TIMEOUT);
5564d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpResponse resp;
5574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            try {
5584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                resp = postAutodiscover(client, post);
5594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } catch (IOException e1) {
560adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                userLog("IOException in autodiscover; trying alternate address");
5614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // We catch the IOException here because we have an alternate address to try
5624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                post.setURI(URI.create("https://autodiscover." + domain + AUTO_DISCOVER_PAGE));
5634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // If we fail here, we're out of options, so we let the outer try catch the
5644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // IOException and return null
5654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                resp = postAutodiscover(client, post);
5664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
5674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Get the "final" code; if it's not 200, just return null
5694d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int code = resp.getStatusLine().getStatusCode();
5704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("Code: " + code);
5714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (code != HttpStatus.SC_OK) return null;
5724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // At this point, we have a 200 response (SC_OK)
5744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpEntity e = resp.getEntity();
5754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            InputStream is = e.getContent();
5764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            try {
5774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // The response to Autodiscover is regular XML (not WBXML)
5784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // If we ever get an error in this process, we'll just punt and return null
5794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
5804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                XmlPullParser parser = factory.newPullParser();
5814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                parser.setInput(is, "UTF-8");
5824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                int type = parser.getEventType();
5834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (type == XmlPullParser.START_DOCUMENT) {
5844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    type = parser.next();
5854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    if (type == XmlPullParser.START_TAG) {
5864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        String name = parser.getName();
5874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        if (name.equals("Autodiscover")) {
5884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            hostAuth = new HostAuth();
5894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            parseAutodiscover(parser, hostAuth);
5904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            // On success, we'll have a server address and login
5914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            if (hostAuth.mAddress != null && hostAuth.mLogin != null) {
5924d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                // Fill in the rest of the HostAuth
5934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mPassword = password;
5944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mPort = 443;
5954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mProtocol = "eas";
5964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mFlags =
5974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                    HostAuth.FLAG_SSL | HostAuth.FLAG_AUTHENTICATE;
5984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                bundle.putParcelable(
5994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                        EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH, hostAuth);
6004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            } else {
6014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
6024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                        MessagingException.UNSPECIFIED_EXCEPTION);
6034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            }
6044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        }
6054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    }
6064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
6074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } catch (XmlPullParserException e1) {
6084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // This would indicate an I/O error of some sort
6094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // We will simply return null and user can configure manually
6104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        // There's no reason at all for exceptions to be thrown, and it's ok if so.
6124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        // We just won't do auto-discover; user can configure manually
6134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank       } catch (IllegalArgumentException e) {
6144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank             bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
6154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                     MessagingException.UNSPECIFIED_EXCEPTION);
6164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank       } catch (IllegalStateException e) {
6174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
6184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    MessagingException.UNSPECIFIED_EXCEPTION);
6194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank       } catch (IOException e) {
6204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("IOException in Autodiscover", e);
6214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
6224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    MessagingException.IOERROR);
6234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        } catch (MessagingException e) {
6244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
6254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    MessagingException.AUTHENTICATION_FAILED);
6264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return bundle;
6284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseServer(XmlPullParser parser, HostAuth hostAuth)
6314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
6324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        boolean mobileSync = false;
6334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
6344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
6354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Server")) {
6364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
6374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
6384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
6394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("Type")) {
6404d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    if (parser.nextText().equals("MobileSync")) {
6414d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        mobileSync = true;
6424d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    }
6434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (mobileSync && name.equals("Url")) {
6444d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    String url = parser.nextText().toLowerCase();
6454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    // This will look like https://<server address>/Microsoft-Server-ActiveSync
6464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    // We need to extract the <server address>
6474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    if (url.startsWith("https://") &&
6484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            url.endsWith("/microsoft-server-activesync")) {
6494d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        int lastSlash = url.lastIndexOf('/');
6504d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        hostAuth.mAddress = url.substring(8, lastSlash);
6514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        userLog("Autodiscover, server: " + hostAuth.mAddress);
6524d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    }
6534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
6544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6564d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseSettings(XmlPullParser parser, HostAuth hostAuth)
6594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
6604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
6614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
6624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Settings")) {
6634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
6644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
6654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
6664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("Server")) {
6674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseServer(parser, hostAuth);
6684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
6694d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseAction(XmlPullParser parser, HostAuth hostAuth)
6744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
6754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
6764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
6774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Action")) {
6784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
6794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
6804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
6814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("Error")) {
6824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    // Should parse the error
6834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("Redirect")) {
6844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    Log.d(TAG, "Redirect: " + parser.nextText());
6854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("Settings")) {
6864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseSettings(parser, hostAuth);
6874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
6884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6924d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseUser(XmlPullParser parser, HostAuth hostAuth)
6934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
6944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
6954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
6964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("User")) {
6974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
6984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
6994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
7004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("EMailAddress")) {
7014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    String addr = parser.nextText();
7024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    hostAuth.mLogin = addr;
7034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    userLog("Autodiscover, login: " + addr);
7044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("DisplayName")) {
7054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    String dn = parser.nextText();
7064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    userLog("Autodiscover, user: " + dn);
7074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
7084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
7094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
7104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
7114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
7124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseResponse(XmlPullParser parser, HostAuth hostAuth)
7134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
7144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
7154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
7164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Response")) {
7174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
7184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
7194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
7204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("User")) {
7214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseUser(parser, hostAuth);
7224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("Action")) {
7234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseAction(parser, hostAuth);
7244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
7254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
7264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
7274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
7284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
7294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseAutodiscover(XmlPullParser parser, HostAuth hostAuth)
7304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
7314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
7324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.nextTag();
7334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Autodiscover")) {
7344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
7354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG && parser.getName().equals("Response")) {
7364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                parseResponse(parser, hostAuth);
7374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
7384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
7394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
7404d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
74196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    /**
74296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * Contact the GAL and obtain a list of matching accounts
74396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @param context caller's context
74496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @param accountId the account Id to search
74596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @param filter the characters entered so far
74696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @return a result record
74796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     *
74896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * TODO: shorter timeout for interactive lookup
74996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * TODO: make watchdog actually work (it doesn't understand our service w/Mailbox == 0)
75096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * TODO: figure out why sendHttpClientPost() hangs - possibly pool exhaustion
75196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     */
75296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    static public GalResult searchGal(Context context, long accountId, String filter)
75396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    {
754114f17e8efea93380680417af4503c8e6820c394Marc Blank        Account acct = SyncManager.getAccountById(accountId);
75596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler        if (acct != null) {
75696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            HostAuth ha = HostAuth.restoreHostAuthWithId(context, acct.mHostAuthKeyRecv);
757a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler            EasSyncService svc = new EasSyncService("%GalLookupk%");
75896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            try {
75996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mContext = context;
76096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mHostAddress = ha.mAddress;
76196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mUserName = ha.mLogin;
76296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mPassword = ha.mPassword;
76396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
76496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mTrustSsl = (ha.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES) != 0;
76596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mDeviceId = SyncManager.getDeviceId();
7667b377bf23e53f2fda5c7c0f19ddf2f9d1096945eMarc Blank                svc.mAccount = acct;
76796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                Serializer s = new Serializer();
76896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE);
76996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.data(Tags.SEARCH_NAME, "GAL").data(Tags.SEARCH_QUERY, filter);
77096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.start(Tags.SEARCH_OPTIONS);
771a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                s.data(Tags.SEARCH_RANGE, "0-19");  // Return 0..20 results
77296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.end().end().end().done();
773a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                if (DEBUG_GAL_SERVICE) svc.userLog("GAL lookup starting for " + ha.mAddress);
77496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                HttpResponse resp = svc.sendHttpClientPost("Search", s.toByteArray());
77596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                int code = resp.getStatusLine().getStatusCode();
77696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                if (code == HttpStatus.SC_OK) {
77796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                    InputStream is = resp.getEntity().getContent();
77896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                    GalParser gp = new GalParser(is, svc);
77996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                    if (gp.parse()) {
780a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                        if (DEBUG_GAL_SERVICE) svc.userLog("GAL lookup OK for " + ha.mAddress);
78196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                        return gp.getGalResult();
782a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                    } else {
783a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                        if (DEBUG_GAL_SERVICE) svc.userLog("GAL lookup returned no matches");
78496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                    }
785a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                } else {
786a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                    svc.userLog("GAL lookup returned " + code);
78796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                }
78896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            } catch (IOException e) {
78996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                // GAL is non-critical; we'll just go on
790a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                svc.userLog("GAL lookup exception " + e);
79196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            }
79296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler        }
79396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler        return null;
79496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    }
79596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler
79681d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank    private void doStatusCallback(long messageId, long attachmentId, int status) {
797d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        try {
798fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().loadAttachmentStatus(messageId, attachmentId, status, 0);
799fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e) {
800d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            // No danger if the client is no longer around
801d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        }
802d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank    }
803ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
80481d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank    private void doProgressCallback(long messageId, long attachmentId, int progress) {
805d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        try {
806fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().loadAttachmentStatus(messageId, attachmentId,
807fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                    EmailServiceStatus.IN_PROGRESS, progress);
808fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e) {
809d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            // No danger if the client is no longer around
810d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        }
811d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank    }
812d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
813842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank    public File createUniqueFileInternal(String dir, String filename) {
814842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        File directory;
815842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (dir == null) {
816842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            directory = mContext.getFilesDir();
817842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        } else {
818842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            directory = new File(dir);
819842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
820842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (!directory.exists()) {
821842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            directory.mkdirs();
822842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
823842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        File file = new File(directory, filename);
824842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (!file.exists()) {
825842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            return file;
826842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
827842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        // Get the extension of the file, if any.
828842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        int index = filename.lastIndexOf('.');
829842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        String name = filename;
830842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        String extension = "";
831842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (index != -1) {
832842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            name = filename.substring(0, index);
833842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            extension = filename.substring(index);
834842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
835842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        for (int i = 2; i < Integer.MAX_VALUE; i++) {
836842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            file = new File(directory, name + '-' + i + extension);
837842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            if (!file.exists()) {
838842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank                return file;
839842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            }
840842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
841842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        return null;
842842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank    }
843842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank
844d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank    /**
845d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * Loads an attachment, based on the PartRequest passed in.  The PartRequest is basically our
846d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * wrapper for Attachment
847d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * @param req the part (attachment) to be retrieved
848d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * @throws IOException
849d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     */
850842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank    protected void getAttachment(PartRequest req) throws IOException {
8517531be7774769c84b499b1de5dc46da3a9468316Marc Blank        Attachment att = req.mAttachment;
852b0ce70f8d18dfe14fdd72be528d89eda1ba229feMarc Blank        Message msg = Message.restoreMessageWithId(mContext, att.mMessageKey);
85381d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank        doProgressCallback(msg.mId, att.mId, 0);
854ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
85542f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        String cmd = "GetAttachment&AttachmentName=" + att.mLocation;
85642f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        HttpResponse res = sendHttpClientPost(cmd, null, COMMAND_TIMEOUT);
85742f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank
858ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int status = res.getStatusLine().getStatusCode();
8591b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        if (status == HttpStatus.SC_OK) {
860ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            HttpEntity e = res.getEntity();
861ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int len = (int)e.getContentLength();
862ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            InputStream is = res.getEntity().getContent();
8637531be7774769c84b499b1de5dc46da3a9468316Marc Blank            File f = (req.mDestination != null)
8647531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    ? new File(req.mDestination)
8657531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    : createUniqueFileInternal(req.mDestination, att.mFileName);
866ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (f != null) {
867bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                // Ensure that the target directory exists
868bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                File destDir = f.getParentFile();
869bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                if (!destDir.exists()) {
870bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                    destDir.mkdirs();
871bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                }
872ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                FileOutputStream os = new FileOutputStream(f);
87395e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                // len > 0 means that Content-Length was set in the headers
87495e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                // len < 0 means "chunked" transfer-encoding
87595e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                if (len != 0) {
876ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    try {
8777531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        mPendingRequest = req;
878ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        byte[] bytes = new byte[CHUNK_SIZE];
879ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        int length = len;
88095e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        // Loop terminates 1) when EOF is reached or 2) if an IOException occurs
88195e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        // One of these is guaranteed to occur
88295e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        int totalRead = 0;
88395e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        userLog("Attachment content-length: ", len);
88495e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        while (true) {
88595e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            int read = is.read(bytes, 0, CHUNK_SIZE);
88695e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank
88795e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // read < 0 means that EOF was reached
88895e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            if (read < 0) {
88995e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                userLog("Attachment load reached EOF, totalRead: ", totalRead);
89095e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                break;
89195e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            }
89295e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank
89395e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // Keep track of how much we've read for progress callback
89495e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            totalRead += read;
89595e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank
89695e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // Write these bytes out
897ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            os.write(bytes, 0, read);
89895e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank
89995e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // We can't report percentages if this is chunked; by definition, the
90095e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // length of incoming data is unknown
90195e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            if (length > 0) {
90295e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                // Belt and suspenders check to prevent runaway reading
90395e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                if (totalRead > length) {
90495e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                    errorLog("totalRead is greater than attachment length?");
90595e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                    break;
90695e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                }
9074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                int pct = (totalRead * 100) / length;
90895e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                doProgressCallback(msg.mId, att.mId, pct);
90995e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            }
91095e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                       }
911ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    } finally {
9127531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        mPendingRequest = null;
913ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
914ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
915ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                os.flush();
916ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                os.close();
917ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
918d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                // EmailProvider will throw an exception if we try to update an unsaved attachment
919d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                if (att.isSaved()) {
9207531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    String contentUriString = (req.mContentUriString != null)
9217531be7774769c84b499b1de5dc46da3a9468316Marc Blank                            ? req.mContentUriString
922bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                            : "file://" + f.getAbsolutePath();
923d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    ContentValues cv = new ContentValues();
924bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                    cv.put(AttachmentColumns.CONTENT_URI, contentUriString);
925d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    att.update(mContext, cv);
92681d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank                    doStatusCallback(msg.mId, att.mId, EmailServiceStatus.SUCCESS);
927d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
928ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
929d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        } else {
93081d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank            doStatusCallback(msg.mId, att.mId, EmailServiceStatus.MESSAGE_NOT_FOUND);
931ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
932ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
933ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
9347531be7774769c84b499b1de5dc46da3a9468316Marc Blank    /**
9355c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     * Send an email responding to a Message that has been marked as a meeting request.  The message
9365c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     * will consist a little bit of event information and an iCalendar attachment
9375c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     * @param msg the meeting request email
9385c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     */
939346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank    private void sendMeetingResponseMail(Message msg, int response) {
9405c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Get the meeting information; we'd better have some...
9415c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        PackedString meetingInfo = new PackedString(msg.mMeetingInfo);
9425c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        if (meetingInfo == null) return;
9435c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9445c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // This will come as "First Last" <box@server.blah>, so we use Address to
9455c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // parse it into parts; we only need the email address part for the ics file
9465c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        Address[] addrs = Address.parse(meetingInfo.get(MeetingInfo.MEETING_ORGANIZER_EMAIL));
9475c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // It shouldn't be possible, but handle it anyway
9485c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        if (addrs.length != 1) return;
9495c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String organizerEmail = addrs[0].getAddress();
9505c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9515c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String dtStamp = meetingInfo.get(MeetingInfo.MEETING_DTSTAMP);
9525c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String dtStart = meetingInfo.get(MeetingInfo.MEETING_DTSTART);
9535c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String dtEnd = meetingInfo.get(MeetingInfo.MEETING_DTEND);
9545c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9555c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // What we're doing here is to create an Entity that looks like an Event as it would be
9565c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // stored by CalendarProvider
9575c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        ContentValues entityValues = new ContentValues();
9585c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        Entity entity = new Entity(entityValues);
9595c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9605c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Fill in times, location, title, and organizer
9615c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put("DTSTAMP",
9625c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                CalendarUtilities.convertEmailDateTimeToCalendarDateTime(dtStamp));
9638e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda        entityValues.put(Events.DTSTART, Utility.parseEmailDateTimeToMillis(dtStart));
9648e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda        entityValues.put(Events.DTEND, Utility.parseEmailDateTimeToMillis(dtEnd));
9655c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put(Events.EVENT_LOCATION, meetingInfo.get(MeetingInfo.MEETING_LOCATION));
9665c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put(Events.TITLE, meetingInfo.get(MeetingInfo.MEETING_TITLE));
9675c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put(Events.ORGANIZER, organizerEmail);
9685c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9695c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Add ourselves as an attendee, using our account email address
9705c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        ContentValues attendeeValues = new ContentValues();
9715c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
9725c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                Attendees.RELATIONSHIP_ATTENDEE);
9735c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        attendeeValues.put(Attendees.ATTENDEE_EMAIL, mAccount.mEmailAddress);
9745c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
9755c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9765c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Add the organizer
9775c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        ContentValues organizerValues = new ContentValues();
9785c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        organizerValues.put(Attendees.ATTENDEE_RELATIONSHIP,
9795c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                Attendees.RELATIONSHIP_ORGANIZER);
9805c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        organizerValues.put(Attendees.ATTENDEE_EMAIL, organizerEmail);
9815c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entity.addSubValue(Attendees.CONTENT_URI, organizerValues);
9825c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9835c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Create a message from the Entity we've built.  The message will have fields like
9845c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // to, subject, date, and text filled in.  There will also be an "inline" attachment
9855c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // which is in iCalendar format
986346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank        int flag;
987346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank        switch(response) {
988346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            case EmailServiceConstants.MEETING_REQUEST_ACCEPTED:
989346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                flag = Message.FLAG_OUTGOING_MEETING_ACCEPT;
990346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                break;
991346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            case EmailServiceConstants.MEETING_REQUEST_DECLINED:
992346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                flag = Message.FLAG_OUTGOING_MEETING_DECLINE;
993346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                break;
994346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            case EmailServiceConstants.MEETING_REQUEST_TENTATIVE:
995346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            default:
996346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                flag = Message.FLAG_OUTGOING_MEETING_TENTATIVE;
997346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                break;
998346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank        }
9995c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        Message outgoingMsg =
1000346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            CalendarUtilities.createMessageForEntity(mContext, entity, flag,
10015c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    meetingInfo.get(MeetingInfo.MEETING_UID), mAccount);
10025c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Assuming we got a message back (we might not if the event has been deleted), send it
10035c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        if (outgoingMsg != null) {
10045c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            EasOutboxService.sendMessage(mContext, mAccount.mId, outgoingMsg);
10055c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        }
10065c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank    }
10075c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
10085c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank    /**
10097531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * Responds to a meeting request.  The MeetingResponseRequest is basically our
10107531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * wrapper for the meetingResponse service call
10117531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * @param req the request (message id and response code)
10127531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * @throws IOException
10137531be7774769c84b499b1de5dc46da3a9468316Marc Blank     */
10147531be7774769c84b499b1de5dc46da3a9468316Marc Blank    protected void sendMeetingResponse(MeetingResponseRequest req) throws IOException {
10154f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        // Retrieve the message and mailbox; punt if either are null
10167531be7774769c84b499b1de5dc46da3a9468316Marc Blank        Message msg = Message.restoreMessageWithId(mContext, req.mMessageId);
10174f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        if (msg == null) return;
10184f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, msg.mMailboxKey);
10194f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        if (mailbox == null) return;
10207531be7774769c84b499b1de5dc46da3a9468316Marc Blank        Serializer s = new Serializer();
10217531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.start(Tags.MREQ_MEETING_RESPONSE).start(Tags.MREQ_REQUEST);
10227531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.data(Tags.MREQ_USER_RESPONSE, Integer.toString(req.mResponse));
10234f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        s.data(Tags.MREQ_COLLECTION_ID, mailbox.mServerId);
10247531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.data(Tags.MREQ_REQ_ID, msg.mServerId);
10257531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.end().end().done();
10267531be7774769c84b499b1de5dc46da3a9468316Marc Blank        HttpResponse res = sendHttpClientPost("MeetingResponse", s.toByteArray());
10277531be7774769c84b499b1de5dc46da3a9468316Marc Blank        int status = res.getStatusLine().getStatusCode();
10287531be7774769c84b499b1de5dc46da3a9468316Marc Blank        if (status == HttpStatus.SC_OK) {
10297531be7774769c84b499b1de5dc46da3a9468316Marc Blank            HttpEntity e = res.getEntity();
10307531be7774769c84b499b1de5dc46da3a9468316Marc Blank            int len = (int)e.getContentLength();
10317531be7774769c84b499b1de5dc46da3a9468316Marc Blank            InputStream is = res.getEntity().getContent();
10327531be7774769c84b499b1de5dc46da3a9468316Marc Blank            if (len != 0) {
10337531be7774769c84b499b1de5dc46da3a9468316Marc Blank                new MeetingResponseParser(is, this).parse();
1034346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                sendMeetingResponseMail(msg, req.mResponse);
10357531be7774769c84b499b1de5dc46da3a9468316Marc Blank            }
10367531be7774769c84b499b1de5dc46da3a9468316Marc Blank        } else if (isAuthError(status)) {
10377531be7774769c84b499b1de5dc46da3a9468316Marc Blank            throw new EasAuthenticationException();
10387531be7774769c84b499b1de5dc46da3a9468316Marc Blank        } else {
10397531be7774769c84b499b1de5dc46da3a9468316Marc Blank            userLog("Meeting response request failed, code: " + status);
10407531be7774769c84b499b1de5dc46da3a9468316Marc Blank            throw new IOException();
10417531be7774769c84b499b1de5dc46da3a9468316Marc Blank        }
10427531be7774769c84b499b1de5dc46da3a9468316Marc Blank    }
10437531be7774769c84b499b1de5dc46da3a9468316Marc Blank
1044147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank    @SuppressWarnings("deprecation")
10459d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private String makeUriString(String cmd, String extra) throws IOException {
1046ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank         // Cache the authentication string and the command string
1047ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        String safeUserName = URLEncoder.encode(mUserName);
1048ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (mAuthString == null) {
1049ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String cs = mUserName + ':' + mPassword;
1050f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker            mAuthString = "Basic " + Base64.encodeToString(cs.getBytes(), Base64.NO_WRAP);
1051ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType="
1052ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    + mDeviceType;
1053ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1054e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        String us = (mSsl ? (mTrustSsl ? "httpts" : "https") : "http") + "://" + mHostAddress +
1055ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            "/Microsoft-Server-ActiveSync";
1056ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (cmd != null) {
1057ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            us += "?Cmd=" + cmd + mCmdString;
1058ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1059ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (extra != null) {
1060ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            us += extra;
1061ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1062ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return us;
1063ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1064ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
106520da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
106620da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Set standard HTTP headers, using a policy key if required
106720da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param method the method we are going to send
106820da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param usePolicyKey whether or not a policy key should be sent in the headers
106920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
107020da011530088036d2bf45d3836d6986a4b5d423Marc Blank    private void setHeaders(HttpRequestBase method, boolean usePolicyKey) {
10718047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("Authorization", mAuthString);
10728047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("MS-ASProtocolVersion", mProtocolVersion);
10738047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("Connection", "keep-alive");
10748047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("User-Agent", mDeviceType + '/' + Eas.VERSION);
107520da011530088036d2bf45d3836d6986a4b5d423Marc Blank        if (usePolicyKey && (mAccount != null)) {
107620da011530088036d2bf45d3836d6986a4b5d423Marc Blank            String key = mAccount.mSecuritySyncKey;
107720da011530088036d2bf45d3836d6986a4b5d423Marc Blank            if (key == null || key.length() == 0) {
107820da011530088036d2bf45d3836d6986a4b5d423Marc Blank                return;
107920da011530088036d2bf45d3836d6986a4b5d423Marc Blank            }
108095fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank             if (Eas.PARSER_LOG) {
108195fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                userLog("Policy key: " , key);
108295fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank            }
108320da011530088036d2bf45d3836d6986a4b5d423Marc Blank            method.setHeader("X-MS-PolicyKey", key);
108420da011530088036d2bf45d3836d6986a4b5d423Marc Blank        }
10858047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
1086ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1087e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank    private ClientConnectionManager getClientConnectionManager() {
1088e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        return SyncManager.getClientConnectionManager();
1089e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank    }
1090e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank
10918047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    private HttpClient getHttpClient(int timeout) {
10928047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpParams params = new BasicHttpParams();
109316b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank        HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
10948047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpConnectionParams.setSoTimeout(params, timeout);
1095e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        HttpConnectionParams.setSocketBufferSize(params, 8192);
1096e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        HttpClient client = new DefaultHttpClient(getClientConnectionManager(), params);
1097e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        return client;
10988047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
1099ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
11008047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    protected HttpResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException {
11011b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return sendHttpClientPost(cmd, new ByteArrayEntity(bytes), COMMAND_TIMEOUT);
11029e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    }
11039e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank
11049e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity) throws IOException {
11051b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return sendHttpClientPost(cmd, entity, COMMAND_TIMEOUT);
11061b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    }
11071b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
11082033dfc4e2e6e352b34565112266084d72c443f1Marc Blank    protected HttpResponse sendPing(byte[] bytes, int heartbeat) throws IOException {
11091b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       Thread.currentThread().setName(mAccount.mDisplayName + ": Ping");
11101b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       if (Eas.USER_LOG) {
11112033dfc4e2e6e352b34565112266084d72c443f1Marc Blank           userLog("Send ping, timeout: " + heartbeat + "s, high: " + mPingHighWaterMark + 's');
11121b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       }
11132033dfc4e2e6e352b34565112266084d72c443f1Marc Blank       return sendHttpClientPost(PING_COMMAND, new ByteArrayEntity(bytes), (heartbeat+5)*SECONDS);
11141b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    }
11151b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
1116adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    /**
1117adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * Convenience method for executePostWithTimeout for use other than with the Ping command
1118adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     */
1119adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    protected HttpResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout)
1120adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            throws IOException {
1121adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        return executePostWithTimeout(client, method, timeout, false);
1122adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    }
1123adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank
1124adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    /**
1125adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * Handle executing an HTTP POST command with proper timeout, watchdog, and ping behavior
1126adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param client the HttpClient
1127adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param method the HttpPost
1128adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param timeout the timeout before failure, in ms
1129adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param isPingCommand whether the POST is for the Ping command (requires wakelock logic)
1130adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @return the HttpResponse
1131adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @throws IOException
1132adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     */
1133adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    protected HttpResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout,
1134adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            boolean isPingCommand) throws IOException {
1135adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        synchronized(getSynchronizer()) {
1136adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            mPendingPost = method;
113716b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            long alarmTime = timeout + WATCHDOG_TIMEOUT_ALLOWANCE;
1138adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            if (isPingCommand) {
1139adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                SyncManager.runAsleep(mMailboxId, alarmTime);
1140adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            } else {
1141adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                SyncManager.setWatchdogAlarm(mMailboxId, alarmTime);
1142adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            }
1143adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        }
1144adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        try {
1145adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            return client.execute(method);
1146adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        } finally {
1147adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            synchronized(getSynchronizer()) {
1148adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                if (isPingCommand) {
1149adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                    SyncManager.runAwake(mMailboxId);
1150adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                } else {
1151adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                    SyncManager.clearWatchdogAlarm(mMailboxId);
1152adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                }
1153adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                mPendingPost = null;
1154adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            }
1155adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        }
1156adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    }
1157adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank
11581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity, int timeout)
11591b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            throws IOException {
11601b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HttpClient client = getHttpClient(timeout);
1161c4bc56c4057d2d7596b75c60ee792676251804f5Marc Blank        boolean isPingCommand = cmd.equals(PING_COMMAND);
116285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
116385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        // Split the mail sending commands
116485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String extra = null;
116585f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        boolean msg = false;
116685f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        if (cmd.startsWith("SmartForward&") || cmd.startsWith("SmartReply&")) {
11675843b85178a359446f81770ed7734604a1b2fa7dMarc Blank            int cmdLength = cmd.indexOf('&');
116885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            extra = cmd.substring(cmdLength);
116985f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            cmd = cmd.substring(0, cmdLength);
117085f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
117185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        } else if (cmd.startsWith("SendMail&")) {
117285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
117385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        }
117485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
117585f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String us = makeUriString(cmd, extra);
11768047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpPost method = new HttpPost(URI.create(us));
117742f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        // Send the proper Content-Type header
117842f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        // If entity is null (e.g. for attachments), don't set this header
117985f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        if (msg) {
11808047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "message/rfc822");
118142f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        } else if (entity != null) {
11828047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml");
1183ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
118420da011530088036d2bf45d3836d6986a4b5d423Marc Blank        setHeaders(method, !cmd.equals(PING_COMMAND));
11859e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        method.setEntity(entity);
1186adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        return executePostWithTimeout(client, method, timeout, isPingCommand);
11878047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
11888047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
11898047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    protected HttpResponse sendHttpClientOptions() throws IOException {
11908047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpClient client = getHttpClient(COMMAND_TIMEOUT);
11918047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        String us = makeUriString("OPTIONS", null);
11928047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpOptions method = new HttpOptions(URI.create(us));
119320da011530088036d2bf45d3836d6986a4b5d423Marc Blank        setHeaders(method, false);
11948047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        return client.execute(method);
1195ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1196ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1197ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    String getTargetCollectionClassFromCursor(Cursor c) {
1198ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
1199ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (type == Mailbox.TYPE_CONTACTS) {
1200ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Contacts";
1201ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else if (type == Mailbox.TYPE_CALENDAR) {
1202ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Calendar";
1203ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else {
1204ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Email";
1205ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1206ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1207ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
120820da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
120920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Negotiate provisioning with the server.  First, get policies form the server and see if
121020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * the policies are supported by the device.  Then, write the policies to the account and
121120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * tell SecurityPolicy that we have policies in effect.  Finally, see if those policies are
121220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * active; if so, acknowledge the policies to the server and get a final policy key that we
121320da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * use in future EAS commands and write this key to the account.
121420da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @return whether or not provisioning has been successful
121520da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @throws IOException
121620da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
121720da011530088036d2bf45d3836d6986a4b5d423Marc Blank    private boolean tryProvision() throws IOException {
121820da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // First, see if provisioning is even possible, i.e. do we support the policies required
121920da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // by the server
122020da011530088036d2bf45d3836d6986a4b5d423Marc Blank        ProvisionParser pp = canProvision();
122120da011530088036d2bf45d3836d6986a4b5d423Marc Blank        if (pp != null) {
122220da011530088036d2bf45d3836d6986a4b5d423Marc Blank            SecurityPolicy sp = SecurityPolicy.getInstance(mContext);
122320da011530088036d2bf45d3836d6986a4b5d423Marc Blank            // Get the policies from ProvisionParser
122420da011530088036d2bf45d3836d6986a4b5d423Marc Blank            PolicySet ps = pp.getPolicySet();
122520da011530088036d2bf45d3836d6986a4b5d423Marc Blank            // Update the account with a null policyKey (the key we've gotten is
122620da011530088036d2bf45d3836d6986a4b5d423Marc Blank            // temporary and cannot be used for syncing)
122720da011530088036d2bf45d3836d6986a4b5d423Marc Blank            if (ps.writeAccount(mAccount, null, true, mContext)) {
122820da011530088036d2bf45d3836d6986a4b5d423Marc Blank                sp.updatePolicies(mAccount.mId);
122920da011530088036d2bf45d3836d6986a4b5d423Marc Blank            }
1230bbc1811956084987767779e31525c5013dab59d8Marc Blank            if (pp.getRemoteWipe()) {
12312a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                // We've gotten a remote wipe command
12322b82632e75c2154ab3eda63fbfb00f6236a297ddMarc Blank                // If we're not the admin, we can't do the wipe, so just return
12332b82632e75c2154ab3eda63fbfb00f6236a297ddMarc Blank                if (!sp.isActiveAdmin()) return false;
1234bbc1811956084987767779e31525c5013dab59d8Marc Blank                // First, we've got to acknowledge it, but wrap the wipe in try/catch so that
1235bbc1811956084987767779e31525c5013dab59d8Marc Blank                // we wipe the device regardless of any errors in acknowledgment
12362a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                try {
12372a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                    acknowledgeRemoteWipe(pp.getPolicyKey());
12382a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                } catch (Exception e) {
12392a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                    // Because remote wipe is such a high priority task, we don't want to
12402a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                    // circumvent it if there's an exception in acknowledgment
12412a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                }
12422a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                // Then, tell SecurityPolicy to wipe the device
12432a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                sp.remoteWipe();
12442a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                return false;
1245bbc1811956084987767779e31525c5013dab59d8Marc Blank            } else if (sp.isActive(ps)) {
1246bbc1811956084987767779e31525c5013dab59d8Marc Blank                // See if the required policies are in force; if they are, acknowledge the policies
1247bbc1811956084987767779e31525c5013dab59d8Marc Blank                // to the server and get the final policy key
1248bbc1811956084987767779e31525c5013dab59d8Marc Blank                String policyKey = acknowledgeProvision(pp.getPolicyKey());
1249bbc1811956084987767779e31525c5013dab59d8Marc Blank                if (policyKey != null) {
1250bbc1811956084987767779e31525c5013dab59d8Marc Blank                    // Write the final policy key to the Account and say we've been successful
1251bbc1811956084987767779e31525c5013dab59d8Marc Blank                    ps.writeAccount(mAccount, policyKey, true, mContext);
1252bbc1811956084987767779e31525c5013dab59d8Marc Blank                    return true;
1253bbc1811956084987767779e31525c5013dab59d8Marc Blank                }
125420da011530088036d2bf45d3836d6986a4b5d423Marc Blank            } else {
125520da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // Notify that we are blocked because of policies
125620da011530088036d2bf45d3836d6986a4b5d423Marc Blank                sp.policiesRequired(mAccount.mId);
125720da011530088036d2bf45d3836d6986a4b5d423Marc Blank            }
125820da011530088036d2bf45d3836d6986a4b5d423Marc Blank        }
125920da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return false;
126020da011530088036d2bf45d3836d6986a4b5d423Marc Blank    }
126120da011530088036d2bf45d3836d6986a4b5d423Marc Blank
1262b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    private String getPolicyType() {
1263d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        return (mProtocolVersionDouble >=
1264d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE;
1265b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    }
1266b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
126720da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
126820da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Obtain a set of policies from the server and determine whether those policies are supported
126920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * by the device.
127020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @return the ProvisionParser (holds policies and key) if we receive policies and they are
127120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * supported by the device; null otherwise
127220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @throws IOException
127320da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
127420da011530088036d2bf45d3836d6986a4b5d423Marc Blank    private ProvisionParser canProvision() throws IOException {
12758692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        Serializer s = new Serializer();
12768692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
1277b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank        s.start(Tags.PROVISION_POLICY).data(Tags.PROVISION_POLICY_TYPE, getPolicyType())
12788692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            .end().end().end().done();
12798692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        HttpResponse resp = sendHttpClientPost("Provision", s.toByteArray());
12808692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        int code = resp.getStatusLine().getStatusCode();
12818692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        if (code == HttpStatus.SC_OK) {
12828692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            InputStream is = resp.getEntity().getContent();
12838692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            ProvisionParser pp = new ProvisionParser(is, this);
12848692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            if (pp.parse()) {
128520da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // If true, we received policies from the server; see if they are supported by
128620da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // the framework; if so, return the ProvisionParser containing the policy set and
128720da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // temporary key
12888692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                PolicySet ps = pp.getPolicySet();
12898692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                if (SecurityPolicy.getInstance(mContext).isSupported(ps)) {
129020da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    return pp;
12918692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                }
12928692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            }
12938692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        }
129420da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // On failures, simply return null
129520da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return null;
129620da011530088036d2bf45d3836d6986a4b5d423Marc Blank    }
129720da011530088036d2bf45d3836d6986a4b5d423Marc Blank
129820da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
129920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Acknowledge that we support the policies provided by the server, and that these policies
130020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * are in force.
130120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param tempKey the initial (temporary) policy key sent by the server
130220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @return the final policy key, which can be used for syncing
130320da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @throws IOException
130420da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
13052a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    private void acknowledgeRemoteWipe(String tempKey) throws IOException {
13062a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        acknowledgeProvisionImpl(tempKey, true);
13072a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    }
13082a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank
130920da011530088036d2bf45d3836d6986a4b5d423Marc Blank    private String acknowledgeProvision(String tempKey) throws IOException {
13102a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        return acknowledgeProvisionImpl(tempKey, false);
13112a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    }
13122a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank
13132a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    private String acknowledgeProvisionImpl(String tempKey, boolean remoteWipe) throws IOException {
131420da011530088036d2bf45d3836d6986a4b5d423Marc Blank        Serializer s = new Serializer();
131520da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
131620da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.start(Tags.PROVISION_POLICY);
1317b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
1318b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank        // Use the proper policy type, depending on EAS version
1319b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank        s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType());
1320b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
132120da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.data(Tags.PROVISION_POLICY_KEY, tempKey);
132220da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.data(Tags.PROVISION_STATUS, "1");
13232ca2e1caef16fde39df2c795ac4c9477f9a8c6d9Marc Blank        s.end().end(); // PROVISION_POLICY, PROVISION_POLICIES
13242a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        if (remoteWipe) {
13252a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank            s.start(Tags.PROVISION_REMOTE_WIPE);
13262a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank            s.data(Tags.PROVISION_STATUS, "1");
13272a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank            s.end();
13282a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        }
13292ca2e1caef16fde39df2c795ac4c9477f9a8c6d9Marc Blank        s.end().done(); // PROVISION_PROVISION
133020da011530088036d2bf45d3836d6986a4b5d423Marc Blank        HttpResponse resp = sendHttpClientPost("Provision", s.toByteArray());
133120da011530088036d2bf45d3836d6986a4b5d423Marc Blank        int code = resp.getStatusLine().getStatusCode();
133220da011530088036d2bf45d3836d6986a4b5d423Marc Blank        if (code == HttpStatus.SC_OK) {
133320da011530088036d2bf45d3836d6986a4b5d423Marc Blank            InputStream is = resp.getEntity().getContent();
133420da011530088036d2bf45d3836d6986a4b5d423Marc Blank            ProvisionParser pp = new ProvisionParser(is, this);
133520da011530088036d2bf45d3836d6986a4b5d423Marc Blank            if (pp.parse()) {
133620da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // Return the final polic key from the ProvisionParser
133720da011530088036d2bf45d3836d6986a4b5d423Marc Blank                return pp.getPolicyKey();
133820da011530088036d2bf45d3836d6986a4b5d423Marc Blank            }
133920da011530088036d2bf45d3836d6986a4b5d423Marc Blank        }
134020da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // On failures, return null
134120da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return null;
13428692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    }
13438692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
1344ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
1345ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Performs FolderSync
1346ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
1347ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
1348ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws EasParserException
1349ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
1350a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank    public void runAccountMailbox() throws IOException, EasParserException {
1351fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        // Initialize exit status to success
1352fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        mExitStatus = EmailServiceStatus.SUCCESS;
1353ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
1354fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            try {
1355fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                SyncManager.callback()
1356fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                    .syncMailboxListStatus(mAccount.mId, EmailServiceStatus.IN_PROGRESS, 0);
1357fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            } catch (RemoteException e1) {
1358fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                // Don't care if this fails
1359fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
1360fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
1361ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (mAccount.mSyncKey == null) {
1362ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                mAccount.mSyncKey = "0";
13631b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Account syncKey INIT to 0");
13649387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                ContentValues cv = new ContentValues();
13659387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
13669387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                mAccount.update(mContext, cv);
1367ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1368ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
13699d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            boolean firstSync = mAccount.mSyncKey.equals("0");
13709d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            if (firstSync) {
13711b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Initial FolderSync");
13721b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            }
13731b275b9408d5b856e2482fa3951827489e9585ccMarc Blank
13744626078bf9d930b2007162db142b5961b38e2166Marc Blank            // When we first start up, change all mailboxes to push.
1375ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ContentValues cv = new ContentValues();
1376f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank            cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
1377ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
13789d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING,
13799d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    new String[] {Long.toString(mAccount.mId)}) > 0) {
13809d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                SyncManager.kick("change ping boxes to push");
1381ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1382ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1383fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank            // Determine our protocol version, if we haven't already and save it in the Account
138485a57898618f48773884e8bc34ca1e8995cc3690Marc Blank            // Also re-check protocol version at least once a day (in case of upgrade)
138585a57898618f48773884e8bc34ca1e8995cc3690Marc Blank            if (mAccount.mProtocolVersion == null ||
138685a57898618f48773884e8bc34ca1e8995cc3690Marc Blank                    ((System.currentTimeMillis() - mMailbox.mSyncTime) > DAYS)) {
13871b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Determine EAS protocol version");
13888047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                HttpResponse resp = sendHttpClientOptions();
13898047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                int code = resp.getStatusLine().getStatusCode();
13900a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("OPTIONS response: ", code);
13911b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                if (code == HttpStatus.SC_OK) {
1392647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    Header header = resp.getFirstHeader("MS-ASProtocolCommands");
1393647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    userLog(header.getValue());
1394647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    header = resp.getFirstHeader("ms-asprotocolversions");
1395d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    try {
1396d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                        setupProtocolVersion(this, header);
1397d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    } catch (MessagingException e) {
1398d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                        // Since we've already validated, this can't really happen
1399d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                        // But if it does, we'll rethrow this...
1400ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        throw new IOException();
1401ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
1402d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    // Save the protocol version
1403d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    cv.clear();
1404d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    // Save the protocol version in the account
1405d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    cv.put(Account.PROTOCOL_VERSION, mProtocolVersion);
1406d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    mAccount.update(mContext, cv);
1407d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    cv.clear();
1408d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    // Save the sync time of the account mailbox to current time
1409d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
1410d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    mMailbox.update(mContext, cv);
1411d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                 } else {
14128047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    errorLog("OPTIONS command failed; throwing IOException");
14138047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    throw new IOException();
14140f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
14150f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank            }
14161b275b9408d5b856e2482fa3951827489e9585ccMarc Blank
141777424af660458104b732bdcb718874b17d0cab3aMarc Blank            // Change all pushable boxes to push when we start the account mailbox
141877424af660458104b732bdcb718874b17d0cab3aMarc Blank            if (mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH) {
1419fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank                cv.clear();
1420f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank                cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
142177424af660458104b732bdcb718874b17d0cab3aMarc Blank                if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
142277424af660458104b732bdcb718874b17d0cab3aMarc Blank                        SyncManager.WHERE_IN_ACCOUNT_AND_PUSHABLE,
142377424af660458104b732bdcb718874b17d0cab3aMarc Blank                        new String[] {Long.toString(mAccount.mId)}) > 0) {
142477424af660458104b732bdcb718874b17d0cab3aMarc Blank                    userLog("Push account; set pushable boxes to push...");
142577424af660458104b732bdcb718874b17d0cab3aMarc Blank                }
142677424af660458104b732bdcb718874b17d0cab3aMarc Blank            }
142777424af660458104b732bdcb718874b17d0cab3aMarc Blank
142877424af660458104b732bdcb718874b17d0cab3aMarc Blank            while (!mStop) {
1429afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                userLog("Sending Account syncKey: ", mAccount.mSyncKey);
1430afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                Serializer s = new Serializer();
1431afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY)
143220da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    .text(mAccount.mSyncKey).end().end().done();
1433afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
1434afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                if (mStop) break;
1435afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                int code = resp.getStatusLine().getStatusCode();
1436afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                if (code == HttpStatus.SC_OK) {
1437afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    HttpEntity entity = resp.getEntity();
1438afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    int len = (int)entity.getContentLength();
1439afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    if (len != 0) {
1440afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                        InputStream is = entity.getContent();
1441afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                        // Returns true if we need to sync again
1442afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                        if (new FolderSyncParser(is, new AccountSyncAdapter(mMailbox, this))
144320da011530088036d2bf45d3836d6986a4b5d423Marc Blank                                .parse()) {
1444afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                            continue;
1445afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                        }
1446afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    }
144720da011530088036d2bf45d3836d6986a4b5d423Marc Blank                } else if (isProvisionError(code)) {
144820da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    // If the sync error is a provisioning failure (perhaps the policies changed),
144995fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    // let's try the provisioning procedure
145095fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    // Provisioning must only be attempted for the account mailbox - trying to
145195fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    // provision any other mailbox may result in race conditions and the creation
145295fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    // of multiple policy keys.
145320da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    if (!tryProvision()) {
145420da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        // Set the appropriate failure status
145520da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        mExitStatus = EXIT_SECURITY_FAILURE;
145620da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        return;
1457094e6da9f5c5728eb10a3572717db2ba55718df3Marc Blank                    } else {
1458094e6da9f5c5728eb10a3572717db2ba55718df3Marc Blank                        // If we succeeded, try again...
1459094e6da9f5c5728eb10a3572717db2ba55718df3Marc Blank                        continue;
1460afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    }
1461afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                } else if (isAuthError(code)) {
14621b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_LOGIN_FAILURE;
146320da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    return;
14640f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } else {
14650a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    userLog("FolderSync response error: ", code);
14660f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
1467ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
14689d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                // Change all push/hold boxes to push
1469fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank                cv.clear();
14709d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                cv.put(Mailbox.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
14719d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
14729d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX,
14739d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        new String[] {Long.toString(mAccount.mId)}) > 0) {
14749d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    userLog("Set push/hold boxes to push...");
14759d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                }
14769d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
14770f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                try {
14780f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    SyncManager.callback()
14799d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        .syncMailboxListStatus(mAccount.mId, mExitStatus, 0);
14800f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } catch (RemoteException e1) {
14810f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    // Don't care if this fails
14820f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
1483b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank
1484b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                // Before each run of the pingLoop, if this Account has a PolicySet, make sure it's
1485b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                // active; otherwise, clear out the key/flag.  This should cause a provisioning
1486b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                // error on the next POST, and start the security sequence over again
1487b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                String key = mAccount.mSecuritySyncKey;
1488b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                if (!TextUtils.isEmpty(key)) {
1489b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                    PolicySet ps = new PolicySet(mAccount);
1490b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                    SecurityPolicy sp = SecurityPolicy.getInstance(mContext);
1491b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                    if (!sp.isActive(ps)) {
1492b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        cv.clear();
1493b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        cv.put(AccountColumns.SECURITY_FLAGS, 0);
1494b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
1495b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        long accountId = mAccount.mId;
1496b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        mContentResolver.update(ContentUris.withAppendedId(
1497b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                                Account.CONTENT_URI, accountId), cv, null, null);
1498b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        sp.policiesRequired(accountId);
1499b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                    }
1500b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                }
1501fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
15020f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                // Wait for push notifications.
15030f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                String threadName = Thread.currentThread().getName();
15040f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                try {
15050f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    runPingLoop();
15060f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } catch (StaleFolderListException e) {
15070f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    // We break out if we get told about a stale folder list
15080f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    userLog("Ping interrupted; folder list requires sync...");
15090f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } finally {
15100f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    Thread.currentThread().setName(threadName);
15110f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
1512ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
15130f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank         } catch (IOException e) {
1514fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // We catch this here to send the folder sync status callback
1515fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // A folder sync failed callback will get sent from run()
1516fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            try {
15174d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                if (!mStop) {
15184d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                    SyncManager.callback()
15194d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                        .syncMailboxListStatus(mAccount.mId,
15204d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                                EmailServiceStatus.CONNECTION_ERROR, 0);
15214d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                }
1522fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            } catch (RemoteException e1) {
1523fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                // Don't care if this fails
1524fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
152596293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank            throw e;
1526ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1527ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1528ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
15294ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank    private void pushFallback(long mailboxId) {
1530c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
15315ea28d42b2aee64747a35d8bf012afff1604bc4cMakoto Onuki        if (mailbox == null) {
15325ea28d42b2aee64747a35d8bf012afff1604bc4cMakoto Onuki            return;
15335ea28d42b2aee64747a35d8bf012afff1604bc4cMakoto Onuki        }
1534c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        ContentValues cv = new ContentValues();
15354626078bf9d930b2007162db142b5961b38e2166Marc Blank        int mins = PING_FALLBACK_PIM;
1536c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        if (mailbox.mType == Mailbox.TYPE_INBOX) {
15374626078bf9d930b2007162db142b5961b38e2166Marc Blank            mins = PING_FALLBACK_INBOX;
1538c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
15394626078bf9d930b2007162db142b5961b38e2166Marc Blank        cv.put(Mailbox.SYNC_INTERVAL, mins);
15404626078bf9d930b2007162db142b5961b38e2166Marc Blank        mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
15414626078bf9d930b2007162db142b5961b38e2166Marc Blank                cv, null, null);
154227cf341571fac3d8dbe866f503c34fc31e02bf85Marc Blank        errorLog("*** PING ERROR LOOP: Set " + mailbox.mDisplayName + " to " + mins + " min sync");
15434626078bf9d930b2007162db142b5961b38e2166Marc Blank        SyncManager.kick("push fallback");
1544368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
1545368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
15464ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank    /**
15474ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank     * Simplistic attempt to determine a NAT timeout, based on experience with various carriers
15482675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank     * and networks.  The string "reset by peer" is very common in these situations, so we look for
15492675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank     * that specifically.  We may add additional tests here as more is learned.
15504ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank     * @param message
15514ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank     * @return whether this message is likely associated with a NAT failure
15524ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank     */
15534ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank    private boolean isLikelyNatFailure(String message) {
15544ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank        if (message == null) return false;
15552675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank        if (message.contains("reset by peer")) {
15564ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank            return true;
15574ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank        }
15584ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank        return false;
15594ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank    }
15604ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank
15614ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank    private void runPingLoop() throws IOException, StaleFolderListException {
15621b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        int pingHeartbeat = mPingHeartbeat;
156374c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank        userLog("runPingLoop");
1564ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Do push for all sync services here
1565c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        long endTime = System.currentTimeMillis() + (30*MINUTES);
15661b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HashMap<String, Integer> pingErrorMap = new HashMap<String, Integer>();
15672033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        ArrayList<String> readyMailboxes = new ArrayList<String>();
15682033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        ArrayList<String> notReadyMailboxes = new ArrayList<String>();
15697672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank        int pingWaitCount = 0;
1570f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker
15717ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        while ((System.currentTimeMillis() < endTime) && !mStop) {
1572ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Count of pushable mailboxes
1573ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int pushCount = 0;
1574ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Count of mailboxes that can be pushed right now
1575ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int canPushCount = 0;
15768a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            // Count of uninitialized boxes
15778a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            int uninitCount = 0;
1578f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker
15797c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            Serializer s = new Serializer();
1580ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
158122bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank                    MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId +
158222bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank                    AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null, null);
15832033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            notReadyMailboxes.clear();
15842033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            readyMailboxes.clear();
1585ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            try {
1586ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Loop through our pushed boxes seeing what is available to push
1587ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                while (c.moveToNext()) {
1588ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    pushCount++;
1589ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // Two requirements for push:
1590ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // 1) SyncManager tells us the mailbox is syncable (not running, not stopped)
1591ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // 2) The syncKey isn't "0" (i.e. it's synced at least once)
1592ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank                    long mailboxId = c.getLong(Mailbox.CONTENT_ID_COLUMN);
159377424af660458104b732bdcb718874b17d0cab3aMarc Blank                    int pingStatus = SyncManager.pingStatus(mailboxId);
159477424af660458104b732bdcb718874b17d0cab3aMarc Blank                    String mailboxName = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN);
159577424af660458104b732bdcb718874b17d0cab3aMarc Blank                    if (pingStatus == SyncManager.PING_STATUS_OK) {
1596ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String syncKey = c.getString(Mailbox.CONTENT_SYNC_KEY_COLUMN);
15977ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                        if ((syncKey == null) || syncKey.equals("0")) {
1598a05c26d8d2cce3faa152096cb8116fce375c6d81Marc Blank                            // We can't push until the initial sync is done
159977424af660458104b732bdcb718874b17d0cab3aMarc Blank                            pushCount--;
16008a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                            uninitCount++;
1601ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            continue;
1602ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
1603ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank
1604ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        if (canPushCount++ == 0) {
1605ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            // Initialize the Ping command
160696293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank                            s.start(Tags.PING_PING)
160705381a6662f28609e8005023515abb82af00e1d4Marc Blank                                .data(Tags.PING_HEARTBEAT_INTERVAL,
16081b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                        Integer.toString(pingHeartbeat))
16097c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                                .start(Tags.PING_FOLDERS);
1610ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
16111b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
1612ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String folderClass = getTargetCollectionClassFromCursor(c);
16137c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        s.start(Tags.PING_FOLDER)
16147c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .data(Tags.PING_ID, c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN))
16157c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .data(Tags.PING_CLASS, folderClass)
16167c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .end();
16172033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                        readyMailboxes.add(mailboxName);
16187ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                    } else if ((pingStatus == SyncManager.PING_STATUS_RUNNING) ||
16197ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                            (pingStatus == SyncManager.PING_STATUS_WAITING)) {
16202033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                        notReadyMailboxes.add(mailboxName);
162177424af660458104b732bdcb718874b17d0cab3aMarc Blank                    } else if (pingStatus == SyncManager.PING_STATUS_UNABLE) {
162277424af660458104b732bdcb718874b17d0cab3aMarc Blank                        pushCount--;
16230a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                        userLog(mailboxName, " in error state; ignore");
162477424af660458104b732bdcb718874b17d0cab3aMarc Blank                        continue;
1625ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
1626ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1627ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } finally {
1628ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                c.close();
1629ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1630ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
16312033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            if (Eas.USER_LOG) {
16322033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                if (!notReadyMailboxes.isEmpty()) {
16332033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                    userLog("Ping not ready for: " + notReadyMailboxes);
16342033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                }
16352033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                if (!readyMailboxes.isEmpty()) {
16362033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                    userLog("Ping ready for: " + readyMailboxes);
16372033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                }
16382033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            }
1639f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker
16407672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            // If we've waited 10 seconds or more, just ping with whatever boxes are ready
16417672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            // But use a shorter than normal heartbeat
16422033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            boolean forcePing = !notReadyMailboxes.isEmpty() && (pingWaitCount > 5);
16437672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank
16447672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            if ((canPushCount > 0) && ((canPushCount == pushCount) || forcePing)) {
16457672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                // If all pingable boxes are ready for push, send Ping to the server
16467c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.end().end().done();
16477672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                pingWaitCount = 0;
1648e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                mPostReset = false;
1649e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                mPostAborted = false;
16507c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank
16511b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                // If we've been stopped, this is a good time to return
16524d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                if (mStop) return;
1653368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
165474c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                long pingTime = SystemClock.elapsedRealtime();
16551b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                try {
16561b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // Send the ping, wrapped by appropriate timeout/alarm
16577672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                    if (forcePing) {
16587672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                        userLog("Forcing ping after waiting for all boxes to be ready");
16597672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                    }
16607672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                    HttpResponse res =
16617672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                        sendPing(s.toByteArray(), forcePing ? PING_FORCE_HEARTBEAT : pingHeartbeat);
1662c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
16631b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    int code = res.getStatusLine().getStatusCode();
16641b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    userLog("Ping response: ", code);
1665368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
16661b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // Return immediately if we've been asked to stop during the ping
16671b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    if (mStop) {
16681b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        userLog("Stopping pingLoop");
16691b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        return;
16701b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    }
16711b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
16721b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    if (code == HttpStatus.SC_OK) {
1673b1ffc81c7e3ee9297133a7924092192998ab3839Marc Blank                        // Make sure to clear out any pending sync errors
1674b1ffc81c7e3ee9297133a7924092192998ab3839Marc Blank                        SyncManager.removeFromSyncErrorMap(mMailboxId);
16751b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        HttpEntity e = res.getEntity();
16761b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        int len = (int)e.getContentLength();
16771b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        InputStream is = res.getEntity().getContent();
167895e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        if (len != 0) {
16798d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            int pingResult = parsePingResult(is, mContentResolver, pingErrorMap);
16807672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                            // If our ping completed (status = 1), and we weren't forced and we're
16817672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                            // not at the maximum, try increasing timeout by two minutes
16828d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            if (pingResult == PROTOCOL_PING_STATUS_COMPLETED && !forcePing) {
16837672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                if (pingHeartbeat > mPingHighWaterMark) {
16847672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    mPingHighWaterMark = pingHeartbeat;
16857672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    userLog("Setting high water mark at: ", mPingHighWaterMark);
16867672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                }
16877672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                if ((pingHeartbeat < PING_MAX_HEARTBEAT) &&
16887672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                        !mPingHeartbeatDropped) {
16897672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    pingHeartbeat += PING_HEARTBEAT_INCREMENT;
16907672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    if (pingHeartbeat > PING_MAX_HEARTBEAT) {
16917672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                        pingHeartbeat = PING_MAX_HEARTBEAT;
16927672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    }
16937672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    userLog("Increasing ping heartbeat to ", pingHeartbeat, "s");
16941b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                }
16951b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            }
16961b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        } else {
16971b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            userLog("Ping returned empty result; throwing IOException");
16981b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            throw new IOException();
16991b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        }
17001b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    } else if (isAuthError(code)) {
17011b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        mExitStatus = EXIT_LOGIN_FAILURE;
17021b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        userLog("Authorization error during Ping: ", code);
1703ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        throw new IOException();
1704ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
17051b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                } catch (IOException e) {
1706252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    String message = e.getMessage();
17071b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // If we get the exception that is indicative of a NAT timeout and if we
17081b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // haven't yet "fixed" the timeout, back off by two minutes and "fix" it
1709252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    boolean hasMessage = message != null;
1710252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    userLog("IOException runPingLoop: " + (hasMessage ? message : "[no message]"));
1711e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    if (mPostReset) {
1712e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        // Nothing to do in this case; this is SyncManager telling us to try another
1713e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        // ping.
17144ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank                    } else if (mPostAborted || isLikelyNatFailure(message)) {
1715b9781ea88a58e746b74076ec499e9885e195acc9Marc Blank                        long pingLength = SystemClock.elapsedRealtime() - pingTime;
17167ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                        if ((pingHeartbeat > PING_MIN_HEARTBEAT) &&
17177ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                                (pingHeartbeat > mPingHighWaterMark)) {
17181b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            pingHeartbeat -= PING_HEARTBEAT_INCREMENT;
17191b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            mPingHeartbeatDropped = true;
17201b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            if (pingHeartbeat < PING_MIN_HEARTBEAT) {
17211b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                pingHeartbeat = PING_MIN_HEARTBEAT;
17221b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            }
17231b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            userLog("Decreased ping heartbeat to ", pingHeartbeat, "s");
1724e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        } else if (mPostAborted) {
1725e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            // There's no point in throwing here; this can happen in two cases
1726e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            // 1) An alarm, which indicates minutes without activity; no sense
1727e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            //    backing off
1728e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            // 2) SyncManager abort, due to sync of mailbox.  Again, we want to
1729e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            //    keep on trying to ping
1730e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            userLog("Ping aborted; retry");
1731e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        } else if (pingLength < 2000) {
1732b9781ea88a58e746b74076ec499e9885e195acc9Marc Blank                            userLog("Abort or NAT type return < 2 seconds; throwing IOException");
173374c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                            throw e;
173474c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                        } else {
17354ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank                            userLog("NAT type IOException");
17361b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        }
17372675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank                    } else if (hasMessage && message.contains("roken pipe")) {
17382675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank                        // The "broken pipe" error (uppercase or lowercase "b") seems to be an
17392675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank                        // internal error, so let's not throw an exception (which leads to delays)
17402675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank                        // but rather simply run through the loop again
17411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    } else {
17421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        throw e;
17431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    }
1744ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
17452033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            } else if (forcePing) {
17462033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                // In this case, there aren't any boxes that are pingable, but there are boxes
17472033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                // waiting (for IOExceptions)
17482033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                userLog("pingLoop waiting 60s for any pingable boxes");
17492033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(60*SECONDS, true);
1750ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else if (pushCount > 0) {
17511ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank                // If we want to Ping, but can't just yet, wait a little bit
17521ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank                // TODO Change sleep to wait and use notify from SyncManager when a sync ends
17532033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(2*SECONDS, false);
17547672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                pingWaitCount++;
17552033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                //userLog("pingLoop waited 2s for: ", (pushCount - canPushCount), " box(es)");
17568a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            } else if (uninitCount > 0) {
17578a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // In this case, we're doing an initial sync of at least one mailbox.  Since this
17588a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // is typically a one-time case, I'm ok with trying again every 10 seconds until
17598a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // we're in one of the other possible states.
17602033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                userLog("pingLoop waiting for initial sync of ", uninitCount, " box(es)");
17612033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(10*SECONDS, true);
1762ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
1763c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                // We've got nothing to do, so we'll check again in 30 minutes at which time
17641b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                // we'll update the folder list.  Let the device sleep in the meantime...
17652033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                userLog("pingLoop sleeping for 30m");
17662033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(30*MINUTES, true);
1767ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1768ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
17692ff60f148dfc0ea1b35168a01d8c48de5658e073Marc Blank
17702ff60f148dfc0ea1b35168a01d8c48de5658e073Marc Blank        // Save away the current heartbeat
17712ff60f148dfc0ea1b35168a01d8c48de5658e073Marc Blank        mPingHeartbeat = pingHeartbeat;
1772ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1773ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
17744ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank    private void sleep(long ms, boolean runAsleep) {
17752033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        if (runAsleep) {
17762033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            SyncManager.runAsleep(mMailboxId, ms+(5*SECONDS));
17772033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        }
1778ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
1779ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Thread.sleep(ms);
1780ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (InterruptedException e) {
1781ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Doesn't matter whether we stop early; it's the thought that counts
17822033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        } finally {
17832033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            if (runAsleep) {
17842033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                SyncManager.runAwake(mMailboxId);
17852033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            }
1786ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1787ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1788ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
17898d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank    private int parsePingResult(InputStream is, ContentResolver cr,
17908d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            HashMap<String, Integer> errorMap)
1791ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        throws IOException, StaleFolderListException {
17928047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        PingParser pp = new PingParser(is, this);
1793ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (pp.parse()) {
1794ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // True indicates some mailboxes need syncing...
1795ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // syncList has the serverId's of the mailboxes...
1796ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mBindArguments[0] = Long.toString(mAccount.mId);
17971b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            mPingChangeList = pp.getSyncList();
17981b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            for (String serverId: mPingChangeList) {
1799d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                mBindArguments[1] = serverId;
1800ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
1801ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
1802ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                try {
1803ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (c.moveToFirst()) {
18048d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank
18058d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        /**
18068d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * Check the boxes reporting changes to see if there really were any...
18078d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * We do this because bugs in various Exchange servers can put us into a
18088d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * looping behavior by continually reporting changes in a mailbox, even when
18098d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * there aren't any.
18108d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         *
18118d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * This behavior is seemingly random, and therefore we must code defensively
18128d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * by backing off of push behavior when it is detected.
18138d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         *
18148d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * One known cause, on certain Exchange 2003 servers, is acknowledged by
18158d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * Microsoft, and the server hotfix for this case can be found at
18168d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * http://support.microsoft.com/kb/923282
18178d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         */
18188d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank
18198d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        // Check the status of the last sync
18208d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        String status = c.getString(Mailbox.CONTENT_SYNC_STATUS_COLUMN);
18218d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        int type = SyncManager.getStatusType(status);
18228d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        // This check should always be true...
18238d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        if (type == SyncManager.SYNC_PING) {
18248d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            int changeCount = SyncManager.getStatusChangeCount(status);
18258d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            if (changeCount > 0) {
18268d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                errorMap.remove(serverId);
18278d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            } else if (changeCount == 0) {
18288d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                // This means that a ping reported changes in error; we keep a count
18298d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                // of consecutive errors of this kind
18308d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                String name = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN);
18318d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                Integer failures = errorMap.get(serverId);
18328d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                if (failures == null) {
18338d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    userLog("Last ping reported changes in error for: ", name);
18348d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    errorMap.put(serverId, 1);
18358d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                } else if (failures > MAX_PING_FAILURES) {
18368d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    // We'll back off of push for this box
18378d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    pushFallback(c.getLong(Mailbox.CONTENT_ID_COLUMN));
18388d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    continue;
18398d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                } else {
18408d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    userLog("Last ping reported changes in error for: ", name);
18418d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    errorMap.put(serverId, failures + 1);
18428d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                }
18438d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            }
18448d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        }
18458d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank
18468d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        // If there were no problems with previous sync, we'll start another one
18474d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                        SyncManager.startManualSync(c.getLong(Mailbox.CONTENT_ID_COLUMN),
1848ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank                                SyncManager.SYNC_PING, null);
1849ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
1850ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } finally {
1851ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    c.close();
1852ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1853ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1854ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
18551b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return pp.getSyncStatus();
1856ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1857ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
18585862a85e17e81866ca82a9905577931947fbd44eMarc Blank    private String getEmailFilter() {
1859368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        String filter = Eas.FILTER_1_WEEK;
1860368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        switch (mAccount.mSyncLookback) {
1861368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_DAY: {
1862368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_DAY;
1863368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1864368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1865368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_3_DAYS: {
1866368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_3_DAYS;
1867368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1868368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1869368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_WEEK: {
1870368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_WEEK;
1871368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1872368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1873368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_2_WEEKS: {
1874368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_2_WEEKS;
1875368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1876368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1877368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_MONTH: {
1878368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_MONTH;
1879368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1880368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1881368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_ALL: {
1882368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_ALL;
1883368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1884368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1885368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        }
1886368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        return filter;
1887368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
1888368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
1889ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
1890ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Common code to sync E+PIM data
1891ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
1892ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @param target, an EasMailbox, EasContacts, or EasCalendar object
1893ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
18947c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank    public void sync(AbstractSyncAdapter target) throws IOException {
1895ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        Mailbox mailbox = target.mMailbox;
1896ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1897ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        boolean moreAvailable = true;
1898ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        while (!mStop && moreAvailable) {
18991b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // If we have no connectivity, just exit cleanly.  SyncManager will start us up again
19001b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // when connectivity has returned
19011b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (!hasConnectivity()) {
19021b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                userLog("No connectivity in sync; finishing sync");
19031b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                mExitStatus = EXIT_DONE;
19041b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                return;
1905aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank            }
1906aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank
1907aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank            // Every time through the loop we check to see if we're still syncable
1908aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank            if (!target.isSyncable()) {
1909aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank                mExitStatus = EXIT_DONE;
1910aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank                return;
19111b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
1912ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
19137531be7774769c84b499b1de5dc46da3a9468316Marc Blank            // Now, handle various requests
1914d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            while (true) {
19157531be7774769c84b499b1de5dc46da3a9468316Marc Blank                Request req = null;
19167531be7774769c84b499b1de5dc46da3a9468316Marc Blank                synchronized (mRequests) {
19177531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    if (mRequests.isEmpty()) {
1918d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        break;
1919d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    } else {
19207531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        req = mRequests.get(0);
1921d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    }
1922d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
19237531be7774769c84b499b1de5dc46da3a9468316Marc Blank
19247531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // Our two request types are PartRequest (loading attachment) and
19257531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // MeetingResponseRequest (respond to a meeting request)
19267531be7774769c84b499b1de5dc46da3a9468316Marc Blank                if (req instanceof PartRequest) {
19277531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    getAttachment((PartRequest)req);
19287531be7774769c84b499b1de5dc46da3a9468316Marc Blank                } else if (req instanceof MeetingResponseRequest) {
19297531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    sendMeetingResponse((MeetingResponseRequest)req);
19307531be7774769c84b499b1de5dc46da3a9468316Marc Blank                }
19317531be7774769c84b499b1de5dc46da3a9468316Marc Blank
19327531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // If there's an exception handling the request, we'll throw it
19337531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // Otherwise, we remove the request
19347531be7774769c84b499b1de5dc46da3a9468316Marc Blank                synchronized(mRequests) {
19357531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    mRequests.remove(req);
1936d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
1937d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            }
1938d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
19397c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            Serializer s = new Serializer();
1940c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank
1941ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String className = target.getCollectionName();
1942c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank
1943c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank            // STOPSHIP Remove the following if statement; temporary logging for Calendar sync
19449972a855025df83129b9c7de4010024188219bd3Marc Blank            if (className.equals("Calendar") && Eas.PARSER_LOG) {
1945c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank                s = new Serializer(true, true);
1946c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank            }
1947c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank
194848af7392c82262d17700e3fbdccf3a582809d449Marc Blank            String syncKey = target.getSyncKey();
19498d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            userLog("sync, sending ", className, " syncKey: ", syncKey);
19507c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.start(Tags.SYNC_SYNC)
19517c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .start(Tags.SYNC_COLLECTIONS)
19527c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .start(Tags.SYNC_COLLECTION)
19537c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .data(Tags.SYNC_CLASS, className)
195448af7392c82262d17700e3fbdccf3a582809d449Marc Blank                .data(Tags.SYNC_SYNC_KEY, syncKey)
19557c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId)
19567c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .tag(Tags.SYNC_DELETES_AS_MOVES);
1957ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
195816b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            // Start with the default timeout
195916b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            int timeout = COMMAND_TIMEOUT;
196048af7392c82262d17700e3fbdccf3a582809d449Marc Blank            if (!syncKey.equals("0")) {
196116b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                // EAS doesn't like GetChanges if the syncKey is "0"; not documented
19627c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.tag(Tags.SYNC_GET_CHANGES);
196316b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            } else {
19649e029e58959e581637a1288a1ff9ef44cad63576Marc Blank                // Use enormous timeout for initial sync, which empirically can take a while longer
19659e029e58959e581637a1288a1ff9ef44cad63576Marc Blank                timeout = 120*SECONDS;
1966ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
19671b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            s.data(Tags.SYNC_WINDOW_SIZE,
19681b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                    className.equals("Email") ? EMAIL_WINDOW_SIZE : PIM_WINDOW_SIZE);
1969895d1e3132622653160516d420231ed366ab411bMarc Blank
1970895d1e3132622653160516d420231ed366ab411bMarc Blank            // Handle options
1971895d1e3132622653160516d420231ed366ab411bMarc Blank            s.start(Tags.SYNC_OPTIONS);
1972895d1e3132622653160516d420231ed366ab411bMarc Blank            // Set the lookback appropriately (EAS calls this a "filter") for all but Contacts
19735862a85e17e81866ca82a9905577931947fbd44eMarc Blank            if (className.equals("Email")) {
19745862a85e17e81866ca82a9905577931947fbd44eMarc Blank                s.data(Tags.SYNC_FILTER_TYPE, getEmailFilter());
19755862a85e17e81866ca82a9905577931947fbd44eMarc Blank            } else if (className.equals("Calendar")) {
197616b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                // TODO Force two weeks for calendar until we can set this!
197716b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                s.data(Tags.SYNC_FILTER_TYPE, Eas.FILTER_2_WEEKS);
1978ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1979895d1e3132622653160516d420231ed366ab411bMarc Blank            // Set the truncation amount for all classes
1980d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            if (mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
19817c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.start(Tags.BASE_BODY_PREFERENCE)
1982368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                    // HTML for email; plain text for everything else
1983c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                    .data(Tags.BASE_TYPE, (className.equals("Email") ? Eas.BODY_PREFERENCE_HTML
1984895d1e3132622653160516d420231ed366ab411bMarc Blank                        : Eas.BODY_PREFERENCE_TEXT))
1985895d1e3132622653160516d420231ed366ab411bMarc Blank                    .data(Tags.BASE_TRUNCATION_SIZE, Eas.EAS12_TRUNCATION_SIZE)
19867c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    .end();
1987895d1e3132622653160516d420231ed366ab411bMarc Blank            } else {
1988895d1e3132622653160516d420231ed366ab411bMarc Blank                s.data(Tags.SYNC_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
1989ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1990895d1e3132622653160516d420231ed366ab411bMarc Blank            s.end();
1991ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1992ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Send our changes up to the server
199348af7392c82262d17700e3fbdccf3a582809d449Marc Blank            target.sendLocalChanges(s);
1994ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
19957c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.end().end().end().done();
199616b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            HttpResponse resp = sendHttpClientPost("Sync", new ByteArrayEntity(s.toByteArray()),
199716b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                    timeout);
19988047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            int code = resp.getStatusLine().getStatusCode();
19991b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (code == HttpStatus.SC_OK) {
200095e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                InputStream is = resp.getEntity().getContent();
2001ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                if (is != null) {
200248af7392c82262d17700e3fbdccf3a582809d449Marc Blank                    moreAvailable = target.parse(is);
200348af7392c82262d17700e3fbdccf3a582809d449Marc Blank                    target.cleanup();
20048d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                } else {
20058d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                    userLog("Empty input stream in sync command response");
2006ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
2007ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
20080a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("Sync response error: ", code);
200920da011530088036d2bf45d3836d6986a4b5d423Marc Blank                if (isProvisionError(code)) {
201095fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    mExitStatus = EXIT_SECURITY_FAILURE;
201120da011530088036d2bf45d3836d6986a4b5d423Marc Blank                } else if (isAuthError(code)) {
20121b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_LOGIN_FAILURE;
20131b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                } else {
20141b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_IO_ERROR;
2015ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
2016ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return;
2017ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
2018ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
20191b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        mExitStatus = EXIT_DONE;
2020ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
2021ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
202222e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank    protected boolean setupService() {
20237ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        // Make sure account and mailbox are always the latest from the database
20247ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
202522e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (mAccount == null) return false;
20267ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId);
202722e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (mMailbox == null) return false;
2028ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mThread = Thread.currentThread();
20297310cbacf2cf614c949330faff3882082054c120Marc Blank        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
2030ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        TAG = mThread.getName();
20319d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
2032ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
203322e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (ha == null) return false;
2034ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mHostAddress = ha.mAddress;
2035ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mUserName = ha.mLogin;
2036ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mPassword = ha.mPassword;
20373852792f1c79dcfca6dc9be67f11e4a53788a462Marc Blank
2038fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        // Set up our protocol version from the Account
20393852792f1c79dcfca6dc9be67f11e4a53788a462Marc Blank        mProtocolVersion = mAccount.mProtocolVersion;
2040fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        // If it hasn't been set up, start with default version
2041fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        if (mProtocolVersion == null) {
2042d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            mProtocolVersion = Eas.DEFAULT_PROTOCOL_VERSION;
204354250c7c23b43b644961a8f241a3a3edcc9c796eMarc Blank        }
2044fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
204522e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        return true;
20467ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    }
20477ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank
20487ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    /* (non-Javadoc)
20497ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank     * @see java.lang.Runnable#run()
20507ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank     */
20517ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    public void run() {
205222e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (!setupService()) return;
2053ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
2054fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        try {
2055fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().syncMailboxStatus(mMailboxId, EmailServiceStatus.IN_PROGRESS, 0);
2056fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e1) {
2057fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // Don't care if this fails
2058fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        }
2059fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
2060a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank        // Whether or not we're the account mailbox
2061ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
20629d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            mDeviceId = SyncManager.getDeviceId();
20637ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank            if ((mMailbox == null) || (mAccount == null)) {
2064147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank                return;
20657c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            } else if (mMailbox.mType == Mailbox.TYPE_EAS_ACCOUNT_MAILBOX) {
2066a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank                runAccountMailbox();
2067ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
20687c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                AbstractSyncAdapter target;
20697cf921b830554e52f88a45ca4a290b17d2a1b146Marc Blank                if (mMailbox.mType == Mailbox.TYPE_CONTACTS) {
20707c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    target = new ContactsSyncAdapter(mMailbox, this);
20715862a85e17e81866ca82a9905577931947fbd44eMarc Blank                } else if (mMailbox.mType == Mailbox.TYPE_CALENDAR) {
20725862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    target = new CalendarSyncAdapter(mMailbox, this);
20737cf921b830554e52f88a45ca4a290b17d2a1b146Marc Blank                } else {
20747c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    target = new EmailSyncAdapter(mMailbox, this);
2075ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
2076ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // We loop here because someone might have put a request in while we were syncing
2077ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // and we've missed that opportunity...
2078ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                do {
2079ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (mRequestTime != 0) {
2080ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        userLog("Looping for user request...");
2081ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        mRequestTime = 0;
2082ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
2083ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    sync(target);
2084ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } while (mRequestTime != 0);
2085ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
20867531be7774769c84b499b1de5dc46da3a9468316Marc Blank        } catch (EasAuthenticationException e) {
20877531be7774769c84b499b1de5dc46da3a9468316Marc Blank            userLog("Caught authentication error");
20887531be7774769c84b499b1de5dc46da3a9468316Marc Blank            mExitStatus = EXIT_LOGIN_FAILURE;
2089ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (IOException e) {
2090f9423affa52661c2f552df35f0b9ddeecd8fa8feMarc Blank            String message = e.getMessage();
20914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("Caught IOException: ", (message == null) ? "No message" : message);
2092ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mExitStatus = EXIT_IO_ERROR;
2093ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (Exception e) {
20941431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank            userLog("Uncaught exception in EasSyncService", e);
2095ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
20962fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank            int status;
20972fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank
20984d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank            if (!mStop) {
20998d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                userLog("Sync finished");
21004d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                SyncManager.done(this);
21015c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                switch (mExitStatus) {
21025c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_IO_ERROR:
21035c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.CONNECTION_ERROR;
21045c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
21055c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_DONE:
21065c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.SUCCESS;
21072fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        ContentValues cv = new ContentValues();
21082fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
21092fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        String s = "S" + mSyncReason + ':' + status + ':' + mChangeCount;
21102fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        cv.put(Mailbox.SYNC_STATUS, s);
21112fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI,
21122fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                                mMailboxId), cv, null, null);
21135c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
21145c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_LOGIN_FAILURE:
21155c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.LOGIN_FAILED;
21165c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
211720da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    case EXIT_SECURITY_FAILURE:
211820da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        status = EmailServiceStatus.SECURITY_FAILURE;
21197726978228698ed6ee155723065b3c06a880ebe2Marc Blank                        // Ask for a new folder list.  This should wake up the account mailbox; a
21207726978228698ed6ee155723065b3c06a880ebe2Marc Blank                        // security error in account mailbox should start the provisioning process
21217726978228698ed6ee155723065b3c06a880ebe2Marc Blank                        SyncManager.reloadFolderList(mContext, mAccount.mId, true);
212220da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        break;
21235c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    default:
21245c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.REMOTE_EXCEPTION;
212577424af660458104b732bdcb718874b17d0cab3aMarc Blank                        errorLog("Sync ended due to an exception.");
21265c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
21275c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                }
21284d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank            } else {
21298d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                userLog("Stopped sync finished.");
21302fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                status = EmailServiceStatus.SUCCESS;
21312fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank            }
21322fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank
21332fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank            try {
21342fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                SyncManager.callback().syncMailboxStatus(mMailboxId, status, 0);
21352fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank            } catch (RemoteException e1) {
21362fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                // Don't care if this fails
2137fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
21389d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
2139c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            // Make sure SyncManager knows about this
2140c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            SyncManager.kick("sync finished");
21419d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank       }
2142ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
2143ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
2144