EasSyncService.java revision 16b445cd6c4de57ae144fe76449ac6953333f0e9
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;
9600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.net.URI;
9700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.net.URLEncoder;
98e44d5875af006f4217718a1c0fc0e235af3863afMarc Blankimport java.security.cert.CertificateException;
9900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.ArrayList;
100ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankimport java.util.HashMap;
10100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
102ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankpublic class EasSyncService extends AbstractSyncService {
103a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler    // STOPSHIP - DO NOT RELEASE AS 'TRUE'
104a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler    public static final boolean DEBUG_GAL_SERVICE = true;
105a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler
1061b275b9408d5b856e2482fa3951827489e9585ccMarc Blank    private static final String EMAIL_WINDOW_SIZE = "5";
107726d60d9b758f0383f8f8481190fc1a638427209Marc Blank    public static final String PIM_WINDOW_SIZE = "5";
108ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID =
109ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SERVER_ID + "=?";
1109d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private static final String WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING =
1119d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank        MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL +
112f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        '=' + Mailbox.CHECK_INTERVAL_PING;
11322bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank    private static final String AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX = " AND " +
114f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        MailboxColumns.SYNC_INTERVAL + " IN (" + Mailbox.CHECK_INTERVAL_PING +
115f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        ',' + Mailbox.CHECK_INTERVAL_PUSH + ") AND " + MailboxColumns.TYPE + "!=\"" +
1167c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank        Mailbox.TYPE_EAS_ACCOUNT_MAILBOX + '\"';
1179d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private static final String WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX =
1189d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank        MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL +
119f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank        '=' + Mailbox.CHECK_INTERVAL_PUSH_HOLD;
1208047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    static private final int CHUNK_SIZE = 16*1024;
1218047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
1228047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    static private final String PING_COMMAND = "Ping";
12316b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // Command timeout is the the time allowed for reading data from an open connection before an
12416b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // IOException is thrown.  After a small added allowance, our watchdog alarm goes off (allowing
12516b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // us to detect a silently dropped connection).  The allowance is defined below.
126c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    static private final int COMMAND_TIMEOUT = 20*SECONDS;
12716b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // Connection timeout is the time given to connect to the server before reporting an IOException
12816b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    static private final int CONNECTION_TIMEOUT = 30*SECONDS;
12916b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // The extra time allowed beyond the COMMAND_TIMEOUT before which our watchdog alarm triggers
13016b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    static private final int WATCHDOG_TIMEOUT_ALLOWANCE = 10*SECONDS;
131c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
1324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    static private final String AUTO_DISCOVER_SCHEMA_PREFIX =
1334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        "http://schemas.microsoft.com/exchange/autodiscover/mobilesync/";
1344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    static private final String AUTO_DISCOVER_PAGE = "/autodiscover/autodiscover.xml";
1354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    static private final int AUTO_DISCOVER_REDIRECT_CODE = 451;
1364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
137b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    static public final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML";
138b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    static public final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML";
139b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
1401b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    /**
1411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * We start with an 8 minute timeout, and increase/decrease by 3 minutes at a time.  There's
1421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * no point having a timeout shorter than 5 minutes, I think; at that point, we can just let
1431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * the ping exception out.  The maximum I use is 17 minutes, which is really an empirical
1441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * choice; too long and we risk silent connection loss and loss of push for that period.  Too
1451b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * short and we lose efficiency/battery life.
1461b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     *
1471b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * If we ever have to drop the ping timeout, we'll never increase it again.  There's no point
1481b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * going into hysteresis; the NAT timeout isn't going to change without a change in connection,
1491b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * which will cause the sync service to be restarted at the starting heartbeat and going through
1501b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * the process again.
1511b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     */
1521b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_MINUTES = 60; // in seconds
1531b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_FUDGE_LOW = 10;
1541b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_STARTING_HEARTBEAT = (8*PING_MINUTES)-PING_FUDGE_LOW;
1551b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_MIN_HEARTBEAT = (5*PING_MINUTES)-PING_FUDGE_LOW;
1561b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_MAX_HEARTBEAT = (17*PING_MINUTES)-PING_FUDGE_LOW;
1571b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PING_HEARTBEAT_INCREMENT = 3*PING_MINUTES;
1587672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank    static private final int PING_FORCE_HEARTBEAT = 2*PING_MINUTES;
1591b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
1601b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    static private final int PROTOCOL_PING_STATUS_COMPLETED = 1;
16196293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank
162c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    // Fallbacks (in minutes) for ping loop failures
163252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank    static private final int MAX_PING_FAILURES = 1;
164c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    static private final int PING_FALLBACK_INBOX = 5;
16527cf341571fac3d8dbe866f503c34fc31e02bf85Marc Blank    static private final int PING_FALLBACK_PIM = 25;
166d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
1678692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    // MSFT's custom HTTP result code indicating the need to provision
1688692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    static private final int HTTP_NEED_PROVISIONING = 449;
1698692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
170ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // Reasonable default
171d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank    public String mProtocolVersion = Eas.DEFAULT_PROTOCOL_VERSION;
17200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank    public Double mProtocolVersionDouble;
17385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank    protected String mDeviceId = null;
1749d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private String mDeviceType = "Android";
1751b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private String mAuthString = null;
1761b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private String mCmdString = null;
177ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mHostAddress;
178ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mUserName;
179ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mPassword;
1801b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private boolean mSsl = true;
1815843b85178a359446f81770ed7734604a1b2fa7dMarc Blank    private boolean mTrustSsl = false;
182ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public ContentResolver mContentResolver;
1831b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private String[] mBindArguments = new String[2];
1841b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private ArrayList<String> mPingChangeList;
1851b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private HttpPost mPendingPost = null;
1861b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    // The ping time (in seconds)
1871b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private int mPingHeartbeat = PING_STARTING_HEARTBEAT;
1881b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    // The longest successful ping heartbeat
1891b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private int mPingHighWaterMark = 0;
1901b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    // Whether we've ever lowered the heartbeat
1911b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private boolean mPingHeartbeatDropped = false;
192e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    // Whether a POST was aborted due to alarm (watchdog alarm)
193ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank    private boolean mPostAborted = false;
194e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    // Whether a POST was aborted due to reset
195e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    private boolean mPostReset = false;
196ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank    // Whether or not the sync service is valid (usable)
197ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank    public boolean mIsValid = true;
198ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
199ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public EasSyncService(Context _context, Mailbox _mailbox) {
200ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        super(_context, _mailbox);
201ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mContentResolver = _context.getContentResolver();
202ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        if (mAccount == null) {
203ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            mIsValid = false;
204ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            return;
205ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        }
206ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv);
207ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        if (ha == null) {
208ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            mIsValid = false;
209ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            return;
210ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        }
211ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
212e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        mTrustSsl = (ha.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES) != 0;
213ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
214ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
215ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private EasSyncService(String prefix) {
216ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        super(prefix);
217ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
218ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
219ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public EasSyncService() {
220ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        this("EAS Validation");
221ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
222ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
223ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
224e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    public void alarm() {
2251b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        synchronized(getSynchronizer()) {
2261b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (mPendingPost != null) {
227e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                URI uri = mPendingPost.getURI();
228e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                if (uri != null) {
229e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    String query = uri.getQuery();
230e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    if (query == null) {
231e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        query = "POST";
232e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    }
233e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    userLog("Alert, aborting " + query);
234e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                } else {
235e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    userLog("Alert, no URI?");
236e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                }
237ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank                mPostAborted = true;
2381b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                mPendingPost.abort();
239e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank            } else {
240e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                userLog("Alert, no pending POST");
241e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank            }
242e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank        }
243e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    }
244e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank
245e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    @Override
246e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    public void reset() {
247e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank        synchronized(getSynchronizer()) {
248e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank            if (mPendingPost != null) {
249e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                URI uri = mPendingPost.getURI();
250e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                if (uri != null) {
251e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    String query = uri.getQuery();
252e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    if (query.startsWith("Cmd=Ping")) {
253e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        userLog("Reset, aborting Ping");
254e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        mPostReset = true;
255e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        mPendingPost.abort();
256e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    }
257e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                }
2581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
259ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
260ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
261ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
262ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
263ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void stop() {
264ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mStop = true;
265c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        synchronized(getSynchronizer()) {
266c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            if (mPendingPost != null) {
267c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                mPendingPost.abort();
268c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            }
269c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
2705c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank    }
271ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
2721b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    /**
2731b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * Determine whether an HTTP code represents an authentication error
2741b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * @param code the HTTP code returned by the server
2751b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     * @return whether or not the code represents an authentication error
2761b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank     */
277f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank    protected boolean isAuthError(int code) {
2784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return (code == HttpStatus.SC_UNAUTHORIZED) || (code == HttpStatus.SC_FORBIDDEN);
279368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
280368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
28120da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
28220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Determine whether an HTTP code represents a provisioning error
28320da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param code the HTTP code returned by the server
28420da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @return whether or not the code represents an provisioning error
28520da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
28620da011530088036d2bf45d3836d6986a4b5d423Marc Blank    protected boolean isProvisionError(int code) {
28720da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return (code == HTTP_NEED_PROVISIONING) || (code == HttpStatus.SC_FORBIDDEN);
28820da011530088036d2bf45d3836d6986a4b5d423Marc Blank    }
28920da011530088036d2bf45d3836d6986a4b5d423Marc Blank
290d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank    private void setupProtocolVersion(EasSyncService service, Header versionHeader)
291d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            throws MessagingException {
292d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // The string is a comma separated list of EAS versions in ascending order
293d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // e.g. 1.0,2.0,2.5,12.0,12.1
294d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        String supportedVersions = versionHeader.getValue();
295d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        userLog("Server supports versions: ", supportedVersions);
296d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        String[] supportedVersionsArray = supportedVersions.split(",");
297d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        String ourVersion = null;
298d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // Find the most recent version we support
299d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        for (String version: supportedVersionsArray) {
300d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            if (version.equals(Eas.SUPPORTED_PROTOCOL_EX2003) ||
301d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    version.equals(Eas.SUPPORTED_PROTOCOL_EX2007)) {
302d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                ourVersion = version;
3038692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            }
304d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        }
305d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // If we don't support any of the servers supported versions, throw an exception here
306d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // This will cause validation to fail
307d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        if (ourVersion == null) {
308d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            Log.w(TAG, "No supported EAS versions: " + supportedVersions);
309d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            throw new MessagingException(MessagingException.PROTOCOL_VERSION_UNSUPPORTED);
310d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        } else {
311d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            service.mProtocolVersion = ourVersion;
312d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            service.mProtocolVersionDouble = Double.parseDouble(ourVersion);
3138692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            if (service.mAccount != null) {
314d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                service.mAccount.mProtocolVersion = ourVersion;
3158692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            }
3168692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        }
3178692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    }
3188692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
319fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank    @Override
320ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void validateAccount(String hostAddress, String userName, String password, int port,
321e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            boolean ssl, boolean trustCertificates, Context context) throws MessagingException {
322ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
3230a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank            userLog("Testing EAS: ", hostAddress, ", ", userName, ", ssl = ", ssl ? "1" : "0");
324ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            EasSyncService svc = new EasSyncService("%TestAccount%");
3259d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            svc.mContext = context;
326ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mHostAddress = hostAddress;
327ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mUserName = userName;
328ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mPassword = password;
329ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            svc.mSsl = ssl;
330e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            svc.mTrustSsl = trustCertificates;
331704cb199ced39727d84103c7170fc888a54f6c97Marc Blank            // We mustn't use the "real" device id or we'll screw up current accounts
332704cb199ced39727d84103c7170fc888a54f6c97Marc Blank            // Any string will do, but we'll go for "validate"
333704cb199ced39727d84103c7170fc888a54f6c97Marc Blank            svc.mDeviceId = "validate";
3348047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            HttpResponse resp = svc.sendHttpClientOptions();
3358047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            int code = resp.getStatusLine().getStatusCode();
3369d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            userLog("Validation (OPTIONS) response: " + code);
3371b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (code == HttpStatus.SC_OK) {
338ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // No exception means successful validation
3393b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                Header commands = resp.getFirstHeader("MS-ASProtocolCommands");
3403b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                Header versions = resp.getFirstHeader("ms-asprotocolversions");
3413b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                if (commands == null || versions == null) {
3423b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                    userLog("OPTIONS response without commands or versions; reporting I/O error");
3433b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                    throw new MessagingException(MessagingException.IOERROR);
3443b5688726987b9dbf020a35e0f80e3460fb0d838Marc Blank                }
345eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank
3468692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                // Make sure we've got the right protocol version set up
3478692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                setupProtocolVersion(svc, versions);
3488692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
349eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                // Run second test here for provisioning failures...
350eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                Serializer s = new Serializer();
351eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                userLog("Try folder sync");
352eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text("0")
353eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                    .end().end().done();
354eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                resp = svc.sendHttpClientPost("FolderSync", s.toByteArray());
355eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                code = resp.getStatusLine().getStatusCode();
3568692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                // We'll get one of the following responses if policies are required by the server
3578692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                if (code == HttpStatus.SC_FORBIDDEN || code == HTTP_NEED_PROVISIONING) {
3588692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                    // Get the policies and see if we are able to support them
35920da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    if (svc.canProvision() != null) {
3608692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                        // If so, send the advisory Exception (the account may be created later)
3618692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                        throw new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED);
3628692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                    } else
3638692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                        // If not, send the unsupported Exception (the account won't be created)
3648692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                        throw new MessagingException(
3658692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                                MessagingException.SECURITY_POLICIES_UNSUPPORTED);
3666e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                } else if (code == HttpStatus.SC_NOT_FOUND) {
3676e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    // We get a 404 from OWA addresses (which are NOT EAS addresses)
3686e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    throw new MessagingException(MessagingException.PROTOCOL_VERSION_UNSUPPORTED);
3696e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                } else if (code != HttpStatus.SC_OK) {
3706e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    // Fail generically with anything other than success
3716e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    userLog("Unexpected response for FolderSync: ", code);
3726e4eccde96c40935019800fb9034abbdce31aaf8Marc Blank                    throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION);
373eb9517c855a275880ac2cd4dbcca0d0a37b70567Marc Blank                }
374ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                userLog("Validation successful");
375ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return;
376ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
377368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            if (isAuthError(code)) {
378ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                userLog("Authentication failed");
379ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                throw new AuthenticationFailedException("Validation failed");
380ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
381ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // TODO Need to catch other kinds of errors (e.g. policy) For now, report the code.
3820a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("Validation failed, reporting I/O error: ", code);
383ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                throw new MessagingException(MessagingException.IOERROR);
384ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
385ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (IOException e) {
386e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            Throwable cause = e.getCause();
387e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            if (cause != null && cause instanceof CertificateException) {
388e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank                userLog("CertificateException caught: ", e.getMessage());
389e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank                throw new MessagingException(MessagingException.GENERAL_SECURITY);
390e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            }
391e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            userLog("IOException caught: ", e.getMessage());
392ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            throw new MessagingException(MessagingException.IOERROR);
393ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
394ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
395ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
396ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
3974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    /**
3984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * Gets the redirect location from the HTTP headers and uses that to modify the HttpPost so that
3994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * it can be reused
4004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     *
4014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param resp the HttpResponse that indicates a redirect (451)
4024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param post the HttpPost that was originally sent to the server
4034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @return the HttpPost, updated with the redirect location
4044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     */
4054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    private HttpPost getRedirect(HttpResponse resp, HttpPost post) {
4064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        Header locHeader = resp.getFirstHeader("X-MS-Location");
4074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        if (locHeader != null) {
4084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            String loc = locHeader.getValue();
4094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // If we've gotten one and it shows signs of looking like an address, we try
4104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // sending our request there
4114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (loc != null && loc.startsWith("http")) {
4124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                post.setURI(URI.create(loc));
4134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                return post;
4144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
4154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
4164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return null;
4174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
4184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
4194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    /**
4204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * Send the POST command to the autodiscover server, handling a redirect, if necessary, and
4214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * return the HttpResponse
4224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     *
4234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param client the HttpClient to be used for the request
4244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param post the HttpPost we're going to send
4254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @return an HttpResponse from the original or redirect server
4264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @throws IOException on any IOException within the HttpClient code
4274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @throws MessagingException
4284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     */
4294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    private HttpResponse postAutodiscover(HttpClient client, HttpPost post)
4304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws IOException, MessagingException {
4314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        userLog("Posting autodiscover to: " + post.getURI());
432adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        HttpResponse resp = executePostWithTimeout(client, post, COMMAND_TIMEOUT);
4334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        int code = resp.getStatusLine().getStatusCode();
4344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        // On a redirect, try the new location
4354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        if (code == AUTO_DISCOVER_REDIRECT_CODE) {
4364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            post = getRedirect(resp, post);
4374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (post != null) {
4384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                userLog("Posting autodiscover to redirect: " + post.getURI());
439adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                return executePostWithTimeout(client, post, COMMAND_TIMEOUT);
4404d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
44152f7f7effdd320594cd1c5c8c67443ad51eca3afMarc Blank        } else if (code == HttpStatus.SC_UNAUTHORIZED) {
44252f7f7effdd320594cd1c5c8c67443ad51eca3afMarc Blank            // 401 (Unauthorized) is for true auth errors when used in Autodiscover
44352f7f7effdd320594cd1c5c8c67443ad51eca3afMarc Blank            // 403 (and others) we'll just punt on
4444d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throw new MessagingException(MessagingException.AUTHENTICATION_FAILED);
4454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        } else if (code != HttpStatus.SC_OK) {
4464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // We'll try the next address if this doesn't work
4474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("Code: " + code + ", throwing IOException");
4484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throw new IOException();
4494d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
4504d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return resp;
4514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
4524d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
4534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    /**
4544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * Use the Exchange 2007 AutoDiscover feature to try to retrieve server information using
4554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * only an email address and the password
4564d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     *
4574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param userName the user's email address
4584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param password the user's password
4594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @return a HostAuth ready to be saved in an Account or null (failure)
4604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     */
4614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    public Bundle tryAutodiscover(String userName, String password) throws RemoteException {
4624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        XmlSerializer s = Xml.newSerializer();
4634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
4644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        HostAuth hostAuth = new HostAuth();
4654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        Bundle bundle = new Bundle();
4664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
4674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                MessagingException.NO_ERROR);
4684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        try {
4694d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Build the XML document that's sent to the autodiscover server(s)
4704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.setOutput(os, "UTF-8");
4714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startDocument("UTF-8", false);
4724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "Autodiscover");
4734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.attribute(null, "xmlns", AUTO_DISCOVER_SCHEMA_PREFIX + "requestschema/2006");
4744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "Request");
4754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "EMailAddress").text(userName).endTag(null, "EMailAddress");
4764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "AcceptableResponseSchema");
4774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.text(AUTO_DISCOVER_SCHEMA_PREFIX + "responseschema/2006");
4784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endTag(null, "AcceptableResponseSchema");
4794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endTag(null, "Request");
4804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endTag(null, "Autodiscover");
4814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endDocument();
4824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            String req = os.toString();
4834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
4844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Initialize the user name and password
4854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            mUserName = userName;
4864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            mPassword = password;
4874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Make sure the authentication string is created (mAuthString)
4884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            makeUriString("foo", null);
4894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
4904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Split out the domain name
4914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int amp = userName.indexOf('@');
4924d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // The UI ensures that userName is a valid email address
4934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (amp < 0) {
4944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                throw new RemoteException();
4954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
4964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            String domain = userName.substring(amp + 1);
4974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
4984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // There are up to four attempts here; the two URLs that we're supposed to try per the
4994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // specification, and up to one redirect for each (handled in postAutodiscover)
5004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Try the domain first and see if we can get a response
5024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpPost post = new HttpPost("https://" + domain + AUTO_DISCOVER_PAGE);
50320da011530088036d2bf45d3836d6986a4b5d423Marc Blank            setHeaders(post, false);
5044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            post.setHeader("Content-Type", "text/xml");
5054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            post.setEntity(new StringEntity(req));
5064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpClient client = getHttpClient(COMMAND_TIMEOUT);
5074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpResponse resp;
5084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            try {
5094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                resp = postAutodiscover(client, post);
5104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } catch (IOException e1) {
511adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                userLog("IOException in autodiscover; trying alternate address");
5124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // We catch the IOException here because we have an alternate address to try
5134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                post.setURI(URI.create("https://autodiscover." + domain + AUTO_DISCOVER_PAGE));
5144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // If we fail here, we're out of options, so we let the outer try catch the
5154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // IOException and return null
5164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                resp = postAutodiscover(client, post);
5174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
5184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Get the "final" code; if it's not 200, just return null
5204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int code = resp.getStatusLine().getStatusCode();
5214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("Code: " + code);
5224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (code != HttpStatus.SC_OK) return null;
5234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // At this point, we have a 200 response (SC_OK)
5254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpEntity e = resp.getEntity();
5264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            InputStream is = e.getContent();
5274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            try {
5284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // The response to Autodiscover is regular XML (not WBXML)
5294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // If we ever get an error in this process, we'll just punt and return null
5304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
5314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                XmlPullParser parser = factory.newPullParser();
5324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                parser.setInput(is, "UTF-8");
5334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                int type = parser.getEventType();
5344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (type == XmlPullParser.START_DOCUMENT) {
5354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    type = parser.next();
5364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    if (type == XmlPullParser.START_TAG) {
5374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        String name = parser.getName();
5384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        if (name.equals("Autodiscover")) {
5394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            hostAuth = new HostAuth();
5404d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            parseAutodiscover(parser, hostAuth);
5414d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            // On success, we'll have a server address and login
5424d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            if (hostAuth.mAddress != null && hostAuth.mLogin != null) {
5434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                // Fill in the rest of the HostAuth
5444d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mPassword = password;
5454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mPort = 443;
5464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mProtocol = "eas";
5474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mFlags =
5484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                    HostAuth.FLAG_SSL | HostAuth.FLAG_AUTHENTICATE;
5494d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                bundle.putParcelable(
5504d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                        EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH, hostAuth);
5514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            } else {
5524d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
5534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                        MessagingException.UNSPECIFIED_EXCEPTION);
5544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            }
5554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        }
5564d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    }
5574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
5584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } catch (XmlPullParserException e1) {
5594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // This would indicate an I/O error of some sort
5604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // We will simply return null and user can configure manually
5614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
5624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        // There's no reason at all for exceptions to be thrown, and it's ok if so.
5634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        // We just won't do auto-discover; user can configure manually
5644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank       } catch (IllegalArgumentException e) {
5654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank             bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
5664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                     MessagingException.UNSPECIFIED_EXCEPTION);
5674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank       } catch (IllegalStateException e) {
5684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
5694d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    MessagingException.UNSPECIFIED_EXCEPTION);
5704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank       } catch (IOException e) {
5714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("IOException in Autodiscover", e);
5724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
5734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    MessagingException.IOERROR);
5744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        } catch (MessagingException e) {
5754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
5764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    MessagingException.AUTHENTICATION_FAILED);
5774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
5784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return bundle;
5794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
5804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
5814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseServer(XmlPullParser parser, HostAuth hostAuth)
5824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
5834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        boolean mobileSync = false;
5844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
5854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
5864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Server")) {
5874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
5884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
5894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
5904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("Type")) {
5914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    if (parser.nextText().equals("MobileSync")) {
5924d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        mobileSync = true;
5934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    }
5944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (mobileSync && name.equals("Url")) {
5954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    String url = parser.nextText().toLowerCase();
5964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    // This will look like https://<server address>/Microsoft-Server-ActiveSync
5974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    // We need to extract the <server address>
5984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    if (url.startsWith("https://") &&
5994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            url.endsWith("/microsoft-server-activesync")) {
6004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        int lastSlash = url.lastIndexOf('/');
6014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        hostAuth.mAddress = url.substring(8, lastSlash);
6024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        userLog("Autodiscover, server: " + hostAuth.mAddress);
6034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    }
6044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
6054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseSettings(XmlPullParser parser, HostAuth hostAuth)
6104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
6114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
6124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
6134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Settings")) {
6144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
6154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
6164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
6174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("Server")) {
6184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseServer(parser, hostAuth);
6194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
6204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseAction(XmlPullParser parser, HostAuth hostAuth)
6254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
6264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
6274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
6284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Action")) {
6294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
6304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
6314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
6324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("Error")) {
6334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    // Should parse the error
6344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("Redirect")) {
6354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    Log.d(TAG, "Redirect: " + parser.nextText());
6364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("Settings")) {
6374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseSettings(parser, hostAuth);
6384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
6394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6404d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6414d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6424d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseUser(XmlPullParser parser, HostAuth hostAuth)
6444d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
6454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
6464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
6474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("User")) {
6484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
6494d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
6504d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
6514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("EMailAddress")) {
6524d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    String addr = parser.nextText();
6534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    hostAuth.mLogin = addr;
6544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    userLog("Autodiscover, login: " + addr);
6554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("DisplayName")) {
6564d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    String dn = parser.nextText();
6574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    userLog("Autodiscover, user: " + dn);
6584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
6594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseResponse(XmlPullParser parser, HostAuth hostAuth)
6644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
6654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
6664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
6674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Response")) {
6684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
6694d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
6704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
6714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("User")) {
6724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseUser(parser, hostAuth);
6734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("Action")) {
6744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseAction(parser, hostAuth);
6754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
6764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseAutodiscover(XmlPullParser parser, HostAuth hostAuth)
6814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
6824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
6834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.nextTag();
6844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Autodiscover")) {
6854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
6864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG && parser.getName().equals("Response")) {
6874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                parseResponse(parser, hostAuth);
6884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
69296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    /**
69396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * Contact the GAL and obtain a list of matching accounts
69496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @param context caller's context
69596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @param accountId the account Id to search
69696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @param filter the characters entered so far
69796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @return a result record
69896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     *
69996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * TODO: shorter timeout for interactive lookup
70096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * TODO: make watchdog actually work (it doesn't understand our service w/Mailbox == 0)
70196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * TODO: figure out why sendHttpClientPost() hangs - possibly pool exhaustion
70296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     */
70396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    static public GalResult searchGal(Context context, long accountId, String filter)
70496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    {
705114f17e8efea93380680417af4503c8e6820c394Marc Blank        Account acct = SyncManager.getAccountById(accountId);
70696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler        if (acct != null) {
70796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            HostAuth ha = HostAuth.restoreHostAuthWithId(context, acct.mHostAuthKeyRecv);
708a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler            EasSyncService svc = new EasSyncService("%GalLookupk%");
70996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            try {
71096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mContext = context;
71196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mHostAddress = ha.mAddress;
71296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mUserName = ha.mLogin;
71396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mPassword = ha.mPassword;
71496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
71596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mTrustSsl = (ha.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES) != 0;
71696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                svc.mDeviceId = SyncManager.getDeviceId();
7177b377bf23e53f2fda5c7c0f19ddf2f9d1096945eMarc Blank                svc.mAccount = acct;
71896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                Serializer s = new Serializer();
71996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE);
72096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.data(Tags.SEARCH_NAME, "GAL").data(Tags.SEARCH_QUERY, filter);
72196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.start(Tags.SEARCH_OPTIONS);
722a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                s.data(Tags.SEARCH_RANGE, "0-19");  // Return 0..20 results
72396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.end().end().end().done();
724a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                if (DEBUG_GAL_SERVICE) svc.userLog("GAL lookup starting for " + ha.mAddress);
72596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                HttpResponse resp = svc.sendHttpClientPost("Search", s.toByteArray());
72696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                int code = resp.getStatusLine().getStatusCode();
72796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                if (code == HttpStatus.SC_OK) {
72896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                    InputStream is = resp.getEntity().getContent();
72996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                    GalParser gp = new GalParser(is, svc);
73096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                    if (gp.parse()) {
731a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                        if (DEBUG_GAL_SERVICE) svc.userLog("GAL lookup OK for " + ha.mAddress);
73296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                        return gp.getGalResult();
733a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                    } else {
734a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                        if (DEBUG_GAL_SERVICE) svc.userLog("GAL lookup returned no matches");
73596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                    }
736a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                } else {
737a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                    svc.userLog("GAL lookup returned " + code);
73896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                }
73996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            } catch (IOException e) {
74096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                // GAL is non-critical; we'll just go on
741a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                svc.userLog("GAL lookup exception " + e);
74296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            }
74396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler        }
74496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler        return null;
74596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    }
74696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler
74781d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank    private void doStatusCallback(long messageId, long attachmentId, int status) {
748d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        try {
749fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().loadAttachmentStatus(messageId, attachmentId, status, 0);
750fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e) {
751d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            // No danger if the client is no longer around
752d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        }
753d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank    }
754ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
75581d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank    private void doProgressCallback(long messageId, long attachmentId, int progress) {
756d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        try {
757fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().loadAttachmentStatus(messageId, attachmentId,
758fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                    EmailServiceStatus.IN_PROGRESS, progress);
759fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e) {
760d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            // No danger if the client is no longer around
761d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        }
762d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank    }
763d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
764842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank    public File createUniqueFileInternal(String dir, String filename) {
765842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        File directory;
766842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (dir == null) {
767842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            directory = mContext.getFilesDir();
768842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        } else {
769842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            directory = new File(dir);
770842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
771842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (!directory.exists()) {
772842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            directory.mkdirs();
773842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
774842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        File file = new File(directory, filename);
775842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (!file.exists()) {
776842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            return file;
777842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
778842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        // Get the extension of the file, if any.
779842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        int index = filename.lastIndexOf('.');
780842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        String name = filename;
781842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        String extension = "";
782842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        if (index != -1) {
783842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            name = filename.substring(0, index);
784842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            extension = filename.substring(index);
785842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
786842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        for (int i = 2; i < Integer.MAX_VALUE; i++) {
787842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            file = new File(directory, name + '-' + i + extension);
788842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            if (!file.exists()) {
789842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank                return file;
790842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank            }
791842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        }
792842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank        return null;
793842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank    }
794842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank
795d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank    /**
796d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * Loads an attachment, based on the PartRequest passed in.  The PartRequest is basically our
797d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * wrapper for Attachment
798d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * @param req the part (attachment) to be retrieved
799d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     * @throws IOException
800d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank     */
801842515bc55b31ad9034c4048ebb402248a69d18aMarc Blank    protected void getAttachment(PartRequest req) throws IOException {
8027531be7774769c84b499b1de5dc46da3a9468316Marc Blank        Attachment att = req.mAttachment;
803b0ce70f8d18dfe14fdd72be528d89eda1ba229feMarc Blank        Message msg = Message.restoreMessageWithId(mContext, att.mMessageKey);
80481d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank        doProgressCallback(msg.mId, att.mId, 0);
805ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
80642f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        String cmd = "GetAttachment&AttachmentName=" + att.mLocation;
80742f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        HttpResponse res = sendHttpClientPost(cmd, null, COMMAND_TIMEOUT);
80842f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank
809ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int status = res.getStatusLine().getStatusCode();
8101b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        if (status == HttpStatus.SC_OK) {
811ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            HttpEntity e = res.getEntity();
812ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int len = (int)e.getContentLength();
813ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            InputStream is = res.getEntity().getContent();
8147531be7774769c84b499b1de5dc46da3a9468316Marc Blank            File f = (req.mDestination != null)
8157531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    ? new File(req.mDestination)
8167531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    : createUniqueFileInternal(req.mDestination, att.mFileName);
817ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (f != null) {
818bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                // Ensure that the target directory exists
819bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                File destDir = f.getParentFile();
820bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                if (!destDir.exists()) {
821bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                    destDir.mkdirs();
822bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                }
823ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                FileOutputStream os = new FileOutputStream(f);
82495e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                // len > 0 means that Content-Length was set in the headers
82595e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                // len < 0 means "chunked" transfer-encoding
82695e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                if (len != 0) {
827ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    try {
8287531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        mPendingRequest = req;
829ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        byte[] bytes = new byte[CHUNK_SIZE];
830ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        int length = len;
83195e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        // Loop terminates 1) when EOF is reached or 2) if an IOException occurs
83295e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        // One of these is guaranteed to occur
83395e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        int totalRead = 0;
83495e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        userLog("Attachment content-length: ", len);
83595e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        while (true) {
83695e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            int read = is.read(bytes, 0, CHUNK_SIZE);
83795e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank
83895e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // read < 0 means that EOF was reached
83995e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            if (read < 0) {
84095e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                userLog("Attachment load reached EOF, totalRead: ", totalRead);
84195e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                break;
84295e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            }
84395e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank
84495e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // Keep track of how much we've read for progress callback
84595e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            totalRead += read;
84695e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank
84795e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // Write these bytes out
848ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            os.write(bytes, 0, read);
84995e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank
85095e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // We can't report percentages if this is chunked; by definition, the
85195e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            // length of incoming data is unknown
85295e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            if (length > 0) {
85395e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                // Belt and suspenders check to prevent runaway reading
85495e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                if (totalRead > length) {
85595e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                    errorLog("totalRead is greater than attachment length?");
85695e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                    break;
85795e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                }
8584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                int pct = (totalRead * 100) / length;
85995e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                                doProgressCallback(msg.mId, att.mId, pct);
86095e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                            }
86195e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                       }
862ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    } finally {
8637531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        mPendingRequest = null;
864ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
865ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
866ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                os.flush();
867ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                os.close();
868ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
869d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                // EmailProvider will throw an exception if we try to update an unsaved attachment
870d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                if (att.isSaved()) {
8717531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    String contentUriString = (req.mContentUriString != null)
8727531be7774769c84b499b1de5dc46da3a9468316Marc Blank                            ? req.mContentUriString
873bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                            : "file://" + f.getAbsolutePath();
874d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    ContentValues cv = new ContentValues();
875bb7360cbcfa12694ebd8a842f8d1d25fc6897dfdAndrew Stadler                    cv.put(AttachmentColumns.CONTENT_URI, contentUriString);
876d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    att.update(mContext, cv);
87781d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank                    doStatusCallback(msg.mId, att.mId, EmailServiceStatus.SUCCESS);
878d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
879ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
880d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank        } else {
88181d9179a5bd856c39ae74f591983bf662d99fb05Marc Blank            doStatusCallback(msg.mId, att.mId, EmailServiceStatus.MESSAGE_NOT_FOUND);
882ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
883ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
884ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
8857531be7774769c84b499b1de5dc46da3a9468316Marc Blank    /**
8865c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     * Send an email responding to a Message that has been marked as a meeting request.  The message
8875c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     * will consist a little bit of event information and an iCalendar attachment
8885c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     * @param msg the meeting request email
8895c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     */
890346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank    private void sendMeetingResponseMail(Message msg, int response) {
8915c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Get the meeting information; we'd better have some...
8925c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        PackedString meetingInfo = new PackedString(msg.mMeetingInfo);
8935c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        if (meetingInfo == null) return;
8945c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
8955c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // This will come as "First Last" <box@server.blah>, so we use Address to
8965c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // parse it into parts; we only need the email address part for the ics file
8975c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        Address[] addrs = Address.parse(meetingInfo.get(MeetingInfo.MEETING_ORGANIZER_EMAIL));
8985c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // It shouldn't be possible, but handle it anyway
8995c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        if (addrs.length != 1) return;
9005c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String organizerEmail = addrs[0].getAddress();
9015c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9025c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String dtStamp = meetingInfo.get(MeetingInfo.MEETING_DTSTAMP);
9035c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String dtStart = meetingInfo.get(MeetingInfo.MEETING_DTSTART);
9045c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String dtEnd = meetingInfo.get(MeetingInfo.MEETING_DTEND);
9055c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9065c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // What we're doing here is to create an Entity that looks like an Event as it would be
9075c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // stored by CalendarProvider
9085c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        ContentValues entityValues = new ContentValues();
9095c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        Entity entity = new Entity(entityValues);
9105c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9115c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Fill in times, location, title, and organizer
9125c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put("DTSTAMP",
9135c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                CalendarUtilities.convertEmailDateTimeToCalendarDateTime(dtStamp));
9148e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda        entityValues.put(Events.DTSTART, Utility.parseEmailDateTimeToMillis(dtStart));
9158e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda        entityValues.put(Events.DTEND, Utility.parseEmailDateTimeToMillis(dtEnd));
9165c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put(Events.EVENT_LOCATION, meetingInfo.get(MeetingInfo.MEETING_LOCATION));
9175c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put(Events.TITLE, meetingInfo.get(MeetingInfo.MEETING_TITLE));
9185c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put(Events.ORGANIZER, organizerEmail);
9195c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9205c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Add ourselves as an attendee, using our account email address
9215c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        ContentValues attendeeValues = new ContentValues();
9225c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
9235c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                Attendees.RELATIONSHIP_ATTENDEE);
9245c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        attendeeValues.put(Attendees.ATTENDEE_EMAIL, mAccount.mEmailAddress);
9255c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
9265c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9275c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Add the organizer
9285c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        ContentValues organizerValues = new ContentValues();
9295c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        organizerValues.put(Attendees.ATTENDEE_RELATIONSHIP,
9305c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                Attendees.RELATIONSHIP_ORGANIZER);
9315c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        organizerValues.put(Attendees.ATTENDEE_EMAIL, organizerEmail);
9325c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entity.addSubValue(Attendees.CONTENT_URI, organizerValues);
9335c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9345c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Create a message from the Entity we've built.  The message will have fields like
9355c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // to, subject, date, and text filled in.  There will also be an "inline" attachment
9365c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // which is in iCalendar format
937346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank        int flag;
938346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank        switch(response) {
939346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            case EmailServiceConstants.MEETING_REQUEST_ACCEPTED:
940346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                flag = Message.FLAG_OUTGOING_MEETING_ACCEPT;
941346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                break;
942346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            case EmailServiceConstants.MEETING_REQUEST_DECLINED:
943346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                flag = Message.FLAG_OUTGOING_MEETING_DECLINE;
944346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                break;
945346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            case EmailServiceConstants.MEETING_REQUEST_TENTATIVE:
946346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            default:
947346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                flag = Message.FLAG_OUTGOING_MEETING_TENTATIVE;
948346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                break;
949346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank        }
9505c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        Message outgoingMsg =
951346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            CalendarUtilities.createMessageForEntity(mContext, entity, flag,
9525c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    meetingInfo.get(MeetingInfo.MEETING_UID), mAccount);
9535c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Assuming we got a message back (we might not if the event has been deleted), send it
9545c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        if (outgoingMsg != null) {
9555c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            EasOutboxService.sendMessage(mContext, mAccount.mId, outgoingMsg);
9565c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        }
9575c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank    }
9585c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9595c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank    /**
9607531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * Responds to a meeting request.  The MeetingResponseRequest is basically our
9617531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * wrapper for the meetingResponse service call
9627531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * @param req the request (message id and response code)
9637531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * @throws IOException
9647531be7774769c84b499b1de5dc46da3a9468316Marc Blank     */
9657531be7774769c84b499b1de5dc46da3a9468316Marc Blank    protected void sendMeetingResponse(MeetingResponseRequest req) throws IOException {
9664f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        // Retrieve the message and mailbox; punt if either are null
9677531be7774769c84b499b1de5dc46da3a9468316Marc Blank        Message msg = Message.restoreMessageWithId(mContext, req.mMessageId);
9684f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        if (msg == null) return;
9694f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, msg.mMailboxKey);
9704f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        if (mailbox == null) return;
9717531be7774769c84b499b1de5dc46da3a9468316Marc Blank        Serializer s = new Serializer();
9727531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.start(Tags.MREQ_MEETING_RESPONSE).start(Tags.MREQ_REQUEST);
9737531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.data(Tags.MREQ_USER_RESPONSE, Integer.toString(req.mResponse));
9744f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        s.data(Tags.MREQ_COLLECTION_ID, mailbox.mServerId);
9757531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.data(Tags.MREQ_REQ_ID, msg.mServerId);
9767531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.end().end().done();
9777531be7774769c84b499b1de5dc46da3a9468316Marc Blank        HttpResponse res = sendHttpClientPost("MeetingResponse", s.toByteArray());
9787531be7774769c84b499b1de5dc46da3a9468316Marc Blank        int status = res.getStatusLine().getStatusCode();
9797531be7774769c84b499b1de5dc46da3a9468316Marc Blank        if (status == HttpStatus.SC_OK) {
9807531be7774769c84b499b1de5dc46da3a9468316Marc Blank            HttpEntity e = res.getEntity();
9817531be7774769c84b499b1de5dc46da3a9468316Marc Blank            int len = (int)e.getContentLength();
9827531be7774769c84b499b1de5dc46da3a9468316Marc Blank            InputStream is = res.getEntity().getContent();
9837531be7774769c84b499b1de5dc46da3a9468316Marc Blank            if (len != 0) {
9847531be7774769c84b499b1de5dc46da3a9468316Marc Blank                new MeetingResponseParser(is, this).parse();
985346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                sendMeetingResponseMail(msg, req.mResponse);
9867531be7774769c84b499b1de5dc46da3a9468316Marc Blank            }
9877531be7774769c84b499b1de5dc46da3a9468316Marc Blank        } else if (isAuthError(status)) {
9887531be7774769c84b499b1de5dc46da3a9468316Marc Blank            throw new EasAuthenticationException();
9897531be7774769c84b499b1de5dc46da3a9468316Marc Blank        } else {
9907531be7774769c84b499b1de5dc46da3a9468316Marc Blank            userLog("Meeting response request failed, code: " + status);
9917531be7774769c84b499b1de5dc46da3a9468316Marc Blank            throw new IOException();
9927531be7774769c84b499b1de5dc46da3a9468316Marc Blank        }
9937531be7774769c84b499b1de5dc46da3a9468316Marc Blank    }
9947531be7774769c84b499b1de5dc46da3a9468316Marc Blank
995147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank    @SuppressWarnings("deprecation")
9969d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank    private String makeUriString(String cmd, String extra) throws IOException {
997ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank         // Cache the authentication string and the command string
998ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        String safeUserName = URLEncoder.encode(mUserName);
999ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (mAuthString == null) {
1000ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String cs = mUserName + ':' + mPassword;
1001f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker            mAuthString = "Basic " + Base64.encodeToString(cs.getBytes(), Base64.NO_WRAP);
1002ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType="
1003ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    + mDeviceType;
1004ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1005e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        String us = (mSsl ? (mTrustSsl ? "httpts" : "https") : "http") + "://" + mHostAddress +
1006ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            "/Microsoft-Server-ActiveSync";
1007ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (cmd != null) {
1008ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            us += "?Cmd=" + cmd + mCmdString;
1009ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1010ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (extra != null) {
1011ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            us += extra;
1012ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1013ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return us;
1014ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1015ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
101620da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
101720da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Set standard HTTP headers, using a policy key if required
101820da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param method the method we are going to send
101920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param usePolicyKey whether or not a policy key should be sent in the headers
102020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
102120da011530088036d2bf45d3836d6986a4b5d423Marc Blank    private void setHeaders(HttpRequestBase method, boolean usePolicyKey) {
10228047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("Authorization", mAuthString);
10238047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("MS-ASProtocolVersion", mProtocolVersion);
10248047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("Connection", "keep-alive");
10258047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("User-Agent", mDeviceType + '/' + Eas.VERSION);
102620da011530088036d2bf45d3836d6986a4b5d423Marc Blank        if (usePolicyKey && (mAccount != null)) {
102720da011530088036d2bf45d3836d6986a4b5d423Marc Blank            String key = mAccount.mSecuritySyncKey;
102820da011530088036d2bf45d3836d6986a4b5d423Marc Blank            if (key == null || key.length() == 0) {
102920da011530088036d2bf45d3836d6986a4b5d423Marc Blank                return;
103020da011530088036d2bf45d3836d6986a4b5d423Marc Blank            }
103195fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank             if (Eas.PARSER_LOG) {
103295fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                userLog("Policy key: " , key);
103395fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank            }
103420da011530088036d2bf45d3836d6986a4b5d423Marc Blank            method.setHeader("X-MS-PolicyKey", key);
103520da011530088036d2bf45d3836d6986a4b5d423Marc Blank        }
10368047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
1037ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1038e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank    private ClientConnectionManager getClientConnectionManager() {
1039e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        return SyncManager.getClientConnectionManager();
1040e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank    }
1041e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank
10428047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    private HttpClient getHttpClient(int timeout) {
10438047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpParams params = new BasicHttpParams();
104416b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank        HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
10458047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpConnectionParams.setSoTimeout(params, timeout);
1046e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        HttpConnectionParams.setSocketBufferSize(params, 8192);
1047e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        HttpClient client = new DefaultHttpClient(getClientConnectionManager(), params);
1048e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        return client;
10498047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
1050ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
10518047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    protected HttpResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException {
10521b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return sendHttpClientPost(cmd, new ByteArrayEntity(bytes), COMMAND_TIMEOUT);
10539e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    }
10549e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank
10559e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity) throws IOException {
10561b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return sendHttpClientPost(cmd, entity, COMMAND_TIMEOUT);
10571b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    }
10581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
10592033dfc4e2e6e352b34565112266084d72c443f1Marc Blank    protected HttpResponse sendPing(byte[] bytes, int heartbeat) throws IOException {
10601b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       Thread.currentThread().setName(mAccount.mDisplayName + ": Ping");
10611b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       if (Eas.USER_LOG) {
10622033dfc4e2e6e352b34565112266084d72c443f1Marc Blank           userLog("Send ping, timeout: " + heartbeat + "s, high: " + mPingHighWaterMark + 's');
10631b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank       }
10642033dfc4e2e6e352b34565112266084d72c443f1Marc Blank       return sendHttpClientPost(PING_COMMAND, new ByteArrayEntity(bytes), (heartbeat+5)*SECONDS);
10651b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    }
10661b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
1067adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    /**
1068adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * Convenience method for executePostWithTimeout for use other than with the Ping command
1069adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     */
1070adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    protected HttpResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout)
1071adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            throws IOException {
1072adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        return executePostWithTimeout(client, method, timeout, false);
1073adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    }
1074adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank
1075adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    /**
1076adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * Handle executing an HTTP POST command with proper timeout, watchdog, and ping behavior
1077adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param client the HttpClient
1078adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param method the HttpPost
1079adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param timeout the timeout before failure, in ms
1080adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param isPingCommand whether the POST is for the Ping command (requires wakelock logic)
1081adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @return the HttpResponse
1082adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @throws IOException
1083adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     */
1084adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    protected HttpResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout,
1085adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            boolean isPingCommand) throws IOException {
1086adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        synchronized(getSynchronizer()) {
1087adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            mPendingPost = method;
108816b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            long alarmTime = timeout + WATCHDOG_TIMEOUT_ALLOWANCE;
1089adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            if (isPingCommand) {
1090adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                SyncManager.runAsleep(mMailboxId, alarmTime);
1091adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            } else {
1092adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                SyncManager.setWatchdogAlarm(mMailboxId, alarmTime);
1093adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            }
1094adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        }
1095adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        try {
1096adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            return client.execute(method);
1097adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        } finally {
1098adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            synchronized(getSynchronizer()) {
1099adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                if (isPingCommand) {
1100adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                    SyncManager.runAwake(mMailboxId);
1101adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                } else {
1102adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                    SyncManager.clearWatchdogAlarm(mMailboxId);
1103adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                }
1104adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                mPendingPost = null;
1105adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            }
1106adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        }
1107adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    }
1108adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank
11091b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity, int timeout)
11101b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            throws IOException {
11111b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HttpClient client = getHttpClient(timeout);
1112c4bc56c4057d2d7596b75c60ee792676251804f5Marc Blank        boolean isPingCommand = cmd.equals(PING_COMMAND);
111385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
111485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        // Split the mail sending commands
111585f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String extra = null;
111685f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        boolean msg = false;
111785f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        if (cmd.startsWith("SmartForward&") || cmd.startsWith("SmartReply&")) {
11185843b85178a359446f81770ed7734604a1b2fa7dMarc Blank            int cmdLength = cmd.indexOf('&');
111985f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            extra = cmd.substring(cmdLength);
112085f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            cmd = cmd.substring(0, cmdLength);
112185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
112285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        } else if (cmd.startsWith("SendMail&")) {
112385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
112485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        }
112585f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
112685f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String us = makeUriString(cmd, extra);
11278047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpPost method = new HttpPost(URI.create(us));
112842f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        // Send the proper Content-Type header
112942f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        // If entity is null (e.g. for attachments), don't set this header
113085f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        if (msg) {
11318047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "message/rfc822");
113242f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        } else if (entity != null) {
11338047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml");
1134ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
113520da011530088036d2bf45d3836d6986a4b5d423Marc Blank        setHeaders(method, !cmd.equals(PING_COMMAND));
11369e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        method.setEntity(entity);
1137adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        return executePostWithTimeout(client, method, timeout, isPingCommand);
11388047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
11398047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
11408047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    protected HttpResponse sendHttpClientOptions() throws IOException {
11418047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpClient client = getHttpClient(COMMAND_TIMEOUT);
11428047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        String us = makeUriString("OPTIONS", null);
11438047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpOptions method = new HttpOptions(URI.create(us));
114420da011530088036d2bf45d3836d6986a4b5d423Marc Blank        setHeaders(method, false);
11458047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        return client.execute(method);
1146ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1147ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1148ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    String getTargetCollectionClassFromCursor(Cursor c) {
1149ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
1150ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (type == Mailbox.TYPE_CONTACTS) {
1151ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Contacts";
1152ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else if (type == Mailbox.TYPE_CALENDAR) {
1153ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Calendar";
1154ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else {
1155ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Email";
1156ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1157ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1158ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
115920da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
116020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Negotiate provisioning with the server.  First, get policies form the server and see if
116120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * the policies are supported by the device.  Then, write the policies to the account and
116220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * tell SecurityPolicy that we have policies in effect.  Finally, see if those policies are
116320da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * active; if so, acknowledge the policies to the server and get a final policy key that we
116420da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * use in future EAS commands and write this key to the account.
116520da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @return whether or not provisioning has been successful
116620da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @throws IOException
116720da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
116820da011530088036d2bf45d3836d6986a4b5d423Marc Blank    private boolean tryProvision() throws IOException {
116920da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // First, see if provisioning is even possible, i.e. do we support the policies required
117020da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // by the server
117120da011530088036d2bf45d3836d6986a4b5d423Marc Blank        ProvisionParser pp = canProvision();
117220da011530088036d2bf45d3836d6986a4b5d423Marc Blank        if (pp != null) {
117320da011530088036d2bf45d3836d6986a4b5d423Marc Blank            SecurityPolicy sp = SecurityPolicy.getInstance(mContext);
117420da011530088036d2bf45d3836d6986a4b5d423Marc Blank            // Get the policies from ProvisionParser
117520da011530088036d2bf45d3836d6986a4b5d423Marc Blank            PolicySet ps = pp.getPolicySet();
117620da011530088036d2bf45d3836d6986a4b5d423Marc Blank            // Update the account with a null policyKey (the key we've gotten is
117720da011530088036d2bf45d3836d6986a4b5d423Marc Blank            // temporary and cannot be used for syncing)
117820da011530088036d2bf45d3836d6986a4b5d423Marc Blank            if (ps.writeAccount(mAccount, null, true, mContext)) {
117920da011530088036d2bf45d3836d6986a4b5d423Marc Blank                sp.updatePolicies(mAccount.mId);
118020da011530088036d2bf45d3836d6986a4b5d423Marc Blank            }
1181bbc1811956084987767779e31525c5013dab59d8Marc Blank            if (pp.getRemoteWipe()) {
11822a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                // We've gotten a remote wipe command
1183bbc1811956084987767779e31525c5013dab59d8Marc Blank                // First, we've got to acknowledge it, but wrap the wipe in try/catch so that
1184bbc1811956084987767779e31525c5013dab59d8Marc Blank                // we wipe the device regardless of any errors in acknowledgment
11852a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                try {
11862a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                    acknowledgeRemoteWipe(pp.getPolicyKey());
11872a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                } catch (Exception e) {
11882a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                    // Because remote wipe is such a high priority task, we don't want to
11892a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                    // circumvent it if there's an exception in acknowledgment
11902a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                }
11912a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                // Then, tell SecurityPolicy to wipe the device
11922a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                sp.remoteWipe();
11932a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank                return false;
1194bbc1811956084987767779e31525c5013dab59d8Marc Blank            } else if (sp.isActive(ps)) {
1195bbc1811956084987767779e31525c5013dab59d8Marc Blank                // See if the required policies are in force; if they are, acknowledge the policies
1196bbc1811956084987767779e31525c5013dab59d8Marc Blank                // to the server and get the final policy key
1197bbc1811956084987767779e31525c5013dab59d8Marc Blank                String policyKey = acknowledgeProvision(pp.getPolicyKey());
1198bbc1811956084987767779e31525c5013dab59d8Marc Blank                if (policyKey != null) {
1199bbc1811956084987767779e31525c5013dab59d8Marc Blank                    // Write the final policy key to the Account and say we've been successful
1200bbc1811956084987767779e31525c5013dab59d8Marc Blank                    ps.writeAccount(mAccount, policyKey, true, mContext);
1201bbc1811956084987767779e31525c5013dab59d8Marc Blank                    return true;
1202bbc1811956084987767779e31525c5013dab59d8Marc Blank                }
120320da011530088036d2bf45d3836d6986a4b5d423Marc Blank            } else {
120420da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // Notify that we are blocked because of policies
120520da011530088036d2bf45d3836d6986a4b5d423Marc Blank                sp.policiesRequired(mAccount.mId);
120620da011530088036d2bf45d3836d6986a4b5d423Marc Blank            }
120720da011530088036d2bf45d3836d6986a4b5d423Marc Blank        }
120820da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return false;
120920da011530088036d2bf45d3836d6986a4b5d423Marc Blank    }
121020da011530088036d2bf45d3836d6986a4b5d423Marc Blank
1211b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    private String getPolicyType() {
1212d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        return (mProtocolVersionDouble >=
1213d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE;
1214b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    }
1215b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
121620da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
121720da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Obtain a set of policies from the server and determine whether those policies are supported
121820da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * by the device.
121920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @return the ProvisionParser (holds policies and key) if we receive policies and they are
122020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * supported by the device; null otherwise
122120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @throws IOException
122220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
122320da011530088036d2bf45d3836d6986a4b5d423Marc Blank    private ProvisionParser canProvision() throws IOException {
12248692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        Serializer s = new Serializer();
12258692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
1226b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank        s.start(Tags.PROVISION_POLICY).data(Tags.PROVISION_POLICY_TYPE, getPolicyType())
12278692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            .end().end().end().done();
12288692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        HttpResponse resp = sendHttpClientPost("Provision", s.toByteArray());
12298692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        int code = resp.getStatusLine().getStatusCode();
12308692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        if (code == HttpStatus.SC_OK) {
12318692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            InputStream is = resp.getEntity().getContent();
12328692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            ProvisionParser pp = new ProvisionParser(is, this);
12338692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            if (pp.parse()) {
123420da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // If true, we received policies from the server; see if they are supported by
123520da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // the framework; if so, return the ProvisionParser containing the policy set and
123620da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // temporary key
12378692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                PolicySet ps = pp.getPolicySet();
12388692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                if (SecurityPolicy.getInstance(mContext).isSupported(ps)) {
123920da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    return pp;
12408692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                }
12418692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            }
12428692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        }
124320da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // On failures, simply return null
124420da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return null;
124520da011530088036d2bf45d3836d6986a4b5d423Marc Blank    }
124620da011530088036d2bf45d3836d6986a4b5d423Marc Blank
124720da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
124820da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Acknowledge that we support the policies provided by the server, and that these policies
124920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * are in force.
125020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param tempKey the initial (temporary) policy key sent by the server
125120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @return the final policy key, which can be used for syncing
125220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @throws IOException
125320da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
12542a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    private void acknowledgeRemoteWipe(String tempKey) throws IOException {
12552a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        acknowledgeProvisionImpl(tempKey, true);
12562a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    }
12572a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank
125820da011530088036d2bf45d3836d6986a4b5d423Marc Blank    private String acknowledgeProvision(String tempKey) throws IOException {
12592a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        return acknowledgeProvisionImpl(tempKey, false);
12602a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    }
12612a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank
12622a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    private String acknowledgeProvisionImpl(String tempKey, boolean remoteWipe) throws IOException {
126320da011530088036d2bf45d3836d6986a4b5d423Marc Blank        Serializer s = new Serializer();
126420da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
126520da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.start(Tags.PROVISION_POLICY);
1266b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
1267b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank        // Use the proper policy type, depending on EAS version
1268b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank        s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType());
1269b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
127020da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.data(Tags.PROVISION_POLICY_KEY, tempKey);
127120da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.data(Tags.PROVISION_STATUS, "1");
12722a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        if (remoteWipe) {
12732a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank            s.start(Tags.PROVISION_REMOTE_WIPE);
12742a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank            s.data(Tags.PROVISION_STATUS, "1");
12752a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank            s.end();
12762a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        }
12772a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        s.end(); // PROVISION_POLICY
12782a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        s.end().end().done(); // PROVISION_POLICIES, PROVISION_PROVISION
127920da011530088036d2bf45d3836d6986a4b5d423Marc Blank        HttpResponse resp = sendHttpClientPost("Provision", s.toByteArray());
128020da011530088036d2bf45d3836d6986a4b5d423Marc Blank        int code = resp.getStatusLine().getStatusCode();
128120da011530088036d2bf45d3836d6986a4b5d423Marc Blank        if (code == HttpStatus.SC_OK) {
128220da011530088036d2bf45d3836d6986a4b5d423Marc Blank            InputStream is = resp.getEntity().getContent();
128320da011530088036d2bf45d3836d6986a4b5d423Marc Blank            ProvisionParser pp = new ProvisionParser(is, this);
128420da011530088036d2bf45d3836d6986a4b5d423Marc Blank            if (pp.parse()) {
128520da011530088036d2bf45d3836d6986a4b5d423Marc Blank                // Return the final polic key from the ProvisionParser
128620da011530088036d2bf45d3836d6986a4b5d423Marc Blank                return pp.getPolicyKey();
128720da011530088036d2bf45d3836d6986a4b5d423Marc Blank            }
128820da011530088036d2bf45d3836d6986a4b5d423Marc Blank        }
128920da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // On failures, return null
129020da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return null;
12918692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    }
12928692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
1293ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
1294ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Performs FolderSync
1295ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
1296ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
1297ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws EasParserException
1298ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
1299a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank    public void runAccountMailbox() throws IOException, EasParserException {
1300fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        // Initialize exit status to success
1301fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        mExitStatus = EmailServiceStatus.SUCCESS;
1302ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
1303fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            try {
1304fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                SyncManager.callback()
1305fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                    .syncMailboxListStatus(mAccount.mId, EmailServiceStatus.IN_PROGRESS, 0);
1306fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            } catch (RemoteException e1) {
1307fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                // Don't care if this fails
1308fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
1309fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
1310ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (mAccount.mSyncKey == null) {
1311ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                mAccount.mSyncKey = "0";
13121b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Account syncKey INIT to 0");
13139387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                ContentValues cv = new ContentValues();
13149387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
13159387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler                mAccount.update(mContext, cv);
1316ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1317ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
13189d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            boolean firstSync = mAccount.mSyncKey.equals("0");
13199d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            if (firstSync) {
13201b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Initial FolderSync");
13211b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            }
13221b275b9408d5b856e2482fa3951827489e9585ccMarc Blank
13234626078bf9d930b2007162db142b5961b38e2166Marc Blank            // When we first start up, change all mailboxes to push.
1324ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ContentValues cv = new ContentValues();
1325f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank            cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
1326ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
13279d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING,
13289d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    new String[] {Long.toString(mAccount.mId)}) > 0) {
13299d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                SyncManager.kick("change ping boxes to push");
1330ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1331ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1332fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank            // Determine our protocol version, if we haven't already and save it in the Account
133385a57898618f48773884e8bc34ca1e8995cc3690Marc Blank            // Also re-check protocol version at least once a day (in case of upgrade)
133485a57898618f48773884e8bc34ca1e8995cc3690Marc Blank            if (mAccount.mProtocolVersion == null ||
133585a57898618f48773884e8bc34ca1e8995cc3690Marc Blank                    ((System.currentTimeMillis() - mMailbox.mSyncTime) > DAYS)) {
13361b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                userLog("Determine EAS protocol version");
13378047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                HttpResponse resp = sendHttpClientOptions();
13388047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                int code = resp.getStatusLine().getStatusCode();
13390a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("OPTIONS response: ", code);
13401b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                if (code == HttpStatus.SC_OK) {
1341647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    Header header = resp.getFirstHeader("MS-ASProtocolCommands");
1342647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    userLog(header.getValue());
1343647aa5f1f229947d982548f698c4533fe538f884Marc Blank                    header = resp.getFirstHeader("ms-asprotocolversions");
1344d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    try {
1345d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                        setupProtocolVersion(this, header);
1346d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    } catch (MessagingException e) {
1347d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                        // Since we've already validated, this can't really happen
1348d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                        // But if it does, we'll rethrow this...
1349ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        throw new IOException();
1350ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
1351d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    // Save the protocol version
1352d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    cv.clear();
1353d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    // Save the protocol version in the account
1354d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    cv.put(Account.PROTOCOL_VERSION, mProtocolVersion);
1355d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    mAccount.update(mContext, cv);
1356d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    cv.clear();
1357d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    // Save the sync time of the account mailbox to current time
1358d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
1359d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    mMailbox.update(mContext, cv);
1360d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                 } else {
13618047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    errorLog("OPTIONS command failed; throwing IOException");
13628047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank                    throw new IOException();
13630f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
13640f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank            }
13651b275b9408d5b856e2482fa3951827489e9585ccMarc Blank
136677424af660458104b732bdcb718874b17d0cab3aMarc Blank            // Change all pushable boxes to push when we start the account mailbox
136777424af660458104b732bdcb718874b17d0cab3aMarc Blank            if (mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH) {
1368fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank                cv.clear();
1369f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank                cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
137077424af660458104b732bdcb718874b17d0cab3aMarc Blank                if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
137177424af660458104b732bdcb718874b17d0cab3aMarc Blank                        SyncManager.WHERE_IN_ACCOUNT_AND_PUSHABLE,
137277424af660458104b732bdcb718874b17d0cab3aMarc Blank                        new String[] {Long.toString(mAccount.mId)}) > 0) {
137377424af660458104b732bdcb718874b17d0cab3aMarc Blank                    userLog("Push account; set pushable boxes to push...");
137477424af660458104b732bdcb718874b17d0cab3aMarc Blank                }
137577424af660458104b732bdcb718874b17d0cab3aMarc Blank            }
137677424af660458104b732bdcb718874b17d0cab3aMarc Blank
137777424af660458104b732bdcb718874b17d0cab3aMarc Blank            while (!mStop) {
1378afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                userLog("Sending Account syncKey: ", mAccount.mSyncKey);
1379afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                Serializer s = new Serializer();
1380afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY)
138120da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    .text(mAccount.mSyncKey).end().end().done();
1382afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
1383afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                if (mStop) break;
1384afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                int code = resp.getStatusLine().getStatusCode();
1385afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                if (code == HttpStatus.SC_OK) {
1386afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    HttpEntity entity = resp.getEntity();
1387afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    int len = (int)entity.getContentLength();
1388afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    if (len != 0) {
1389afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                        InputStream is = entity.getContent();
1390afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                        // Returns true if we need to sync again
1391afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                        if (new FolderSyncParser(is, new AccountSyncAdapter(mMailbox, this))
139220da011530088036d2bf45d3836d6986a4b5d423Marc Blank                                .parse()) {
1393afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                            continue;
1394afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                        }
1395afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    }
139620da011530088036d2bf45d3836d6986a4b5d423Marc Blank                } else if (isProvisionError(code)) {
139720da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    // If the sync error is a provisioning failure (perhaps the policies changed),
139895fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    // let's try the provisioning procedure
139995fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    // Provisioning must only be attempted for the account mailbox - trying to
140095fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    // provision any other mailbox may result in race conditions and the creation
140195fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    // of multiple policy keys.
140220da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    if (!tryProvision()) {
140320da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        // Set the appropriate failure status
140420da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        mExitStatus = EXIT_SECURITY_FAILURE;
140520da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        return;
1406094e6da9f5c5728eb10a3572717db2ba55718df3Marc Blank                    } else {
1407094e6da9f5c5728eb10a3572717db2ba55718df3Marc Blank                        // If we succeeded, try again...
1408094e6da9f5c5728eb10a3572717db2ba55718df3Marc Blank                        continue;
1409afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                    }
1410afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler                } else if (isAuthError(code)) {
14111b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_LOGIN_FAILURE;
141220da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    return;
14130f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } else {
14140a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    userLog("FolderSync response error: ", code);
14150f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
1416ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
14179d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                // Change all push/hold boxes to push
1418fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank                cv.clear();
14199d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                cv.put(Mailbox.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
14209d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
14219d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX,
14229d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        new String[] {Long.toString(mAccount.mId)}) > 0) {
14239d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                    userLog("Set push/hold boxes to push...");
14249d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                }
14259d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
14260f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                try {
14270f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    SyncManager.callback()
14289d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank                        .syncMailboxListStatus(mAccount.mId, mExitStatus, 0);
14290f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } catch (RemoteException e1) {
14300f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    // Don't care if this fails
14310f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
1432b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank
1433b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                // Before each run of the pingLoop, if this Account has a PolicySet, make sure it's
1434b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                // active; otherwise, clear out the key/flag.  This should cause a provisioning
1435b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                // error on the next POST, and start the security sequence over again
1436b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                String key = mAccount.mSecuritySyncKey;
1437b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                if (!TextUtils.isEmpty(key)) {
1438b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                    PolicySet ps = new PolicySet(mAccount);
1439b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                    SecurityPolicy sp = SecurityPolicy.getInstance(mContext);
1440b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                    if (!sp.isActive(ps)) {
1441b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        cv.clear();
1442b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        cv.put(AccountColumns.SECURITY_FLAGS, 0);
1443b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
1444b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        long accountId = mAccount.mId;
1445b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        mContentResolver.update(ContentUris.withAppendedId(
1446b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                                Account.CONTENT_URI, accountId), cv, null, null);
1447b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                        sp.policiesRequired(accountId);
1448b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                    }
1449b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank                }
1450fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
14510f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                // Wait for push notifications.
14520f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                String threadName = Thread.currentThread().getName();
14530f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                try {
14540f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    runPingLoop();
14550f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } catch (StaleFolderListException e) {
14560f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    // We break out if we get told about a stale folder list
14570f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    userLog("Ping interrupted; folder list requires sync...");
14580f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                } finally {
14590f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                    Thread.currentThread().setName(threadName);
14600f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank                }
1461ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
14620f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank         } catch (IOException e) {
1463fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // We catch this here to send the folder sync status callback
1464fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // A folder sync failed callback will get sent from run()
1465fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            try {
14664d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                if (!mStop) {
14674d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                    SyncManager.callback()
14684d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                        .syncMailboxListStatus(mAccount.mId,
14694d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                                EmailServiceStatus.CONNECTION_ERROR, 0);
14704d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                }
1471fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            } catch (RemoteException e1) {
1472fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank                // Don't care if this fails
1473fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
147496293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank            throw e;
1475ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1476ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1477ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1478c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    void pushFallback(long mailboxId) {
1479c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
14805ea28d42b2aee64747a35d8bf012afff1604bc4cMakoto Onuki        if (mailbox == null) {
14815ea28d42b2aee64747a35d8bf012afff1604bc4cMakoto Onuki            return;
14825ea28d42b2aee64747a35d8bf012afff1604bc4cMakoto Onuki        }
1483c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        ContentValues cv = new ContentValues();
14844626078bf9d930b2007162db142b5961b38e2166Marc Blank        int mins = PING_FALLBACK_PIM;
1485c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        if (mailbox.mType == Mailbox.TYPE_INBOX) {
14864626078bf9d930b2007162db142b5961b38e2166Marc Blank            mins = PING_FALLBACK_INBOX;
1487c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
14884626078bf9d930b2007162db142b5961b38e2166Marc Blank        cv.put(Mailbox.SYNC_INTERVAL, mins);
14894626078bf9d930b2007162db142b5961b38e2166Marc Blank        mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
14904626078bf9d930b2007162db142b5961b38e2166Marc Blank                cv, null, null);
149127cf341571fac3d8dbe866f503c34fc31e02bf85Marc Blank        errorLog("*** PING ERROR LOOP: Set " + mailbox.mDisplayName + " to " + mins + " min sync");
14924626078bf9d930b2007162db142b5961b38e2166Marc Blank        SyncManager.kick("push fallback");
1493368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
1494368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
1495ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    void runPingLoop() throws IOException, StaleFolderListException {
14961b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        int pingHeartbeat = mPingHeartbeat;
149774c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank        userLog("runPingLoop");
1498ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Do push for all sync services here
1499c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        long endTime = System.currentTimeMillis() + (30*MINUTES);
15001b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HashMap<String, Integer> pingErrorMap = new HashMap<String, Integer>();
15012033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        ArrayList<String> readyMailboxes = new ArrayList<String>();
15022033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        ArrayList<String> notReadyMailboxes = new ArrayList<String>();
15037672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank        int pingWaitCount = 0;
1504f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker
15057ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        while ((System.currentTimeMillis() < endTime) && !mStop) {
1506ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Count of pushable mailboxes
1507ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int pushCount = 0;
1508ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Count of mailboxes that can be pushed right now
1509ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            int canPushCount = 0;
15108a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            // Count of uninitialized boxes
15118a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            int uninitCount = 0;
1512f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker
15137c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            Serializer s = new Serializer();
1514ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
151522bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank                    MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId +
151622bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank                    AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null, null);
15172033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            notReadyMailboxes.clear();
15182033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            readyMailboxes.clear();
1519ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            try {
1520ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Loop through our pushed boxes seeing what is available to push
1521ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                while (c.moveToNext()) {
1522ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    pushCount++;
1523ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // Two requirements for push:
1524ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // 1) SyncManager tells us the mailbox is syncable (not running, not stopped)
1525ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // 2) The syncKey isn't "0" (i.e. it's synced at least once)
1526ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank                    long mailboxId = c.getLong(Mailbox.CONTENT_ID_COLUMN);
152777424af660458104b732bdcb718874b17d0cab3aMarc Blank                    int pingStatus = SyncManager.pingStatus(mailboxId);
152877424af660458104b732bdcb718874b17d0cab3aMarc Blank                    String mailboxName = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN);
152977424af660458104b732bdcb718874b17d0cab3aMarc Blank                    if (pingStatus == SyncManager.PING_STATUS_OK) {
1530ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String syncKey = c.getString(Mailbox.CONTENT_SYNC_KEY_COLUMN);
15317ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                        if ((syncKey == null) || syncKey.equals("0")) {
1532a05c26d8d2cce3faa152096cb8116fce375c6d81Marc Blank                            // We can't push until the initial sync is done
153377424af660458104b732bdcb718874b17d0cab3aMarc Blank                            pushCount--;
15348a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                            uninitCount++;
1535ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            continue;
1536ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
1537ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank
1538ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        if (canPushCount++ == 0) {
1539ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            // Initialize the Ping command
154096293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank                            s.start(Tags.PING_PING)
154105381a6662f28609e8005023515abb82af00e1d4Marc Blank                                .data(Tags.PING_HEARTBEAT_INTERVAL,
15421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                        Integer.toString(pingHeartbeat))
15437c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                                .start(Tags.PING_FOLDERS);
1544ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
15451b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
1546ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String folderClass = getTargetCollectionClassFromCursor(c);
15477c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        s.start(Tags.PING_FOLDER)
15487c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .data(Tags.PING_ID, c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN))
15497c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .data(Tags.PING_CLASS, folderClass)
15507c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            .end();
15512033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                        readyMailboxes.add(mailboxName);
15527ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                    } else if ((pingStatus == SyncManager.PING_STATUS_RUNNING) ||
15537ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                            (pingStatus == SyncManager.PING_STATUS_WAITING)) {
15542033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                        notReadyMailboxes.add(mailboxName);
155577424af660458104b732bdcb718874b17d0cab3aMarc Blank                    } else if (pingStatus == SyncManager.PING_STATUS_UNABLE) {
155677424af660458104b732bdcb718874b17d0cab3aMarc Blank                        pushCount--;
15570a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                        userLog(mailboxName, " in error state; ignore");
155877424af660458104b732bdcb718874b17d0cab3aMarc Blank                        continue;
1559ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
1560ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1561ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } finally {
1562ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                c.close();
1563ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1564ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
15652033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            if (Eas.USER_LOG) {
15662033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                if (!notReadyMailboxes.isEmpty()) {
15672033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                    userLog("Ping not ready for: " + notReadyMailboxes);
15682033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                }
15692033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                if (!readyMailboxes.isEmpty()) {
15702033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                    userLog("Ping ready for: " + readyMailboxes);
15712033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                }
15722033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            }
1573f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker
15747672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            // If we've waited 10 seconds or more, just ping with whatever boxes are ready
15757672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            // But use a shorter than normal heartbeat
15762033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            boolean forcePing = !notReadyMailboxes.isEmpty() && (pingWaitCount > 5);
15777672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank
15787672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank            if ((canPushCount > 0) && ((canPushCount == pushCount) || forcePing)) {
15797672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                // If all pingable boxes are ready for push, send Ping to the server
15807c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.end().end().done();
15817672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                pingWaitCount = 0;
1582e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                mPostReset = false;
1583e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                mPostAborted = false;
15847c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank
15851b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                // If we've been stopped, this is a good time to return
15864d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                if (mStop) return;
1587368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
158874c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                long pingTime = SystemClock.elapsedRealtime();
15891b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                try {
15901b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // Send the ping, wrapped by appropriate timeout/alarm
15917672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                    if (forcePing) {
15927672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                        userLog("Forcing ping after waiting for all boxes to be ready");
15937672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                    }
15947672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                    HttpResponse res =
15957672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                        sendPing(s.toByteArray(), forcePing ? PING_FORCE_HEARTBEAT : pingHeartbeat);
1596c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
15971b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    int code = res.getStatusLine().getStatusCode();
15981b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    userLog("Ping response: ", code);
1599368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
16001b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // Return immediately if we've been asked to stop during the ping
16011b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    if (mStop) {
16021b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        userLog("Stopping pingLoop");
16031b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        return;
16041b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    }
16051b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank
16061b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    if (code == HttpStatus.SC_OK) {
1607b1ffc81c7e3ee9297133a7924092192998ab3839Marc Blank                        // Make sure to clear out any pending sync errors
1608b1ffc81c7e3ee9297133a7924092192998ab3839Marc Blank                        SyncManager.removeFromSyncErrorMap(mMailboxId);
16091b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        HttpEntity e = res.getEntity();
16101b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        int len = (int)e.getContentLength();
16111b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        InputStream is = res.getEntity().getContent();
161295e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                        if (len != 0) {
16138d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            int pingResult = parsePingResult(is, mContentResolver, pingErrorMap);
16147672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                            // If our ping completed (status = 1), and we weren't forced and we're
16157672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                            // not at the maximum, try increasing timeout by two minutes
16168d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            if (pingResult == PROTOCOL_PING_STATUS_COMPLETED && !forcePing) {
16177672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                if (pingHeartbeat > mPingHighWaterMark) {
16187672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    mPingHighWaterMark = pingHeartbeat;
16197672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    userLog("Setting high water mark at: ", mPingHighWaterMark);
16207672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                }
16217672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                if ((pingHeartbeat < PING_MAX_HEARTBEAT) &&
16227672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                        !mPingHeartbeatDropped) {
16237672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    pingHeartbeat += PING_HEARTBEAT_INCREMENT;
16247672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    if (pingHeartbeat > PING_MAX_HEARTBEAT) {
16257672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                        pingHeartbeat = PING_MAX_HEARTBEAT;
16267672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    }
16277672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                                    userLog("Increasing ping heartbeat to ", pingHeartbeat, "s");
16281b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                }
16291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            }
16301b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        } else {
16311b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            userLog("Ping returned empty result; throwing IOException");
16321b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            throw new IOException();
16331b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        }
16341b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    } else if (isAuthError(code)) {
16351b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        mExitStatus = EXIT_LOGIN_FAILURE;
16361b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        userLog("Authorization error during Ping: ", code);
1637ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        throw new IOException();
1638ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
16391b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                } catch (IOException e) {
1640252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    String message = e.getMessage();
16411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // If we get the exception that is indicative of a NAT timeout and if we
16421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    // haven't yet "fixed" the timeout, back off by two minutes and "fix" it
1643252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    boolean hasMessage = message != null;
1644252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank                    userLog("IOException runPingLoop: " + (hasMessage ? message : "[no message]"));
1645e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    if (mPostReset) {
1646e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        // Nothing to do in this case; this is SyncManager telling us to try another
1647e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        // ping.
1648e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    } else if (mPostAborted || (hasMessage && message.contains("reset by peer"))) {
1649b9781ea88a58e746b74076ec499e9885e195acc9Marc Blank                        long pingLength = SystemClock.elapsedRealtime() - pingTime;
16507ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                        if ((pingHeartbeat > PING_MIN_HEARTBEAT) &&
16517ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank                                (pingHeartbeat > mPingHighWaterMark)) {
16521b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            pingHeartbeat -= PING_HEARTBEAT_INCREMENT;
16531b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            mPingHeartbeatDropped = true;
16541b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            if (pingHeartbeat < PING_MIN_HEARTBEAT) {
16551b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                                pingHeartbeat = PING_MIN_HEARTBEAT;
16561b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            }
16571b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                            userLog("Decreased ping heartbeat to ", pingHeartbeat, "s");
1658e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        } else if (mPostAborted) {
1659e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            // There's no point in throwing here; this can happen in two cases
1660e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            // 1) An alarm, which indicates minutes without activity; no sense
1661e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            //    backing off
1662e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            // 2) SyncManager abort, due to sync of mailbox.  Again, we want to
1663e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            //    keep on trying to ping
1664e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                            userLog("Ping aborted; retry");
1665e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        } else if (pingLength < 2000) {
1666b9781ea88a58e746b74076ec499e9885e195acc9Marc Blank                            userLog("Abort or NAT type return < 2 seconds; throwing IOException");
166774c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                            throw e;
166874c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                        } else {
166974c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank                            userLog("NAT type IOException > 2 seconds?");
16701b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        }
16711b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    } else {
16721b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                        throw e;
16731b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    }
1674ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
16752033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            } else if (forcePing) {
16762033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                // In this case, there aren't any boxes that are pingable, but there are boxes
16772033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                // waiting (for IOExceptions)
16782033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                userLog("pingLoop waiting 60s for any pingable boxes");
16792033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(60*SECONDS, true);
1680ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else if (pushCount > 0) {
16811ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank                // If we want to Ping, but can't just yet, wait a little bit
16821ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank                // TODO Change sleep to wait and use notify from SyncManager when a sync ends
16832033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(2*SECONDS, false);
16847672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank                pingWaitCount++;
16852033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                //userLog("pingLoop waited 2s for: ", (pushCount - canPushCount), " box(es)");
16868a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank            } else if (uninitCount > 0) {
16878a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // In this case, we're doing an initial sync of at least one mailbox.  Since this
16888a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // is typically a one-time case, I'm ok with trying again every 10 seconds until
16898a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank                // we're in one of the other possible states.
16902033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                userLog("pingLoop waiting for initial sync of ", uninitCount, " box(es)");
16912033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(10*SECONDS, true);
1692ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
1693c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                // We've got nothing to do, so we'll check again in 30 minutes at which time
16941b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                // we'll update the folder list.  Let the device sleep in the meantime...
16952033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                userLog("pingLoop sleeping for 30m");
16962033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                sleep(30*MINUTES, true);
1697ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1698ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
16992ff60f148dfc0ea1b35168a01d8c48de5658e073Marc Blank
17002ff60f148dfc0ea1b35168a01d8c48de5658e073Marc Blank        // Save away the current heartbeat
17012ff60f148dfc0ea1b35168a01d8c48de5658e073Marc Blank        mPingHeartbeat = pingHeartbeat;
1702ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1703ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
17042033dfc4e2e6e352b34565112266084d72c443f1Marc Blank    void sleep(long ms, boolean runAsleep) {
17052033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        if (runAsleep) {
17062033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            SyncManager.runAsleep(mMailboxId, ms+(5*SECONDS));
17072033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        }
1708ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
1709ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Thread.sleep(ms);
1710ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (InterruptedException e) {
1711ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Doesn't matter whether we stop early; it's the thought that counts
17122033dfc4e2e6e352b34565112266084d72c443f1Marc Blank        } finally {
17132033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            if (runAsleep) {
17142033dfc4e2e6e352b34565112266084d72c443f1Marc Blank                SyncManager.runAwake(mMailboxId);
17152033dfc4e2e6e352b34565112266084d72c443f1Marc Blank            }
1716ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1717ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1718ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
17198d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank    private int parsePingResult(InputStream is, ContentResolver cr,
17208d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            HashMap<String, Integer> errorMap)
1721ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        throws IOException, StaleFolderListException {
17228047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        PingParser pp = new PingParser(is, this);
1723ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (pp.parse()) {
1724ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // True indicates some mailboxes need syncing...
1725ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // syncList has the serverId's of the mailboxes...
1726ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mBindArguments[0] = Long.toString(mAccount.mId);
17271b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            mPingChangeList = pp.getSyncList();
17281b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            for (String serverId: mPingChangeList) {
1729d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                mBindArguments[1] = serverId;
1730ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
1731ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
1732ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                try {
1733ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (c.moveToFirst()) {
17348d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank
17358d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        /**
17368d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * Check the boxes reporting changes to see if there really were any...
17378d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * We do this because bugs in various Exchange servers can put us into a
17388d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * looping behavior by continually reporting changes in a mailbox, even when
17398d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * there aren't any.
17408d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         *
17418d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * This behavior is seemingly random, and therefore we must code defensively
17428d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * by backing off of push behavior when it is detected.
17438d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         *
17448d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * One known cause, on certain Exchange 2003 servers, is acknowledged by
17458d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * Microsoft, and the server hotfix for this case can be found at
17468d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         * http://support.microsoft.com/kb/923282
17478d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                         */
17488d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank
17498d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        // Check the status of the last sync
17508d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        String status = c.getString(Mailbox.CONTENT_SYNC_STATUS_COLUMN);
17518d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        int type = SyncManager.getStatusType(status);
17528d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        // This check should always be true...
17538d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        if (type == SyncManager.SYNC_PING) {
17548d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            int changeCount = SyncManager.getStatusChangeCount(status);
17558d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            if (changeCount > 0) {
17568d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                errorMap.remove(serverId);
17578d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            } else if (changeCount == 0) {
17588d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                // This means that a ping reported changes in error; we keep a count
17598d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                // of consecutive errors of this kind
17608d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                String name = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN);
17618d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                Integer failures = errorMap.get(serverId);
17628d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                if (failures == null) {
17638d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    userLog("Last ping reported changes in error for: ", name);
17648d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    errorMap.put(serverId, 1);
17658d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                } else if (failures > MAX_PING_FAILURES) {
17668d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    // We'll back off of push for this box
17678d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    pushFallback(c.getLong(Mailbox.CONTENT_ID_COLUMN));
17688d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    continue;
17698d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                } else {
17708d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    userLog("Last ping reported changes in error for: ", name);
17718d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                    errorMap.put(serverId, failures + 1);
17728d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                                }
17738d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                            }
17748d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        }
17758d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank
17768d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        // If there were no problems with previous sync, we'll start another one
17774d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                        SyncManager.startManualSync(c.getLong(Mailbox.CONTENT_ID_COLUMN),
1778ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank                                SyncManager.SYNC_PING, null);
1779ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
1780ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } finally {
1781ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    c.close();
1782ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1783ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1784ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
17851b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return pp.getSyncStatus();
1786ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1787ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
17885862a85e17e81866ca82a9905577931947fbd44eMarc Blank    private String getEmailFilter() {
1789368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        String filter = Eas.FILTER_1_WEEK;
1790368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        switch (mAccount.mSyncLookback) {
1791368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_DAY: {
1792368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_DAY;
1793368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1794368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1795368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_3_DAYS: {
1796368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_3_DAYS;
1797368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1798368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1799368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_WEEK: {
1800368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_WEEK;
1801368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1802368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1803368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_2_WEEKS: {
1804368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_2_WEEKS;
1805368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1806368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1807368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_MONTH: {
1808368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_1_MONTH;
1809368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1810368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1811368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            case com.android.email.Account.SYNC_WINDOW_ALL: {
1812368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                filter = Eas.FILTER_ALL;
1813368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                break;
1814368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank            }
1815368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        }
1816368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank        return filter;
1817368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    }
1818368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank
1819ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
1820ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Common code to sync E+PIM data
1821ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
1822ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @param target, an EasMailbox, EasContacts, or EasCalendar object
1823ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
18247c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank    public void sync(AbstractSyncAdapter target) throws IOException {
1825ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        Mailbox mailbox = target.mMailbox;
1826ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1827ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        boolean moreAvailable = true;
1828ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        while (!mStop && moreAvailable) {
18291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // If we have no connectivity, just exit cleanly.  SyncManager will start us up again
18301b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // when connectivity has returned
18311b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (!hasConnectivity()) {
18321b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                userLog("No connectivity in sync; finishing sync");
18331b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                mExitStatus = EXIT_DONE;
18341b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                return;
1835aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank            }
1836aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank
1837aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank            // Every time through the loop we check to see if we're still syncable
1838aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank            if (!target.isSyncable()) {
1839aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank                mExitStatus = EXIT_DONE;
1840aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank                return;
18411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
1842ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
18437531be7774769c84b499b1de5dc46da3a9468316Marc Blank            // Now, handle various requests
1844d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            while (true) {
18457531be7774769c84b499b1de5dc46da3a9468316Marc Blank                Request req = null;
18467531be7774769c84b499b1de5dc46da3a9468316Marc Blank                synchronized (mRequests) {
18477531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    if (mRequests.isEmpty()) {
1848d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        break;
1849d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    } else {
18507531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        req = mRequests.get(0);
1851d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                    }
1852d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
18537531be7774769c84b499b1de5dc46da3a9468316Marc Blank
18547531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // Our two request types are PartRequest (loading attachment) and
18557531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // MeetingResponseRequest (respond to a meeting request)
18567531be7774769c84b499b1de5dc46da3a9468316Marc Blank                if (req instanceof PartRequest) {
18577531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    getAttachment((PartRequest)req);
18587531be7774769c84b499b1de5dc46da3a9468316Marc Blank                } else if (req instanceof MeetingResponseRequest) {
18597531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    sendMeetingResponse((MeetingResponseRequest)req);
18607531be7774769c84b499b1de5dc46da3a9468316Marc Blank                }
18617531be7774769c84b499b1de5dc46da3a9468316Marc Blank
18627531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // If there's an exception handling the request, we'll throw it
18637531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // Otherwise, we remove the request
18647531be7774769c84b499b1de5dc46da3a9468316Marc Blank                synchronized(mRequests) {
18657531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    mRequests.remove(req);
1866d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
1867d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            }
1868d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
18697c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            Serializer s = new Serializer();
1870c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank
1871ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String className = target.getCollectionName();
1872c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank
1873c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank            // STOPSHIP Remove the following if statement; temporary logging for Calendar sync
18749972a855025df83129b9c7de4010024188219bd3Marc Blank            if (className.equals("Calendar") && Eas.PARSER_LOG) {
1875c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank                s = new Serializer(true, true);
1876c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank            }
1877c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank
187848af7392c82262d17700e3fbdccf3a582809d449Marc Blank            String syncKey = target.getSyncKey();
18798d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            userLog("sync, sending ", className, " syncKey: ", syncKey);
18807c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.start(Tags.SYNC_SYNC)
18817c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .start(Tags.SYNC_COLLECTIONS)
18827c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .start(Tags.SYNC_COLLECTION)
18837c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .data(Tags.SYNC_CLASS, className)
188448af7392c82262d17700e3fbdccf3a582809d449Marc Blank                .data(Tags.SYNC_SYNC_KEY, syncKey)
18857c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId)
18867c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .tag(Tags.SYNC_DELETES_AS_MOVES);
1887ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
188816b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            // Start with the default timeout
188916b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            int timeout = COMMAND_TIMEOUT;
189048af7392c82262d17700e3fbdccf3a582809d449Marc Blank            if (!syncKey.equals("0")) {
189116b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                // EAS doesn't like GetChanges if the syncKey is "0"; not documented
18927c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.tag(Tags.SYNC_GET_CHANGES);
189316b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            } else {
189416b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                // Use 2x timeout for initial sync, which empirically can take a while longer
189516b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                timeout <<= 1;
1896ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
18971b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            s.data(Tags.SYNC_WINDOW_SIZE,
18981b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                    className.equals("Email") ? EMAIL_WINDOW_SIZE : PIM_WINDOW_SIZE);
1899895d1e3132622653160516d420231ed366ab411bMarc Blank
1900895d1e3132622653160516d420231ed366ab411bMarc Blank            // Handle options
1901895d1e3132622653160516d420231ed366ab411bMarc Blank            s.start(Tags.SYNC_OPTIONS);
1902895d1e3132622653160516d420231ed366ab411bMarc Blank            // Set the lookback appropriately (EAS calls this a "filter") for all but Contacts
19035862a85e17e81866ca82a9905577931947fbd44eMarc Blank            if (className.equals("Email")) {
19045862a85e17e81866ca82a9905577931947fbd44eMarc Blank                s.data(Tags.SYNC_FILTER_TYPE, getEmailFilter());
19055862a85e17e81866ca82a9905577931947fbd44eMarc Blank            } else if (className.equals("Calendar")) {
190616b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                // TODO Force two weeks for calendar until we can set this!
190716b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                s.data(Tags.SYNC_FILTER_TYPE, Eas.FILTER_2_WEEKS);
1908ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1909895d1e3132622653160516d420231ed366ab411bMarc Blank            // Set the truncation amount for all classes
1910d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            if (mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
19117c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                s.start(Tags.BASE_BODY_PREFERENCE)
1912368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank                    // HTML for email; plain text for everything else
1913c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                    .data(Tags.BASE_TYPE, (className.equals("Email") ? Eas.BODY_PREFERENCE_HTML
1914895d1e3132622653160516d420231ed366ab411bMarc Blank                        : Eas.BODY_PREFERENCE_TEXT))
1915895d1e3132622653160516d420231ed366ab411bMarc Blank                    .data(Tags.BASE_TRUNCATION_SIZE, Eas.EAS12_TRUNCATION_SIZE)
19167c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    .end();
1917895d1e3132622653160516d420231ed366ab411bMarc Blank            } else {
1918895d1e3132622653160516d420231ed366ab411bMarc Blank                s.data(Tags.SYNC_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
1919ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1920895d1e3132622653160516d420231ed366ab411bMarc Blank            s.end();
1921ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1922ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Send our changes up to the server
192348af7392c82262d17700e3fbdccf3a582809d449Marc Blank            target.sendLocalChanges(s);
1924ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
19257c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.end().end().end().done();
192616b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            HttpResponse resp = sendHttpClientPost("Sync", new ByteArrayEntity(s.toByteArray()),
192716b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                    timeout);
19288047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            int code = resp.getStatusLine().getStatusCode();
19291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (code == HttpStatus.SC_OK) {
193095e8381b688152dd60e0eda84aef3bfc14fd1672Marc Blank                InputStream is = resp.getEntity().getContent();
1931ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                if (is != null) {
193248af7392c82262d17700e3fbdccf3a582809d449Marc Blank                    moreAvailable = target.parse(is);
193348af7392c82262d17700e3fbdccf3a582809d449Marc Blank                    target.cleanup();
19348d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                } else {
19358d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                    userLog("Empty input stream in sync command response");
1936ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1937ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
19380a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                userLog("Sync response error: ", code);
193920da011530088036d2bf45d3836d6986a4b5d423Marc Blank                if (isProvisionError(code)) {
194095fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank                    mExitStatus = EXIT_SECURITY_FAILURE;
194120da011530088036d2bf45d3836d6986a4b5d423Marc Blank                } else if (isAuthError(code)) {
19421b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_LOGIN_FAILURE;
19431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                } else {
19441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                    mExitStatus = EXIT_IO_ERROR;
1945ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1946ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return;
1947ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1948ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
19491b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        mExitStatus = EXIT_DONE;
1950ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1951ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
195222e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank    protected boolean setupService() {
19537ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        // Make sure account and mailbox are always the latest from the database
19547ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
195522e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (mAccount == null) return false;
19567ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId);
195722e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (mMailbox == null) return false;
1958ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mThread = Thread.currentThread();
19597310cbacf2cf614c949330faff3882082054c120Marc Blank        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
1960ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        TAG = mThread.getName();
19619d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
1962ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
196322e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (ha == null) return false;
1964ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mHostAddress = ha.mAddress;
1965ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mUserName = ha.mLogin;
1966ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mPassword = ha.mPassword;
19673852792f1c79dcfca6dc9be67f11e4a53788a462Marc Blank
1968fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        // Set up our protocol version from the Account
19693852792f1c79dcfca6dc9be67f11e4a53788a462Marc Blank        mProtocolVersion = mAccount.mProtocolVersion;
1970fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        // If it hasn't been set up, start with default version
1971fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        if (mProtocolVersion == null) {
1972d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            mProtocolVersion = Eas.DEFAULT_PROTOCOL_VERSION;
197354250c7c23b43b644961a8f241a3a3edcc9c796eMarc Blank        }
1974fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
197522e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        return true;
19767ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    }
19777ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank
19787ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    /* (non-Javadoc)
19797ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank     * @see java.lang.Runnable#run()
19807ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank     */
19817ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    public void run() {
198222e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (!setupService()) return;
1983ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1984fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        try {
1985fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            SyncManager.callback().syncMailboxStatus(mMailboxId, EmailServiceStatus.IN_PROGRESS, 0);
1986fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        } catch (RemoteException e1) {
1987fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            // Don't care if this fails
1988fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank        }
1989fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
1990a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank        // Whether or not we're the account mailbox
1991ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
19929d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank            mDeviceId = SyncManager.getDeviceId();
19937ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank            if ((mMailbox == null) || (mAccount == null)) {
1994147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank                return;
19957c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            } else if (mMailbox.mType == Mailbox.TYPE_EAS_ACCOUNT_MAILBOX) {
1996a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank                runAccountMailbox();
1997ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else {
19987c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                AbstractSyncAdapter target;
19997cf921b830554e52f88a45ca4a290b17d2a1b146Marc Blank                if (mMailbox.mType == Mailbox.TYPE_CONTACTS) {
20007c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    target = new ContactsSyncAdapter(mMailbox, this);
20015862a85e17e81866ca82a9905577931947fbd44eMarc Blank                } else if (mMailbox.mType == Mailbox.TYPE_CALENDAR) {
20025862a85e17e81866ca82a9905577931947fbd44eMarc Blank                    target = new CalendarSyncAdapter(mMailbox, this);
20037cf921b830554e52f88a45ca4a290b17d2a1b146Marc Blank                } else {
20047c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    target = new EmailSyncAdapter(mMailbox, this);
2005ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
2006ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // We loop here because someone might have put a request in while we were syncing
2007ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // and we've missed that opportunity...
2008ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                do {
2009ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (mRequestTime != 0) {
2010ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        userLog("Looping for user request...");
2011ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        mRequestTime = 0;
2012ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
2013ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    sync(target);
2014ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } while (mRequestTime != 0);
2015ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
20167531be7774769c84b499b1de5dc46da3a9468316Marc Blank        } catch (EasAuthenticationException e) {
20177531be7774769c84b499b1de5dc46da3a9468316Marc Blank            userLog("Caught authentication error");
20187531be7774769c84b499b1de5dc46da3a9468316Marc Blank            mExitStatus = EXIT_LOGIN_FAILURE;
2019ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (IOException e) {
2020f9423affa52661c2f552df35f0b9ddeecd8fa8feMarc Blank            String message = e.getMessage();
20214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("Caught IOException: ", (message == null) ? "No message" : message);
2022ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mExitStatus = EXIT_IO_ERROR;
2023ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (Exception e) {
20241431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank            userLog("Uncaught exception in EasSyncService", e);
2025ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
20262fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank            int status;
20272fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank
20284d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank            if (!mStop) {
20298d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                userLog("Sync finished");
20304d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank                SyncManager.done(this);
20315c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                switch (mExitStatus) {
20325c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_IO_ERROR:
20335c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.CONNECTION_ERROR;
20345c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
20355c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_DONE:
20365c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.SUCCESS;
20372fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        ContentValues cv = new ContentValues();
20382fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
20392fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        String s = "S" + mSyncReason + ':' + status + ':' + mChangeCount;
20402fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        cv.put(Mailbox.SYNC_STATUS, s);
20412fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                        mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI,
20422fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                                mMailboxId), cv, null, null);
20435c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
20445c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    case EXIT_LOGIN_FAILURE:
20455c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.LOGIN_FAILED;
20465c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
204720da011530088036d2bf45d3836d6986a4b5d423Marc Blank                    case EXIT_SECURITY_FAILURE:
204820da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        status = EmailServiceStatus.SECURITY_FAILURE;
204920da011530088036d2bf45d3836d6986a4b5d423Marc Blank                        break;
20505c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                    default:
20515c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        status = EmailServiceStatus.REMOTE_EXCEPTION;
205277424af660458104b732bdcb718874b17d0cab3aMarc Blank                        errorLog("Sync ended due to an exception.");
20535c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                        break;
20545c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                }
20554d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank            } else {
20568d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                userLog("Stopped sync finished.");
20572fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                status = EmailServiceStatus.SUCCESS;
20582fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank            }
20592fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank
20602fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank            try {
20612fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                SyncManager.callback().syncMailboxStatus(mMailboxId, status, 0);
20622fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank            } catch (RemoteException e1) {
20632fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank                // Don't care if this fails
2064fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank            }
20659d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank
2066c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            // Make sure SyncManager knows about this
2067c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            SyncManager.kick("sync finished");
20689d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank       }
2069ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
2070ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
2071