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
20d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.ContentResolver;
21d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.ContentUris;
22d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.ContentValues;
23d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.Context;
24d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.Entity;
25d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.database.Cursor;
26c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blankimport android.net.TrafficStats;
27d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.net.Uri;
28d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.os.Build;
29d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.os.Bundle;
30d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.os.RemoteException;
31d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.provider.CalendarContract.Attendees;
32d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.provider.CalendarContract.Events;
33d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.text.TextUtils;
34847322e4b75287643a8381e28401ee5cf4945a44Yu Ping Huimport android.text.format.DateUtils;
35d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.util.Base64;
36d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.util.Xml;
37d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blank
38c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blankimport com.android.emailcommon.TrafficFlags;
39c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.Address;
40c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.MeetingInfo;
41c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.MessagingException;
42c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.PackedString;
437372782488977df778a33d990401ce9e397f646bMarc Blankimport com.android.emailcommon.provider.Account;
44c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.AccountColumns;
45c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
46c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.MessageColumns;
47c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.SyncColumns;
48c936e6dc6b6979350083a8ab28a24ba8cdda45e0Ben Komaloimport com.android.emailcommon.provider.HostAuth;
494d8774462ace9a45154b2df418b9f2fe7a9c685dBen Komaloimport com.android.emailcommon.provider.Mailbox;
503ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blankimport com.android.emailcommon.provider.Policy;
510164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blankimport com.android.emailcommon.provider.ProviderUnavailableException;
5258dac270fd3a2fb1ff6cb5287ef9b61ea5080e41Marc Blankimport com.android.emailcommon.service.EmailServiceConstants;
5358dac270fd3a2fb1ff6cb5287ef9b61ea5080e41Marc Blankimport com.android.emailcommon.service.EmailServiceProxy;
5458dac270fd3a2fb1ff6cb5287ef9b61ea5080e41Marc Blankimport com.android.emailcommon.service.EmailServiceStatus;
55e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blankimport com.android.emailcommon.service.PolicyServiceProxy;
562bf91acb8174aab82582ce975a4b60702c810a9cMarc Blankimport com.android.emailcommon.utility.EmailClientConnectionManager;
572bf91acb8174aab82582ce975a4b60702c810a9cMarc Blankimport com.android.emailcommon.utility.Utility;
58e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blankimport com.android.emailsync.AbstractSyncService;
59e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blankimport com.android.emailsync.PartRequest;
60e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blankimport com.android.emailsync.Request;
6177186bb1a174432ef272584374942d8b9228e39cMarc Blankimport com.android.exchange.CommandStatusException.CommandStatus;
627c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.AbstractSyncAdapter;
6348af7392c82262d17700e3fbdccf3a582809d449Marc Blankimport com.android.exchange.adapter.AccountSyncAdapter;
64bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blankimport com.android.exchange.adapter.AttachmentLoader;
657c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.EmailSyncAdapter;
667c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.FolderSyncParser;
6796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadlerimport com.android.exchange.adapter.GalParser;
687531be7774769c84b499b1de5dc46da3a9468316Marc Blankimport com.android.exchange.adapter.MeetingResponseParser;
694471a6960d352242cc65bddf7888cc5335840c74Marc Blankimport com.android.exchange.adapter.MoveItemsParser;
70bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blankimport com.android.exchange.adapter.Parser.EmptyStreamException;
718692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blankimport com.android.exchange.adapter.ProvisionParser;
727c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.Serializer;
738b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blankimport com.android.exchange.adapter.SettingsParser;
747c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.Tags;
7596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadlerimport com.android.exchange.provider.GalResult;
765c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport com.android.exchange.utility.CalendarUtilities;
776ac502883abcecc4c80b26ed2b1a2a1e7308505cAlon Albertimport com.android.exchange.utility.CurlLogger;
78942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedyimport com.android.mail.utils.LogUtils;
79538ffdd571446a24d1436b5931c9f112ec200b51Marc Blankimport com.google.common.annotations.VisibleForTesting;
80ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
818047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.Header;
8200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.HttpEntity;
8300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.HttpResponse;
841b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blankimport org.apache.http.HttpStatus;
858047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.HttpClient;
868047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.methods.HttpOptions;
8700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.client.methods.HttpPost;
888047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.methods.HttpRequestBase;
898047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.entity.ByteArrayEntity;
904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.apache.http.entity.StringEntity;
9100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.impl.client.DefaultHttpClient;
928047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.BasicHttpParams;
938047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.HttpConnectionParams;
948047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.HttpParams;
956ac502883abcecc4c80b26ed2b1a2a1e7308505cAlon Albertimport org.apache.http.protocol.BasicHttpProcessor;
964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlPullParser;
974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlPullParserException;
984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlPullParserFactory;
994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlSerializer;
10000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
1014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport java.io.ByteArrayOutputStream;
10200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.IOException;
10300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.InputStream;
1045b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blankimport java.lang.Thread.State;
10500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.net.URI;
106e44d5875af006f4217718a1c0fc0e235af3863afMarc Blankimport java.security.cert.CertificateException;
10700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
108ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankpublic class EasSyncService extends AbstractSyncService {
1099b0ebbbe8e94b8da8b95ef4a84d76ab1e8f6a676Marc Blank    // DO NOT CHECK IN SET TO TRUE
1109b0ebbbe8e94b8da8b95ef4a84d76ab1e8f6a676Marc Blank    public static final boolean DEBUG_GAL_SERVICE = false;
111a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler
112e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    protected static final String PING_COMMAND = "Ping";
11316b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // Command timeout is the the time allowed for reading data from an open connection before an
11416b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // IOException is thrown.  After a small added allowance, our watchdog alarm goes off (allowing
11516b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // us to detect a silently dropped connection).  The allowance is defined below.
116847322e4b75287643a8381e28401ee5cf4945a44Yu Ping Hu    static public final int COMMAND_TIMEOUT = (int)(30 * DateUtils.SECOND_IN_MILLIS);
11716b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // Connection timeout is the time given to connect to the server before reporting an IOException
118847322e4b75287643a8381e28401ee5cf4945a44Yu Ping Hu    static private final int CONNECTION_TIMEOUT = (int)(20 * DateUtils.SECOND_IN_MILLIS);
11916b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank    // The extra time allowed beyond the COMMAND_TIMEOUT before which our watchdog alarm triggers
120847322e4b75287643a8381e28401ee5cf4945a44Yu Ping Hu    static private final int WATCHDOG_TIMEOUT_ALLOWANCE = (int)(30 * DateUtils.SECOND_IN_MILLIS);
121c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
1224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    static private final String AUTO_DISCOVER_SCHEMA_PREFIX =
1234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        "http://schemas.microsoft.com/exchange/autodiscover/mobilesync/";
1244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    static private final String AUTO_DISCOVER_PAGE = "/autodiscover/autodiscover.xml";
125e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    static protected final int EAS_REDIRECT_CODE = 451;
1264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
1276534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank    static public final int INTERNAL_SERVER_ERROR_CODE = 500;
1288f29d002c63fdd370e201ea92c532beac4e818e9Marc Blank
129b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    static public final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML";
130b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    static public final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML";
131b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
132a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank    static public final int MESSAGE_FLAG_MOVED_MESSAGE = 1 << Message.FLAG_SYNC_ADAPTER_SHIFT;
1335b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank    // The amount of time we allow for a thread to release its post lock after receiving an alert
134847322e4b75287643a8381e28401ee5cf4945a44Yu Ping Hu    static private final int POST_LOCK_TIMEOUT = (int)(10 * DateUtils.SECOND_IN_MILLIS);
1355b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
1367dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank    // The EAS protocol Provision status for "we implement all of the policies"
1377dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank    static private final String PROVISION_STATUS_OK = "1";
1387dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank    // The EAS protocol Provision status meaning "we partially implement the policies"
1397dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank    static private final String PROVISION_STATUS_PARTIAL = "2";
1407dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank
1410209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank    static /*package*/ final String DEVICE_TYPE = "Android";
142e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    static final String USER_AGENT = DEVICE_TYPE + '/' + Build.VERSION.RELEASE + '-' +
1430209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank        Eas.CLIENT_VERSION;
1440209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank
145e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    // Maximum number of times we'll allow a sync to "loop" with MoreAvailable true before
146e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    // forcing it to stop.  This number has been determined empirically.
147e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    static private final int MAX_LOOPING_COUNT = 100;
148ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // Reasonable default
149d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank    public String mProtocolVersion = Eas.DEFAULT_PROTOCOL_VERSION;
15000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank    public Double mProtocolVersionDouble;
15185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank    protected String mDeviceId = null;
1526dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank    @VisibleForTesting
1536dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank    String mAuthString = null;
1546dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank    @VisibleForTesting
1556dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank    String mUserString = null;
1566dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank    @VisibleForTesting
1576dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank    String mBaseUriString = null;
158ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mHostAddress;
159ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mUserName;
160ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String mPassword;
161e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo
162e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    // The HttpPost in progress
163e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    private volatile HttpPost mPendingPost = null;
164e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    // Whether a POST was aborted due to alarm (watchdog alarm)
165e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    protected boolean mPostAborted = false;
166e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    // Whether a POST was aborted due to reset
167e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    protected boolean mPostReset = false;
168e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank
169e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo    // The parameters for the connection must be modified through setConnectionParameters
1703fedf8da5e2bd8c5b5a128ffe27e6bb42ebc611dMarc Blank    private HostAuth mHostAuth;
1711b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank    private boolean mSsl = true;
1725843b85178a359446f81770ed7734604a1b2fa7dMarc Blank    private boolean mTrustSsl = false;
173e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo    private String mClientCertAlias = null;
174e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo
175ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public ContentResolver mContentResolver;
176ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank    // Whether or not the sync service is valid (usable)
177ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank    public boolean mIsValid = true;
178ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
17961c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank    // Whether the most recent upsync failed (status 7)
18061c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank    public boolean mUpsyncFailed = false;
18161c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank
182e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    protected EasSyncService(Context _context, Mailbox _mailbox) {
183ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        super(_context, _mailbox);
184ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mContentResolver = _context.getContentResolver();
185ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        if (mAccount == null) {
186ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            mIsValid = false;
187ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            return;
188ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        }
189ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv);
190ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        if (ha == null) {
191ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            mIsValid = false;
192ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank            return;
193ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank        }
194ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
195c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank        mTrustSsl = (ha.mFlags & HostAuth.FLAG_TRUST_ALL) != 0;
196ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
197ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
198ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private EasSyncService(String prefix) {
199ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        super(prefix);
200ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
201ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
202ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public EasSyncService() {
203ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        this("EAS Validation");
204ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
205ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
206e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    public static EasSyncService getServiceForMailbox(Context context, Mailbox m) {
207e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        switch(m.mType) {
208e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            case Mailbox.TYPE_EAS_ACCOUNT_MAILBOX:
209e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                return new EasAccountService(context, m);
210e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            case Mailbox.TYPE_OUTBOX:
211e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                return new EasOutboxService(context, m);
212e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            default:
213e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                return new EasSyncService(context, m);
214e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        }
215e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    }
216e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank
217847322e4b75287643a8381e28401ee5cf4945a44Yu Ping Hu    @Override
218e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    public void resetCalendarSyncKey() {
219de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // resetCalendarSyncKey is declared in AbstractSyncAdapterService,
220de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // so we need to define this function, by since CalendarSyncAdapter no longer exists,
221de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon        // we need to remove this:
222de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//        CalendarSyncAdapter adapter = new CalendarSyncAdapter(this);
223de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//            try {
224de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                adapter.setSyncKey("0", false);
225de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//            } catch (IOException e) {
226de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                // The provider can't be reached; nothing to be done
227de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//            }
228de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon
229de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon     // For reference, this is what CalendarSyncAdapter.setSyncKey() did:
230de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//        synchronized (sSyncKeyLock) {
231de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//            if ("0".equals(syncKey) || !inCommands) {
232de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                ContentProviderClient client = mService.mContentResolver
233de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                        .acquireContentProviderClient(CalendarContract.CONTENT_URI);
234de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                try {
235de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                    SyncStateContract.Helpers.set(
236de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                            client,
237de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                            asSyncAdapter(SyncState.CONTENT_URI, mEmailAddress,
238de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                                    Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), mAccountManagerAccount,
239de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                            syncKey.getBytes());
240de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                    userLog("SyncKey set to ", syncKey, " in CalendarProvider");
241de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                } catch (RemoteException e) {
242de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                    throw new IOException("Can't set SyncKey in CalendarProvider");
243de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                }
244de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//            }
245de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//            mMailbox.mSyncKey = syncKey;
246de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//        }
247e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    }
248e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank
2495b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank    /**
2505b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     * Try to wake up a sync thread that is waiting on an HttpClient POST and has waited past its
2515b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     * socket timeout without having thrown an Exception
2525b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     *
2535b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     * @return true if the POST was successfully stopped; false if we've failed and interrupted
2545b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     * the thread
2555b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank     */
256e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo    @Override
2575b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank    public boolean alarm() {
2585b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        HttpPost post;
2591d7c38cd7be83e124b0c7e952b85234d4ee64f47Marc Blank        if (mThread == null) return true;
2605b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        String threadName = mThread.getName();
2615b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
2625b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        // Synchronize here so that we are guaranteed to have valid mPendingPost and mPostLock
2635b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        // executePostWithTimeout (which executes the HttpPost) also uses this lock
2641b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        synchronized(getSynchronizer()) {
2655b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            // Get a reference to the current post lock
2665b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            post = mPendingPost;
2675b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            if (post != null) {
2685b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                if (Eas.USER_LOG) {
2695b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                    URI uri = post.getURI();
2705b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                    if (uri != null) {
2715b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        String query = uri.getQuery();
2725b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        if (query == null) {
2735b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                            query = "POST";
2745b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        }
2755b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        userLog(threadName, ": Alert, aborting ", query);
2765b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                    } else {
2775b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                        userLog(threadName, ": Alert, no URI?");
278e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    }
279e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                }
2805b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                // Abort the POST
281ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank                mPostAborted = true;
2825b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                post.abort();
283e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank            } else {
2845b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                // If there's no POST, we're done
285e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                userLog("Alert, no pending POST");
2865b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                return true;
2875b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            }
2885b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        }
2895b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
2905b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        // Wait for the POST to finish
2915b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        try {
2925b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            Thread.sleep(POST_LOCK_TIMEOUT);
2935b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        } catch (InterruptedException e) {
2945b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        }
2955b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
2965b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        State s = mThread.getState();
2975b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        if (Eas.USER_LOG) {
2985b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            userLog(threadName + ": State = " + s.name());
2995b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        }
3005b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank
3015b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        synchronized (getSynchronizer()) {
3025b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            // If the thread is still hanging around and the same post is pending, let's try to
3035b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            // stop the thread with an interrupt.
3045b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank            if ((s != State.TERMINATED) && (mPendingPost != null) && (mPendingPost == post)) {
3055b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                mStop = true;
3065b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                mThread.interrupt();
3075b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                userLog("Interrupting...");
3085b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                // Let the caller know we had to interrupt the thread
3095b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank                return false;
310e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank            }
311e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank        }
3125b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        // Let the caller know that the alarm was handled normally
3135b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank        return true;
314e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    }
315e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank
316e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    @Override
317e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank    public void reset() {
318e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank        synchronized(getSynchronizer()) {
319e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank            if (mPendingPost != null) {
320e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                URI uri = mPendingPost.getURI();
321e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                if (uri != null) {
322e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    String query = uri.getQuery();
323e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    if (query.startsWith("Cmd=Ping")) {
324e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        userLog("Reset, aborting Ping");
325e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        mPostReset = true;
326e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                        mPendingPost.abort();
327e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                    }
328e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank                }
3291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
330ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
331ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
332ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
333ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
334ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void stop() {
335ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mStop = true;
336c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        synchronized(getSynchronizer()) {
337c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            if (mPendingPost != null) {
338c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank                mPendingPost.abort();
339c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank            }
340c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
3415c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank    }
342ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
343e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    void setupProtocolVersion(EasSyncService service, Header versionHeader)
344d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            throws MessagingException {
345d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // The string is a comma separated list of EAS versions in ascending order
34677186bb1a174432ef272584374942d8b9228e39cMarc Blank        // e.g. 1.0,2.0,2.5,12.0,12.1,14.0,14.1
347d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        String supportedVersions = versionHeader.getValue();
348d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        userLog("Server supports versions: ", supportedVersions);
349d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        String[] supportedVersionsArray = supportedVersions.split(",");
350d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        String ourVersion = null;
351d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // Find the most recent version we support
352d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        for (String version: supportedVersionsArray) {
353d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            if (version.equals(Eas.SUPPORTED_PROTOCOL_EX2003) ||
354e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank                    version.equals(Eas.SUPPORTED_PROTOCOL_EX2007) ||
35577186bb1a174432ef272584374942d8b9228e39cMarc Blank                    version.equals(Eas.SUPPORTED_PROTOCOL_EX2007_SP1) ||
35677186bb1a174432ef272584374942d8b9228e39cMarc Blank                    version.equals(Eas.SUPPORTED_PROTOCOL_EX2010) ||
35777186bb1a174432ef272584374942d8b9228e39cMarc Blank                    version.equals(Eas.SUPPORTED_PROTOCOL_EX2010_SP1)) {
358d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                ourVersion = version;
3598692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            }
360d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        }
361d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // If we don't support any of the servers supported versions, throw an exception here
362d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        // This will cause validation to fail
363d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        if (ourVersion == null) {
364942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy            LogUtils.w(TAG, "No supported EAS versions: " + supportedVersions);
365d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            throw new MessagingException(MessagingException.PROTOCOL_VERSION_UNSUPPORTED);
366d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank        } else {
3678b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank            // Debug code for testing EAS 14.0; disables support for EAS 14.1
3688b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank            // "adb shell setprop log.tag.Exchange14 VERBOSE"
3698b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank            if (ourVersion.equals(Eas.SUPPORTED_PROTOCOL_EX2010_SP1) &&
370942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy                    LogUtils.isLoggable("Exchange14", LogUtils.VERBOSE)) {
3718b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                ourVersion = Eas.SUPPORTED_PROTOCOL_EX2010;
3728b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank            }
373d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            service.mProtocolVersion = ourVersion;
374cb5c48824628e93e98ca24edec46f05c54851af1Marc Blank            service.mProtocolVersionDouble = Eas.getProtocolVersionDouble(ourVersion);
37582c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank            Account account = service.mAccount;
37682c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank            if (account != null) {
37782c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank                account.mProtocolVersion = ourVersion;
37882c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank                // Fixup search flags, if they're not set
37982c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank                if (service.mProtocolVersionDouble >= 12.0 &&
38082c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank                        (account.mFlags & Account.FLAGS_SUPPORTS_SEARCH) == 0) {
381097cd98bed66adfffc623f977b776974296cc3faMarc Blank                    if (account.isSaved()) {
382097cd98bed66adfffc623f977b776974296cc3faMarc Blank                        ContentValues cv = new ContentValues();
383097cd98bed66adfffc623f977b776974296cc3faMarc Blank                        account.mFlags |=
384097cd98bed66adfffc623f977b776974296cc3faMarc Blank                            Account.FLAGS_SUPPORTS_GLOBAL_SEARCH + Account.FLAGS_SUPPORTS_SEARCH;
385097cd98bed66adfffc623f977b776974296cc3faMarc Blank                        cv.put(AccountColumns.FLAGS, account.mFlags);
386097cd98bed66adfffc623f977b776974296cc3faMarc Blank                        account.update(service.mContext, cv);
387097cd98bed66adfffc623f977b776974296cc3faMarc Blank                    }
38882c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank                }
3898692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            }
3908692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        }
3918692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    }
3928692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
39326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank    /**
39426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     * Create an EasSyncService for the specified account
39526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     *
39626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     * @param context the caller's context
39726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     * @param account the account
39826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     * @return the service, or null if the account is on hold or hasn't been initialized
39926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     */
40077e0f0dc7b064e0a320d11c33ce9da2f8ae5ba3bMarc Blank    public static EasSyncService setupServiceForAccount(Context context, Account account) {
40126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        // Just return null if we're on security hold
40226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
40326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            return null;
40426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        }
40526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        // If there's no protocol version, we're not initialized
40626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        String protocolVersion = account.mProtocolVersion;
40726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        if (protocolVersion == null) {
40826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            return null;
40926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        }
41026d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        EasSyncService svc = new EasSyncService("OutOfBand");
41126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv);
41226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        svc.mProtocolVersion = protocolVersion;
41326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        svc.mProtocolVersionDouble = Eas.getProtocolVersionDouble(protocolVersion);
41426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        svc.mContext = context;
41526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        svc.mHostAddress = ha.mAddress;
41626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        svc.mUserName = ha.mLogin;
41726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        svc.mPassword = ha.mPassword;
41826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        try {
419e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            svc.setConnectionParameters(ha);
42026d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            svc.mDeviceId = ExchangeService.getDeviceId(context);
42136a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo        } catch (CertificateException e) {
42236a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo            return null;
42326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        }
42426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        svc.mAccount = account;
42526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        return svc;
42626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank    }
42726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank
42848ac73e73700994435bf182a3bccb43e96b68811Marc Blank    /**
42948ac73e73700994435bf182a3bccb43e96b68811Marc Blank     * Get a redirect address and validate against it
43048ac73e73700994435bf182a3bccb43e96b68811Marc Blank     * @param resp the EasResponse to our POST
43148ac73e73700994435bf182a3bccb43e96b68811Marc Blank     * @param hostAuth the HostAuth we're using to validate
43248ac73e73700994435bf182a3bccb43e96b68811Marc Blank     * @return true if we have an updated HostAuth (with redirect address); false otherwise
43348ac73e73700994435bf182a3bccb43e96b68811Marc Blank     */
434e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    protected boolean getValidateRedirect(EasResponse resp, HostAuth hostAuth) {
43548ac73e73700994435bf182a3bccb43e96b68811Marc Blank        Header locHeader = resp.getHeader("X-MS-Location");
43648ac73e73700994435bf182a3bccb43e96b68811Marc Blank        if (locHeader != null) {
43748ac73e73700994435bf182a3bccb43e96b68811Marc Blank            String loc;
43848ac73e73700994435bf182a3bccb43e96b68811Marc Blank            try {
43948ac73e73700994435bf182a3bccb43e96b68811Marc Blank                loc = locHeader.getValue();
44048ac73e73700994435bf182a3bccb43e96b68811Marc Blank                // Reset our host address and uncache our base uri
44148ac73e73700994435bf182a3bccb43e96b68811Marc Blank                mHostAddress = Uri.parse(loc).getHost();
44248ac73e73700994435bf182a3bccb43e96b68811Marc Blank                mBaseUriString = null;
44348ac73e73700994435bf182a3bccb43e96b68811Marc Blank                hostAuth.mAddress = mHostAddress;
44448ac73e73700994435bf182a3bccb43e96b68811Marc Blank                userLog("Redirecting to: " + loc);
44548ac73e73700994435bf182a3bccb43e96b68811Marc Blank                return true;
44648ac73e73700994435bf182a3bccb43e96b68811Marc Blank            } catch (RuntimeException e) {
44748ac73e73700994435bf182a3bccb43e96b68811Marc Blank                // Just don't crash if the Uri is illegal
44848ac73e73700994435bf182a3bccb43e96b68811Marc Blank            }
44948ac73e73700994435bf182a3bccb43e96b68811Marc Blank        }
45048ac73e73700994435bf182a3bccb43e96b68811Marc Blank        return false;
45148ac73e73700994435bf182a3bccb43e96b68811Marc Blank    }
45248ac73e73700994435bf182a3bccb43e96b68811Marc Blank
45348ac73e73700994435bf182a3bccb43e96b68811Marc Blank    private static final int MAX_REDIRECTS = 3;
45448ac73e73700994435bf182a3bccb43e96b68811Marc Blank    private int mRedirectCount = 0;
45548ac73e73700994435bf182a3bccb43e96b68811Marc Blank
456fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank    @Override
45748ac73e73700994435bf182a3bccb43e96b68811Marc Blank    public Bundle validateAccount(HostAuth hostAuth, Context context) {
4587b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank        Bundle bundle = new Bundle();
4597b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank        int resultCode = MessagingException.NO_ERROR;
460ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
46191e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo            userLog("Testing EAS: ", hostAuth.mAddress, ", ", hostAuth.mLogin,
46287b42fc62ef62eed2188d11da4f0a8e0b635230aBen Komalo                    ", ssl = ", hostAuth.shouldUseSsl() ? "1" : "0");
4633b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank            mContext = context;
4643b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank            mHostAddress = hostAuth.mAddress;
4653b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank            mUserName = hostAuth.mLogin;
4663b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank            mPassword = hostAuth.mPassword;
467e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo
468e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            setConnectionParameters(hostAuth);
4693b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank            mDeviceId = ExchangeService.getDeviceId(context);
4703b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank            mAccount = new Account();
4713b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank            mAccount.mEmailAddress = hostAuth.mLogin;
4723b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank            EasResponse resp = sendHttpClientOptions();
47354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            try {
474bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                int code = resp.getStatus();
47554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                userLog("Validation (OPTIONS) response: " + code);
47654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                if (code == HttpStatus.SC_OK) {
47754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // No exception means successful validation
478bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    Header commands = resp.getHeader("MS-ASProtocolCommands");
479bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    Header versions = resp.getHeader("ms-asprotocolversions");
48054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // Make sure we've got the right protocol version set up
48154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    try {
48254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        if (commands == null || versions == null) {
48354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            userLog("OPTIONS response without commands or versions");
48454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            // We'll treat this as a protocol exception
48554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            throw new MessagingException(0);
48654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        }
4873b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank                        setupProtocolVersion(this, versions);
48854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    } catch (MessagingException e) {
48954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE,
49054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                                MessagingException.PROTOCOL_VERSION_UNSUPPORTED);
49154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        return bundle;
4927b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank                    }
4938692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
49454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // Run second test here for provisioning failures using FolderSync
49554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    userLog("Try folder sync");
49654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // Send "0" as the sync key for new accounts; otherwise, use the current key
49754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    String syncKey = "0";
49891e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo                    Account existingAccount = Utility.findExistingAccount(
49991e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo                            context, -1L, hostAuth.mAddress, hostAuth.mLogin);
50054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    if (existingAccount != null && existingAccount.mSyncKey != null) {
50154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        syncKey = existingAccount.mSyncKey;
50254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    }
50354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    Serializer s = new Serializer();
50454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey)
50554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        .end().end().done();
5063b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank                    resp = sendHttpClientPost("FolderSync", s.toByteArray());
507bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    code = resp.getStatus();
5080b06bcd31ccdfd15478e8ee846fd963775ca036aMarc Blank                    // Handle HTTP error responses accordingly
5090b06bcd31ccdfd15478e8ee846fd963775ca036aMarc Blank                    if (code == HttpStatus.SC_FORBIDDEN) {
5100b06bcd31ccdfd15478e8ee846fd963775ca036aMarc Blank                        // For validation only, we take 403 as ACCESS_DENIED (the account isn't
5110b06bcd31ccdfd15478e8ee846fd963775ca036aMarc Blank                        // authorized, possibly due to device type)
5120b06bcd31ccdfd15478e8ee846fd963775ca036aMarc Blank                        resultCode = MessagingException.ACCESS_DENIED;
513ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu                    } else if (resp.isProvisionError()) {
5140b06bcd31ccdfd15478e8ee846fd963775ca036aMarc Blank                        // The device needs to have security policies enforced
51577186bb1a174432ef272584374942d8b9228e39cMarc Blank                        throw new CommandStatusException(CommandStatus.NEEDS_PROVISIONING);
51654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    } else if (code == HttpStatus.SC_NOT_FOUND) {
51754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        // We get a 404 from OWA addresses (which are NOT EAS addresses)
51854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        resultCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED;
5191b52d3a2a7526810c18c040665d75a467b12677cMarc Blank                    } else if (code == HttpStatus.SC_UNAUTHORIZED) {
5200b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo                        resultCode = resp.isMissingCertificate()
52136a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo                                ? MessagingException.CLIENT_CERTIFICATE_REQUIRED
5220b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo                                : MessagingException.AUTHENTICATION_FAILED;
52354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    } else if (code != HttpStatus.SC_OK) {
52448ac73e73700994435bf182a3bccb43e96b68811Marc Blank                        if ((code == EAS_REDIRECT_CODE) && (mRedirectCount++ < MAX_REDIRECTS) &&
52548ac73e73700994435bf182a3bccb43e96b68811Marc Blank                                getValidateRedirect(resp, hostAuth)) {
52648ac73e73700994435bf182a3bccb43e96b68811Marc Blank                            return validateAccount(hostAuth, context);
52748ac73e73700994435bf182a3bccb43e96b68811Marc Blank                        }
52854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        // Fail generically with anything other than success
52954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        userLog("Unexpected response for FolderSync: ", code);
53054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        resultCode = MessagingException.UNSPECIFIED_EXCEPTION;
53154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    } else {
53277186bb1a174432ef272584374942d8b9228e39cMarc Blank                        // We need to parse the result to see if we've got a provisioning issue
53377186bb1a174432ef272584374942d8b9228e39cMarc Blank                        // (EAS 14.0 only)
53477186bb1a174432ef272584374942d8b9228e39cMarc Blank                        if (!resp.isEmpty()) {
53577186bb1a174432ef272584374942d8b9228e39cMarc Blank                            InputStream is = resp.getInputStream();
53677186bb1a174432ef272584374942d8b9228e39cMarc Blank                            // Create the parser with statusOnly set to true; we only care about
53777186bb1a174432ef272584374942d8b9228e39cMarc Blank                            // seeing if a CommandStatusException is thrown (indicating a
53877186bb1a174432ef272584374942d8b9228e39cMarc Blank                            // provisioning failure)
5393b73884f0524f21ad4a37127c8bcdaafca193e28Marc Blank                            new FolderSyncParser(is, new AccountSyncAdapter(this), true).parse();
54077186bb1a174432ef272584374942d8b9228e39cMarc Blank                        }
54154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        userLog("Validation successful");
54254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    }
543ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu                } else if (resp.isAuthError()) {
54454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    userLog("Authentication failed");
5450b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo                    resultCode = resp.isMissingCertificate()
54636a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo                            ? MessagingException.CLIENT_CERTIFICATE_REQUIRED
5470b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo                            : MessagingException.AUTHENTICATION_FAILED;
54854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                } else if (code == INTERNAL_SERVER_ERROR_CODE) {
54954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // For Exchange 2003, this could mean an authentication failure OR server error
55054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    userLog("Internal server error");
55154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    resultCode = MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR;
5527b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank                } else {
55348ac73e73700994435bf182a3bccb43e96b68811Marc Blank                    if ((code == EAS_REDIRECT_CODE) && (mRedirectCount++ < MAX_REDIRECTS) &&
55448ac73e73700994435bf182a3bccb43e96b68811Marc Blank                            getValidateRedirect(resp, hostAuth)) {
55548ac73e73700994435bf182a3bccb43e96b68811Marc Blank                        return validateAccount(hostAuth, context);
55648ac73e73700994435bf182a3bccb43e96b68811Marc Blank                    }
55754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // TODO Need to catch other kinds of errors (e.g. policy) For now, report code.
55854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    userLog("Validation failed, reporting I/O error: ", code);
55954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    resultCode = MessagingException.IOERROR;
56054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                }
56177186bb1a174432ef272584374942d8b9228e39cMarc Blank            } catch (CommandStatusException e) {
56277186bb1a174432ef272584374942d8b9228e39cMarc Blank                int status = e.mStatus;
56377186bb1a174432ef272584374942d8b9228e39cMarc Blank                if (CommandStatus.isNeedsProvisioning(status)) {
56477186bb1a174432ef272584374942d8b9228e39cMarc Blank                    // Get the policies and see if we are able to support them
565e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    ProvisionParser pp = canProvision(this);
5663524b49b0e8eda6a3f5d46f08fa90b44fcb5318eMarc Blank                    if (pp != null && pp.hasSupportablePolicySet()) {
56777186bb1a174432ef272584374942d8b9228e39cMarc Blank                        // Set the proper result code and save the PolicySet in our Bundle
56877186bb1a174432ef272584374942d8b9228e39cMarc Blank                        resultCode = MessagingException.SECURITY_POLICIES_REQUIRED;
56977186bb1a174432ef272584374942d8b9228e39cMarc Blank                        bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET,
5703ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank                                pp.getPolicy());
5718b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                        if (mProtocolVersionDouble == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
5728b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                            mAccount.mSecuritySyncKey = pp.getSecuritySyncKey();
5738b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                            if (!sendSettings()) {
5748b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                                userLog("Denied access: ", CommandStatus.toString(status));
5758b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                                resultCode = MessagingException.ACCESS_DENIED;
5768b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                            }
5778b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                        }
578e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    } else {
57977186bb1a174432ef272584374942d8b9228e39cMarc Blank                        // If not, set the proper code (the account will not be created)
58077186bb1a174432ef272584374942d8b9228e39cMarc Blank                        resultCode = MessagingException.SECURITY_POLICIES_UNSUPPORTED;
581e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET,
582e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                                pp.getPolicy());
583e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    }
58477186bb1a174432ef272584374942d8b9228e39cMarc Blank                } else if (CommandStatus.isDeniedAccess(status)) {
58577186bb1a174432ef272584374942d8b9228e39cMarc Blank                    userLog("Denied access: ", CommandStatus.toString(status));
58677186bb1a174432ef272584374942d8b9228e39cMarc Blank                    resultCode = MessagingException.ACCESS_DENIED;
58777186bb1a174432ef272584374942d8b9228e39cMarc Blank                } else if (CommandStatus.isTransientError(status)) {
58877186bb1a174432ef272584374942d8b9228e39cMarc Blank                    userLog("Transient error: ", CommandStatus.toString(status));
58977186bb1a174432ef272584374942d8b9228e39cMarc Blank                    resultCode = MessagingException.IOERROR;
59077186bb1a174432ef272584374942d8b9228e39cMarc Blank                } else {
59177186bb1a174432ef272584374942d8b9228e39cMarc Blank                    userLog("Unexpected response: ", CommandStatus.toString(status));
59277186bb1a174432ef272584374942d8b9228e39cMarc Blank                    resultCode = MessagingException.UNSPECIFIED_EXCEPTION;
59377186bb1a174432ef272584374942d8b9228e39cMarc Blank                }
59454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            } finally {
595bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                resp.close();
596bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank           }
597ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (IOException e) {
598e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            Throwable cause = e.getCause();
599e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            if (cause != null && cause instanceof CertificateException) {
60036a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo                // This could be because the server's certificate failed to validate.
601e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank                userLog("CertificateException caught: ", e.getMessage());
6027b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank                resultCode = MessagingException.GENERAL_SECURITY;
603e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            }
604e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank            userLog("IOException caught: ", e.getMessage());
6057b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank            resultCode = MessagingException.IOERROR;
60636a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo        } catch (CertificateException e) {
60736a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo            // This occurs if the client certificate the user specified is invalid/inaccessible.
60836a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo            userLog("CertificateException caught: ", e.getMessage());
60936a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo            resultCode = MessagingException.CLIENT_CERTIFICATE_ERROR;
610ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
6117b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank        bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, resultCode);
6127b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank        return bundle;
613ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
614ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
6154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    /**
6164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * Gets the redirect location from the HTTP headers and uses that to modify the HttpPost so that
6174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * it can be reused
6184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     *
6194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param resp the HttpResponse that indicates a redirect (451)
6204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param post the HttpPost that was originally sent to the server
6214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @return the HttpPost, updated with the redirect location
6224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     */
623942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy    private static HttpPost getRedirect(HttpResponse resp, HttpPost post) {
6244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        Header locHeader = resp.getFirstHeader("X-MS-Location");
6254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        if (locHeader != null) {
6264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            String loc = locHeader.getValue();
6274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // If we've gotten one and it shows signs of looking like an address, we try
6284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // sending our request there
6294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (loc != null && loc.startsWith("http")) {
6304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                post.setURI(URI.create(loc));
6314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                return post;
6324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
6334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return null;
6354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    /**
6384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * Send the POST command to the autodiscover server, handling a redirect, if necessary, and
63983ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler     * return the HttpResponse.  If we get a 401 (unauthorized) error and we're using the
64083ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler     * full email address, try the bare user name instead (e.g. foo instead of foo@bar.com)
6414d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     *
6424d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param client the HttpClient to be used for the request
6434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @param post the HttpPost we're going to send
64483ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler     * @param canRetry whether we can retry using the bare name on an authentication failure (401)
6454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @return an HttpResponse from the original or redirect server
6464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @throws IOException on any IOException within the HttpClient code
6474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @throws MessagingException
6484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     */
649bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank    private EasResponse postAutodiscover(HttpClient client, HttpPost post, boolean canRetry)
6504d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws IOException, MessagingException {
6514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        userLog("Posting autodiscover to: " + post.getURI());
652bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank        EasResponse resp = executePostWithTimeout(client, post, COMMAND_TIMEOUT);
653bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank        int code = resp.getStatus();
6544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        // On a redirect, try the new location
65548ac73e73700994435bf182a3bccb43e96b68811Marc Blank        if (code == EAS_REDIRECT_CODE) {
656ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu            //post = getRedirect(resp.mResponse, post);
6574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (post != null) {
6584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                userLog("Posting autodiscover to redirect: " + post.getURI());
659adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                return executePostWithTimeout(client, post, COMMAND_TIMEOUT);
6604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
66183ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler        // 401 (Unauthorized) is for true auth errors when used in Autodiscover
66252f7f7effdd320594cd1c5c8c67443ad51eca3afMarc Blank        } else if (code == HttpStatus.SC_UNAUTHORIZED) {
66383ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler            if (canRetry && mUserName.contains("@")) {
66483ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                // Try again using the bare user name
66583ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                int atSignIndex = mUserName.indexOf('@');
66683ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                mUserName = mUserName.substring(0, atSignIndex);
6676dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank                cacheAuthUserAndBaseUriStrings();
66883ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                userLog("401 received; trying username: ", mUserName);
66983ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                // Recreate the basic authentication string and reset the header
67083ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                post.removeHeaders("Authorization");
67183ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                post.setHeader("Authorization", mAuthString);
67283ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                return postAutodiscover(client, post, false);
67383ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler            }
6744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throw new MessagingException(MessagingException.AUTHENTICATION_FAILED);
67583ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler        // 403 (and others) we'll just punt on
6764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        } else if (code != HttpStatus.SC_OK) {
6774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // We'll try the next address if this doesn't work
6784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("Code: " + code + ", throwing IOException");
6794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throw new IOException();
6804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
6814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return resp;
6824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
6834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
6844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    /**
685e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank     * Convert an EAS server url to a HostAuth host address
686e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank     * @param url a url, as provided by the Exchange server
687e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank     * @return our equivalent host address
688e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank     */
689e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    protected String autodiscoverUrlToHostAddress(String url) {
690e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        if (url == null) return null;
691e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        // We need to extract the server address from a url
692e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        return Uri.parse(url).getHost();
693e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    }
694e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank
695e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    /**
6964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * Use the Exchange 2007 AutoDiscover feature to try to retrieve server information using
6974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * only an email address and the password
6984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     *
699e2788afb86984dae29255fd9c7e24c78d90dbdadYu Ping Hu     * @param context Our {@link Context}.
7004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     * @return a HostAuth ready to be saved in an Account or null (failure)
7014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank     */
702fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank    public Bundle tryAutodiscover(Context context, HostAuth hostAuth) throws RemoteException {
7034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        XmlSerializer s = Xml.newSerializer();
7044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
7054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        Bundle bundle = new Bundle();
7064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
7074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                MessagingException.NO_ERROR);
7084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        try {
7094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Build the XML document that's sent to the autodiscover server(s)
7104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.setOutput(os, "UTF-8");
7114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startDocument("UTF-8", false);
7124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "Autodiscover");
7134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.attribute(null, "xmlns", AUTO_DISCOVER_SCHEMA_PREFIX + "requestschema/2006");
7144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "Request");
715fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank            s.startTag(null, "EMailAddress").text(hostAuth.mLogin).endTag(null, "EMailAddress");
7164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.startTag(null, "AcceptableResponseSchema");
7174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.text(AUTO_DISCOVER_SCHEMA_PREFIX + "responseschema/2006");
7184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endTag(null, "AcceptableResponseSchema");
7194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endTag(null, "Request");
7204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endTag(null, "Autodiscover");
7214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            s.endDocument();
7224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            String req = os.toString();
7234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
724fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank            // Initialize user name, password, etc.
725fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank            mContext = context;
726fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank            mHostAuth = hostAuth;
727fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank            mUserName = hostAuth.mLogin;
728fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank            mPassword = hostAuth.mPassword;
729fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank            mSsl = hostAuth.shouldUseSsl();
730e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank
73183ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler            // Make sure the authentication string is recreated and cached
7326dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank            cacheAuthUserAndBaseUriStrings();
7334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
7344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Split out the domain name
735fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank            int amp = mUserName.indexOf('@');
7364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // The UI ensures that userName is a valid email address
7374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (amp < 0) {
7384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                throw new RemoteException();
7394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
740fd64bd37caf49cd3e2acacee12c20bedd807e362Marc Blank            String domain = mUserName.substring(amp + 1);
7414d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
7424d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // There are up to four attempts here; the two URLs that we're supposed to try per the
7434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // specification, and up to one redirect for each (handled in postAutodiscover)
74483ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler            // Note: The expectation is that, of these four attempts, only a single server will
74583ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler            // actually be identified as the autodiscover server.  For the identified server,
74683ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler            // we may also try a 2nd connection with a different format (bare name).
7474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
7484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            // Try the domain first and see if we can get a response
7494d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpPost post = new HttpPost("https://" + domain + AUTO_DISCOVER_PAGE);
75020da011530088036d2bf45d3836d6986a4b5d423Marc Blank            setHeaders(post, false);
7514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            post.setHeader("Content-Type", "text/xml");
7524d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            post.setEntity(new StringEntity(req));
7534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            HttpClient client = getHttpClient(COMMAND_TIMEOUT);
754bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            EasResponse resp;
7554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            try {
75683ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                resp = postAutodiscover(client, post, true /*canRetry*/);
7574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } catch (IOException e1) {
758adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                userLog("IOException in autodiscover; trying alternate address");
7594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // We catch the IOException here because we have an alternate address to try
7604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                post.setURI(URI.create("https://autodiscover." + domain + AUTO_DISCOVER_PAGE));
7614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // If we fail here, we're out of options, so we let the outer try catch the
7624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // IOException and return null
76383ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                resp = postAutodiscover(client, post, true /*canRetry*/);
7644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
7654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
7664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            try {
76754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                // Get the "final" code; if it's not 200, just return null
768bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                int code = resp.getStatus();
76954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                userLog("Code: " + code);
77054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                if (code != HttpStatus.SC_OK) return null;
77154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank
772bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                InputStream is = resp.getInputStream();
7734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // The response to Autodiscover is regular XML (not WBXML)
7744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // If we ever get an error in this process, we'll just punt and return null
7754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
7764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                XmlPullParser parser = factory.newPullParser();
7774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                parser.setInput(is, "UTF-8");
7784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                int type = parser.getEventType();
7794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (type == XmlPullParser.START_DOCUMENT) {
7804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    type = parser.next();
7814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    if (type == XmlPullParser.START_TAG) {
7824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        String name = parser.getName();
7834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        if (name.equals("Autodiscover")) {
7844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            hostAuth = new HostAuth();
7854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            parseAutodiscover(parser, hostAuth);
7864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            // On success, we'll have a server address and login
78783ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                            if (hostAuth.mAddress != null) {
7884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                // Fill in the rest of the HostAuth
78983ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                                // We use the user name and password that were successful during
79083ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                                // the autodiscover process
79183ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                                hostAuth.mLogin = mUserName;
79283ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                                hostAuth.mPassword = mPassword;
793e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo                                // Note: there is no way we can auto-discover the proper client
794e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo                                // SSL certificate to use, if one is needed.
7954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mPort = 443;
7962bf91acb8174aab82582ce975a4b60702c810a9cMarc Blank                                hostAuth.mProtocol = Eas.PROTOCOL;
7974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                hostAuth.mFlags =
7984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                    HostAuth.FLAG_SSL | HostAuth.FLAG_AUTHENTICATE;
7994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                bundle.putParcelable(
8004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                        EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH, hostAuth);
8014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            } else {
8024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
8034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                                        MessagingException.UNSPECIFIED_EXCEPTION);
8044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                            }
8054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        }
8064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    }
8074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
8084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } catch (XmlPullParserException e1) {
8094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // This would indicate an I/O error of some sort
8104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                // We will simply return null and user can configure manually
81154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            } finally {
812bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank               resp.close();
8134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
8144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        // There's no reason at all for exceptions to be thrown, and it's ok if so.
8154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        // We just won't do auto-discover; user can configure manually
8164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank       } catch (IllegalArgumentException e) {
8174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank             bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
8184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                     MessagingException.UNSPECIFIED_EXCEPTION);
8194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank       } catch (IllegalStateException e) {
8204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
8214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    MessagingException.UNSPECIFIED_EXCEPTION);
8224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank       } catch (IOException e) {
8234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            userLog("IOException in Autodiscover", e);
8244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
8254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    MessagingException.IOERROR);
8264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        } catch (MessagingException e) {
8274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
828df1b1f66c8c0708e99221eff0bba602859bc8a58Marc Blank                    MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED);
8294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
8304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        return bundle;
8314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
8324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
8334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseServer(XmlPullParser parser, HostAuth hostAuth)
8344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
8354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        boolean mobileSync = false;
8364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
8374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
8384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Server")) {
8394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
8404d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
8414d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
8424d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("Type")) {
8434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    if (parser.nextText().equals("MobileSync")) {
8444d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                        mobileSync = true;
8454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    }
8464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (mobileSync && name.equals("Url")) {
847e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    String hostAddress =
848e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        autodiscoverUrlToHostAddress(parser.nextText());
849e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    if (hostAddress != null) {
850e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        hostAuth.mAddress = hostAddress;
851e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        userLog("Autodiscover, server: " + hostAddress);
8524d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    }
8534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
8544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
8554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
8564d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
8574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
8584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseSettings(XmlPullParser parser, HostAuth hostAuth)
8594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
8604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
8614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
8624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Settings")) {
8634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
8644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
8654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
8664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("Server")) {
8674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseServer(parser, hostAuth);
8684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
8694d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
8704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
8714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
8724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
8734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseAction(XmlPullParser parser, HostAuth hostAuth)
8744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
8754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
8764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
8774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Action")) {
8784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
8794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
8804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
8814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("Error")) {
8824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    // Should parse the error
8834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("Redirect")) {
884942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy                    LogUtils.d(TAG, "Redirect: " + parser.nextText());
8854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("Settings")) {
8864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseSettings(parser, hostAuth);
8874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
8884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
8894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
8904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
8914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
8924d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseUser(XmlPullParser parser, HostAuth hostAuth)
8934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
8944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
8954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
8964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("User")) {
8974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
8984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
8994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
9004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("EMailAddress")) {
9014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    String addr = parser.nextText();
90283ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler                    userLog("Autodiscover, email: " + addr);
9034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("DisplayName")) {
9044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    String dn = parser.nextText();
9054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    userLog("Autodiscover, user: " + dn);
9064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
9074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
9084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
9094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
9104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
9114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseResponse(XmlPullParser parser, HostAuth hostAuth)
9124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
9134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
9144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.next();
9154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Response")) {
9164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
9174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG) {
9184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                String name = parser.getName();
9194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                if (name.equals("User")) {
9204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseUser(parser, hostAuth);
9214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                } else if (name.equals("Action")) {
9224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                    parseAction(parser, hostAuth);
9234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                }
9244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
9254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
9264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
9274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
9284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    void parseAutodiscover(XmlPullParser parser, HostAuth hostAuth)
9294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            throws XmlPullParserException, IOException {
9304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        while (true) {
9314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            int type = parser.nextTag();
9324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            if (type == XmlPullParser.END_TAG && parser.getName().equals("Autodiscover")) {
9334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                break;
9344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            } else if (type == XmlPullParser.START_TAG && parser.getName().equals("Response")) {
9354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank                parseResponse(parser, hostAuth);
9364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank            }
9374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank        }
9384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank    }
9394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank
94096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    /**
94196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * Contact the GAL and obtain a list of matching accounts
94296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @param context caller's context
94396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @param accountId the account Id to search
94496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * @param filter the characters entered so far
9455aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank     * @return a result record or null for no data
94696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     *
94796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * TODO: shorter timeout for interactive lookup
94896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * TODO: make watchdog actually work (it doesn't understand our service w/Mailbox == 0)
94996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     * TODO: figure out why sendHttpClientPost() hangs - possibly pool exhaustion
95096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler     */
951469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank    static public GalResult searchGal(Context context, long accountId, String filter, int limit) {
95226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        Account acct = Account.restoreAccountWithId(context, accountId);
95396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler        if (acct != null) {
95426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            EasSyncService svc = setupServiceForAccount(context, acct);
95526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            if (svc == null) return null;
95696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            try {
95796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                Serializer s = new Serializer();
95896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE);
95996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.data(Tags.SEARCH_NAME, "GAL").data(Tags.SEARCH_QUERY, filter);
96096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.start(Tags.SEARCH_OPTIONS);
961469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                s.data(Tags.SEARCH_RANGE, "0-" + Integer.toString(limit - 1));
96296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                s.end().end().end().done();
963bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                EasResponse resp = svc.sendHttpClientPost("Search", s.toByteArray());
96454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                try {
965bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    int code = resp.getStatus();
96654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    if (code == HttpStatus.SC_OK) {
967bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        InputStream is = resp.getInputStream();
96854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        try {
96954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            GalParser gp = new GalParser(is, svc);
97054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            if (gp.parse()) {
97154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                                return gp.getGalResult();
97254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            }
97354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        } finally {
97454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            is.close();
97554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        }
976a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                    } else {
97754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        svc.userLog("GAL lookup returned " + code);
97854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    }
97954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                } finally {
980bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    resp.close();
98196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                }
98296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            } catch (IOException e) {
98396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler                // GAL is non-critical; we'll just go on
984a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler                svc.userLog("GAL lookup exception " + e);
98596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler            }
98696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler        }
98796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler        return null;
98896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler    }
9897531be7774769c84b499b1de5dc46da3a9468316Marc Blank    /**
9905c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     * Send an email responding to a Message that has been marked as a meeting request.  The message
9915c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     * will consist a little bit of event information and an iCalendar attachment
9925c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     * @param msg the meeting request email
9935c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank     */
994346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank    private void sendMeetingResponseMail(Message msg, int response) {
9955c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Get the meeting information; we'd better have some...
99677186bb1a174432ef272584374942d8b9228e39cMarc Blank        if (msg.mMeetingInfo == null) return;
9975c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        PackedString meetingInfo = new PackedString(msg.mMeetingInfo);
9985c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
9995c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // This will come as "First Last" <box@server.blah>, so we use Address to
10005c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // parse it into parts; we only need the email address part for the ics file
10015c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        Address[] addrs = Address.parse(meetingInfo.get(MeetingInfo.MEETING_ORGANIZER_EMAIL));
10025c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // It shouldn't be possible, but handle it anyway
10035c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        if (addrs.length != 1) return;
10045c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String organizerEmail = addrs[0].getAddress();
10055c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
10065c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String dtStamp = meetingInfo.get(MeetingInfo.MEETING_DTSTAMP);
10075c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String dtStart = meetingInfo.get(MeetingInfo.MEETING_DTSTART);
10085c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        String dtEnd = meetingInfo.get(MeetingInfo.MEETING_DTEND);
10095c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
10105c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // What we're doing here is to create an Entity that looks like an Event as it would be
10115c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // stored by CalendarProvider
10125c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        ContentValues entityValues = new ContentValues();
10135c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        Entity entity = new Entity(entityValues);
10145c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
10155c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Fill in times, location, title, and organizer
10165c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put("DTSTAMP",
10175c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                CalendarUtilities.convertEmailDateTimeToCalendarDateTime(dtStamp));
10188e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda        entityValues.put(Events.DTSTART, Utility.parseEmailDateTimeToMillis(dtStart));
10198e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda        entityValues.put(Events.DTEND, Utility.parseEmailDateTimeToMillis(dtEnd));
10205c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put(Events.EVENT_LOCATION, meetingInfo.get(MeetingInfo.MEETING_LOCATION));
10215c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put(Events.TITLE, meetingInfo.get(MeetingInfo.MEETING_TITLE));
10225c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entityValues.put(Events.ORGANIZER, organizerEmail);
10235c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
10245c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Add ourselves as an attendee, using our account email address
10255c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        ContentValues attendeeValues = new ContentValues();
10265c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
10275c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                Attendees.RELATIONSHIP_ATTENDEE);
10285c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        attendeeValues.put(Attendees.ATTENDEE_EMAIL, mAccount.mEmailAddress);
10295c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
10305c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
10315c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Add the organizer
10325c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        ContentValues organizerValues = new ContentValues();
10335c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        organizerValues.put(Attendees.ATTENDEE_RELATIONSHIP,
10345c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                Attendees.RELATIONSHIP_ORGANIZER);
10355c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        organizerValues.put(Attendees.ATTENDEE_EMAIL, organizerEmail);
10365c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        entity.addSubValue(Attendees.CONTENT_URI, organizerValues);
10375c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
10385c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Create a message from the Entity we've built.  The message will have fields like
10395c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // to, subject, date, and text filled in.  There will also be an "inline" attachment
10405c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // which is in iCalendar format
1041346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank        int flag;
1042346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank        switch(response) {
1043346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            case EmailServiceConstants.MEETING_REQUEST_ACCEPTED:
1044346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                flag = Message.FLAG_OUTGOING_MEETING_ACCEPT;
1045346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                break;
1046346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            case EmailServiceConstants.MEETING_REQUEST_DECLINED:
1047346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                flag = Message.FLAG_OUTGOING_MEETING_DECLINE;
1048346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                break;
1049346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            case EmailServiceConstants.MEETING_REQUEST_TENTATIVE:
1050346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            default:
1051346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                flag = Message.FLAG_OUTGOING_MEETING_TENTATIVE;
1052346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank                break;
1053346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank        }
10545c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        Message outgoingMsg =
1055346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank            CalendarUtilities.createMessageForEntity(mContext, entity, flag,
10565c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    meetingInfo.get(MeetingInfo.MEETING_UID), mAccount);
10575c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        // Assuming we got a message back (we might not if the event has been deleted), send it
10585c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        if (outgoingMsg != null) {
10595c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            EasOutboxService.sendMessage(mContext, mAccount.mId, outgoingMsg);
10605c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        }
10615c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank    }
10625c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank
10635c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank    /**
10644471a6960d352242cc65bddf7888cc5335840c74Marc Blank     * Responds to a move request.  The MessageMoveRequest is basically our
10654471a6960d352242cc65bddf7888cc5335840c74Marc Blank     * wrapper for the MoveItems service call
10664471a6960d352242cc65bddf7888cc5335840c74Marc Blank     * @param req the request (message id and "to" mailbox id)
10674471a6960d352242cc65bddf7888cc5335840c74Marc Blank     * @throws IOException
10684471a6960d352242cc65bddf7888cc5335840c74Marc Blank     */
10694471a6960d352242cc65bddf7888cc5335840c74Marc Blank    protected void messageMoveRequest(MessageMoveRequest req) throws IOException {
10704471a6960d352242cc65bddf7888cc5335840c74Marc Blank        // Retrieve the message and mailbox; punt if either are null
10714471a6960d352242cc65bddf7888cc5335840c74Marc Blank        Message msg = Message.restoreMessageWithId(mContext, req.mMessageId);
10724471a6960d352242cc65bddf7888cc5335840c74Marc Blank        if (msg == null) return;
10734471a6960d352242cc65bddf7888cc5335840c74Marc Blank        Cursor c = mContentResolver.query(ContentUris.withAppendedId(Message.UPDATED_CONTENT_URI,
10744471a6960d352242cc65bddf7888cc5335840c74Marc Blank                msg.mId), new String[] {MessageColumns.MAILBOX_KEY}, null, null, null);
1075db2f99c98a6eea4cf33daaba3c349b8c263c3e42Marc Blank        if (c == null) throw new ProviderUnavailableException();
10764471a6960d352242cc65bddf7888cc5335840c74Marc Blank        Mailbox srcMailbox = null;
10774471a6960d352242cc65bddf7888cc5335840c74Marc Blank        try {
10784471a6960d352242cc65bddf7888cc5335840c74Marc Blank            if (!c.moveToNext()) return;
10794471a6960d352242cc65bddf7888cc5335840c74Marc Blank            srcMailbox = Mailbox.restoreMailboxWithId(mContext, c.getLong(0));
10804471a6960d352242cc65bddf7888cc5335840c74Marc Blank        } finally {
10814471a6960d352242cc65bddf7888cc5335840c74Marc Blank            c.close();
10824471a6960d352242cc65bddf7888cc5335840c74Marc Blank        }
10834471a6960d352242cc65bddf7888cc5335840c74Marc Blank        if (srcMailbox == null) return;
10844471a6960d352242cc65bddf7888cc5335840c74Marc Blank        Mailbox dstMailbox = Mailbox.restoreMailboxWithId(mContext, req.mMailboxId);
10854471a6960d352242cc65bddf7888cc5335840c74Marc Blank        if (dstMailbox == null) return;
10864471a6960d352242cc65bddf7888cc5335840c74Marc Blank        Serializer s = new Serializer();
10874471a6960d352242cc65bddf7888cc5335840c74Marc Blank        s.start(Tags.MOVE_MOVE_ITEMS).start(Tags.MOVE_MOVE);
10884471a6960d352242cc65bddf7888cc5335840c74Marc Blank        s.data(Tags.MOVE_SRCMSGID, msg.mServerId);
10894471a6960d352242cc65bddf7888cc5335840c74Marc Blank        s.data(Tags.MOVE_SRCFLDID, srcMailbox.mServerId);
10904471a6960d352242cc65bddf7888cc5335840c74Marc Blank        s.data(Tags.MOVE_DSTFLDID, dstMailbox.mServerId);
10914471a6960d352242cc65bddf7888cc5335840c74Marc Blank        s.end().end().done();
1092bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank        EasResponse resp = sendHttpClientPost("MoveItems", s.toByteArray());
109354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank        try {
1094bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            int status = resp.getStatus();
109554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            if (status == HttpStatus.SC_OK) {
1096bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                if (!resp.isEmpty()) {
1097bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    InputStream is = resp.getInputStream();
10987d35f61afc910b8f21d3225762c251df10dc76eaYu Ping Hu                    MoveItemsParser p = new MoveItemsParser(is);
1099bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    p.parse();
1100bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    int statusCode = p.getStatusCode();
1101bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    ContentValues cv = new ContentValues();
1102bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    if (statusCode == MoveItemsParser.STATUS_CODE_REVERT) {
1103bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        // Restore the old mailbox id
1104bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        cv.put(MessageColumns.MAILBOX_KEY, srcMailbox.mServerId);
1105bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        mContentResolver.update(
1106bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                ContentUris.withAppendedId(Message.CONTENT_URI, req.mMessageId),
1107bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                cv, null, null);
1108bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    } else if (statusCode == MoveItemsParser.STATUS_CODE_SUCCESS) {
1109bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        // Update with the new server id
1110bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        cv.put(SyncColumns.SERVER_ID, p.getNewServerId());
1111bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        cv.put(Message.FLAGS, msg.mFlags | MESSAGE_FLAG_MOVED_MESSAGE);
1112bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        mContentResolver.update(
1113bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                ContentUris.withAppendedId(Message.CONTENT_URI, req.mMessageId),
1114bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                cv, null, null);
1115bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    }
1116bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    if (statusCode == MoveItemsParser.STATUS_CODE_SUCCESS
1117bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                            || statusCode == MoveItemsParser.STATUS_CODE_REVERT) {
1118bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        // If we revert or succeed, we no longer need the update information
1119bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        // OR the now-duplicate email (the new copy will be synced down)
1120bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        mContentResolver.delete(ContentUris.withAppendedId(
1121bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                Message.UPDATED_CONTENT_URI, req.mMessageId), null, null);
1122bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    } else {
1123bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        // In this case, we're retrying, so do nothing.  The request will be
1124bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        // handled next sync
112554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    }
1126bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                }
1127ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu            } else if (resp.isAuthError()) {
112854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                throw new EasAuthenticationException();
112954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            } else {
113054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                userLog("Move items request failed, code: " + status);
113154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                throw new IOException();
113254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            }
113354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank        } finally {
1134bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            resp.close();
11354471a6960d352242cc65bddf7888cc5335840c74Marc Blank        }
11364471a6960d352242cc65bddf7888cc5335840c74Marc Blank    }
11374471a6960d352242cc65bddf7888cc5335840c74Marc Blank
11384471a6960d352242cc65bddf7888cc5335840c74Marc Blank    /**
11397531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * Responds to a meeting request.  The MeetingResponseRequest is basically our
11407531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * wrapper for the meetingResponse service call
11417531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * @param req the request (message id and response code)
11427531be7774769c84b499b1de5dc46da3a9468316Marc Blank     * @throws IOException
11437531be7774769c84b499b1de5dc46da3a9468316Marc Blank     */
11447531be7774769c84b499b1de5dc46da3a9468316Marc Blank    protected void sendMeetingResponse(MeetingResponseRequest req) throws IOException {
11454f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        // Retrieve the message and mailbox; punt if either are null
11467531be7774769c84b499b1de5dc46da3a9468316Marc Blank        Message msg = Message.restoreMessageWithId(mContext, req.mMessageId);
11474f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        if (msg == null) return;
11484f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, msg.mMailboxKey);
11494f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        if (mailbox == null) return;
11507531be7774769c84b499b1de5dc46da3a9468316Marc Blank        Serializer s = new Serializer();
11517531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.start(Tags.MREQ_MEETING_RESPONSE).start(Tags.MREQ_REQUEST);
11527531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.data(Tags.MREQ_USER_RESPONSE, Integer.toString(req.mResponse));
11534f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank        s.data(Tags.MREQ_COLLECTION_ID, mailbox.mServerId);
11547531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.data(Tags.MREQ_REQ_ID, msg.mServerId);
11557531be7774769c84b499b1de5dc46da3a9468316Marc Blank        s.end().end().done();
1156bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank        EasResponse resp = sendHttpClientPost("MeetingResponse", s.toByteArray());
115754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank        try {
1158bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            int status = resp.getStatus();
115954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            if (status == HttpStatus.SC_OK) {
1160bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                if (!resp.isEmpty()) {
1161bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    InputStream is = resp.getInputStream();
116262c287af3bed45818accf595c56879ad5c57aaf9Yu Ping Hu                    new MeetingResponseParser(is).parse();
116354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    String meetingInfo = msg.mMeetingInfo;
116454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    if (meetingInfo != null) {
116554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        String responseRequested = new PackedString(meetingInfo).get(
116654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                                MeetingInfo.MEETING_RESPONSE_REQUESTED);
116754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        // If there's no tag, or a non-zero tag, we send the response mail
116854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        if ("0".equals(responseRequested)) {
116954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            return;
117054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        }
117139fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                    }
117254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    sendMeetingResponseMail(msg, req.mResponse);
117339fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                }
1174ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu            } else if (resp.isAuthError()) {
117554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                throw new EasAuthenticationException();
117654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            } else {
117754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                userLog("Meeting response request failed, code: " + status);
117854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                throw new IOException();
11797531be7774769c84b499b1de5dc46da3a9468316Marc Blank            }
118054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank        } finally {
1181bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            resp.close();
118254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank       }
11837531be7774769c84b499b1de5dc46da3a9468316Marc Blank    }
11847531be7774769c84b499b1de5dc46da3a9468316Marc Blank
118583ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler    /**
11866dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank     * Using mUserName and mPassword, lazily create the strings that are commonly used in our HTTP
11876dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank     * POSTs, including the authentication header string, the base URI we use to communicate with
11886dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank     * EAS, and the user information string (user, deviceId, and deviceType)
118983ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler     */
11906dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank    private void cacheAuthUserAndBaseUriStrings() {
11916dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        if (mAuthString == null || mUserString == null || mBaseUriString == null) {
11926dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank            String safeUserName = Uri.encode(mUserName);
11936dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank            String cs = mUserName + ':' + mPassword;
11946dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank            mAuthString = "Basic " + Base64.encodeToString(cs.getBytes(), Base64.NO_WRAP);
11956dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank            mUserString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId +
11966dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank                "&DeviceType=" + DEVICE_TYPE;
11976dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank            String scheme =
11986dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank                EmailClientConnectionManager.makeScheme(mSsl, mTrustSsl, mClientCertAlias);
11996dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank            mBaseUriString = scheme + "://" + mHostAddress + "/Microsoft-Server-ActiveSync";
12006dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        }
120183ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler    }
120283ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler
1203e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo    @VisibleForTesting
1204e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo    String makeUriString(String cmd, String extra) {
12056dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        cacheAuthUserAndBaseUriStrings();
12066dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        String uriString = mBaseUriString;
1207ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (cmd != null) {
12086dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank            uriString += "?Cmd=" + cmd + mUserString;
1209ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1210ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (extra != null) {
1211e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo            uriString += extra;
1212ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1213e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo        return uriString;
1214ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1215ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
121620da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
121720da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Set standard HTTP headers, using a policy key if required
121820da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param method the method we are going to send
121920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param usePolicyKey whether or not a policy key should be sent in the headers
122020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
12217dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank    /*package*/ void setHeaders(HttpRequestBase method, boolean usePolicyKey) {
12228047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("Authorization", mAuthString);
12238047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        method.setHeader("MS-ASProtocolVersion", mProtocolVersion);
12240209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank        method.setHeader("User-Agent", USER_AGENT);
1225bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank        method.setHeader("Accept-Encoding", "gzip");
12267dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank        if (usePolicyKey) {
12277dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank            // If there's an account in existence, use its key; otherwise (we're creating the
12287dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank            // account), send "0".  The server will respond with code 449 if there are policies
12297dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank            // to be enforced
12307dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank            String key = "0";
12317dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank            if (mAccount != null) {
12327dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank                String accountKey = mAccount.mSecuritySyncKey;
12337dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank                if (!TextUtils.isEmpty(accountKey)) {
12347dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank                    key = accountKey;
12357dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank                }
123695fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank            }
123720da011530088036d2bf45d3836d6986a4b5d423Marc Blank            method.setHeader("X-MS-PolicyKey", key);
123820da011530088036d2bf45d3836d6986a4b5d423Marc Blank        }
12398047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
1240ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1241e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    protected void setConnectionParameters(HostAuth hostAuth) throws CertificateException {
12423fedf8da5e2bd8c5b5a128ffe27e6bb42ebc611dMarc Blank        mHostAuth = hostAuth;
1243e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        mSsl = hostAuth.shouldUseSsl();
1244e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        mTrustSsl = hostAuth.shouldTrustAllServerCerts();
1245e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        mClientCertAlias = hostAuth.mClientCertAlias;
1246e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo
1247e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo        // Register the new alias, if needed.
1248e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo        if (mClientCertAlias != null) {
1249e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo            // Ensure that the connection manager knows to use the proper client certificate
1250e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo            // when establishing connections for this service.
1251e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            EmailClientConnectionManager connManager = getClientConnectionManager();
1252e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            connManager.registerClientCert(mContext, hostAuth);
1253e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo        }
1254e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo    }
1255e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo
1256e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo    private EmailClientConnectionManager getClientConnectionManager() {
12573fedf8da5e2bd8c5b5a128ffe27e6bb42ebc611dMarc Blank        return ExchangeService.getClientConnectionManager(mContext, mHostAuth);
1258e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank    }
1259e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank
12608047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    private HttpClient getHttpClient(int timeout) {
12618047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpParams params = new BasicHttpParams();
126216b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank        HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
12638047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpConnectionParams.setSoTimeout(params, timeout);
1264e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank        HttpConnectionParams.setSocketBufferSize(params, 8192);
12656ac502883abcecc4c80b26ed2b1a2a1e7308505cAlon Albert        return new DefaultHttpClient(getClientConnectionManager(), params) {
12666ac502883abcecc4c80b26ed2b1a2a1e7308505cAlon Albert            protected BasicHttpProcessor createHttpProcessor() {
12676ac502883abcecc4c80b26ed2b1a2a1e7308505cAlon Albert                final BasicHttpProcessor processor = super.createHttpProcessor();
12686ac502883abcecc4c80b26ed2b1a2a1e7308505cAlon Albert                processor.addRequestInterceptor(new CurlLogger());
12696ac502883abcecc4c80b26ed2b1a2a1e7308505cAlon Albert                return processor;
12706ac502883abcecc4c80b26ed2b1a2a1e7308505cAlon Albert            }
12716ac502883abcecc4c80b26ed2b1a2a1e7308505cAlon Albert        };
12728047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
1273ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1274bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blank    public EasResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException {
12751b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        return sendHttpClientPost(cmd, new ByteArrayEntity(bytes), COMMAND_TIMEOUT);
12769e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    }
12779e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank
1278adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    /**
1279adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * Convenience method for executePostWithTimeout for use other than with the Ping command
1280adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     */
1281bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank    protected EasResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout)
1282adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            throws IOException {
1283adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        return executePostWithTimeout(client, method, timeout, false);
1284adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    }
1285adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank
1286adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    /**
1287adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * Handle executing an HTTP POST command with proper timeout, watchdog, and ping behavior
1288adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param client the HttpClient
1289adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param method the HttpPost
1290adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param timeout the timeout before failure, in ms
1291adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @param isPingCommand whether the POST is for the Ping command (requires wakelock logic)
1292adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @return the HttpResponse
1293adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     * @throws IOException
1294adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank     */
1295c716a538cffe7f5cea0d55683c696bc25e3568c5Yu Ping Hu    protected EasResponse executePostWithTimeout(HttpClient client, HttpPost method,
1296c716a538cffe7f5cea0d55683c696bc25e3568c5Yu Ping Hu            final int timeout, final boolean isPingCommand) throws IOException {
1297c716a538cffe7f5cea0d55683c696bc25e3568c5Yu Ping Hu        final boolean hasWakeLock;
1298adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        synchronized(getSynchronizer()) {
1299c716a538cffe7f5cea0d55683c696bc25e3568c5Yu Ping Hu            hasWakeLock = ExchangeService.isHoldingWakeLock(mMailboxId);
1300c716a538cffe7f5cea0d55683c696bc25e3568c5Yu Ping Hu            if (isPingCommand && !hasWakeLock) {
1301942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy                LogUtils.e(TAG, "executePostWithTimeout (ping) without holding wakelock");
1302c716a538cffe7f5cea0d55683c696bc25e3568c5Yu Ping Hu            }
1303adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            mPendingPost = method;
130416b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            long alarmTime = timeout + WATCHDOG_TIMEOUT_ALLOWANCE;
1305c716a538cffe7f5cea0d55683c696bc25e3568c5Yu Ping Hu            if (isPingCommand && hasWakeLock) {
1306385a0be662509754e687bcfa9813208b050bf951Marc Blank                ExchangeService.runAsleep(mMailboxId, alarmTime);
1307adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            } else {
1308385a0be662509754e687bcfa9813208b050bf951Marc Blank                ExchangeService.setWatchdogAlarm(mMailboxId, alarmTime);
1309adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            }
1310adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        }
1311adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        try {
13123d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo            return EasResponse.fromHttpRequest(getClientConnectionManager(), client, method);
1313adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        } finally {
1314adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            synchronized(getSynchronizer()) {
1315c716a538cffe7f5cea0d55683c696bc25e3568c5Yu Ping Hu                if (isPingCommand && hasWakeLock) {
1316385a0be662509754e687bcfa9813208b050bf951Marc Blank                    ExchangeService.runAwake(mMailboxId);
1317adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                } else {
1318385a0be662509754e687bcfa9813208b050bf951Marc Blank                    ExchangeService.clearWatchdogAlarm(mMailboxId);
1319adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                }
1320adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank                mPendingPost = null;
1321adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank            }
1322adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        }
1323adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank    }
1324adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank
13250565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    public EasResponse sendHttpClientPost(String cmd, HttpEntity entity, int timeout)
13261b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            throws IOException {
13271b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        HttpClient client = getHttpClient(timeout);
1328c4bc56c4057d2d7596b75c60ee792676251804f5Marc Blank        boolean isPingCommand = cmd.equals(PING_COMMAND);
132985f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
133085f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        // Split the mail sending commands
133185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String extra = null;
133285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        boolean msg = false;
133385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        if (cmd.startsWith("SmartForward&") || cmd.startsWith("SmartReply&")) {
13345843b85178a359446f81770ed7734604a1b2fa7dMarc Blank            int cmdLength = cmd.indexOf('&');
133585f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            extra = cmd.substring(cmdLength);
133685f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            cmd = cmd.substring(0, cmdLength);
133785f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
133885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        } else if (cmd.startsWith("SendMail&")) {
133985f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            msg = true;
134085f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        }
134185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
134285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        String us = makeUriString(cmd, extra);
13438047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank        HttpPost method = new HttpPost(URI.create(us));
1344c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        // Send the proper Content-Type header; it's always wbxml except for messages when
1345c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        // the EAS protocol version is < 14.0
134642f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        // If entity is null (e.g. for attachments), don't set this header
1347c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        if (msg && (mProtocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE)) {
13488047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "message/rfc822");
134942f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank        } else if (entity != null) {
13508047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml");
1351ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1352b7e8db5257203ce221885cbdff256092a073e1daMarc Blank        setHeaders(method, !isPingCommand);
1353b7e8db5257203ce221885cbdff256092a073e1daMarc Blank        // NOTE
1354b7e8db5257203ce221885cbdff256092a073e1daMarc Blank        // The next lines are added at the insistence of $VENDOR, who is seeing inappropriate
1355b7e8db5257203ce221885cbdff256092a073e1daMarc Blank        // network activity related to the Ping command on some networks with some servers.
1356b7e8db5257203ce221885cbdff256092a073e1daMarc Blank        // This code should be removed when the underlying issue is resolved
1357b7e8db5257203ce221885cbdff256092a073e1daMarc Blank        if (isPingCommand) {
1358b7e8db5257203ce221885cbdff256092a073e1daMarc Blank            method.setHeader("Connection", "close");
1359b7e8db5257203ce221885cbdff256092a073e1daMarc Blank        }
13609e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        method.setEntity(entity);
1361adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank        return executePostWithTimeout(client, method, timeout, isPingCommand);
13628047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    }
13638047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
1364bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank    protected EasResponse sendHttpClientOptions() throws IOException {
13656dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        cacheAuthUserAndBaseUriStrings();
13666dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        // For OPTIONS, just use the base string and the single header
13676dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        String uriString = mBaseUriString;
13686dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        HttpOptions method = new HttpOptions(URI.create(uriString));
13696dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        method.setHeader("Authorization", mAuthString);
13706dd267921d03b252b1d74ba6e75516263dfdf080Marc Blank        method.setHeader("User-Agent", USER_AGENT);
1371e9353616056991511d4c3a707e97ca0468c0ef42Marc Blank        HttpClient client = getHttpClient(COMMAND_TIMEOUT);
13723d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo        return EasResponse.fromHttpRequest(getClientConnectionManager(), client, method);
1373ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1374ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1375e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    String getTargetCollectionClassFromCursor(Cursor c) {
1376ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
1377ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (type == Mailbox.TYPE_CONTACTS) {
1378ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Contacts";
1379ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else if (type == Mailbox.TYPE_CALENDAR) {
1380ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Calendar";
1381ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } else {
1382ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return "Email";
1383ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1384ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1385ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
138620da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
138720da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Negotiate provisioning with the server.  First, get policies form the server and see if
138820da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * the policies are supported by the device.  Then, write the policies to the account and
138920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * tell SecurityPolicy that we have policies in effect.  Finally, see if those policies are
139020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * active; if so, acknowledge the policies to the server and get a final policy key that we
139120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * use in future EAS commands and write this key to the account.
139220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @return whether or not provisioning has been successful
139320da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @throws IOException
139420da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
1395e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    public static boolean tryProvision(EasSyncService svc) throws IOException {
139620da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // First, see if provisioning is even possible, i.e. do we support the policies required
139720da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // by the server
1398e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        ProvisionParser pp = canProvision(svc);
1399e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        if (pp == null) return false;
1400e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        Context context = svc.mContext;
1401e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        Account account = svc.mAccount;
1402e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        // Get the policies from ProvisionParser
1403e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        Policy policy = pp.getPolicy();
1404e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        Policy oldPolicy = null;
1405e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        // Grab the old policy (if any)
1406e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        if (svc.mAccount.mPolicyKey > 0) {
1407e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            oldPolicy = Policy.restorePolicyWithId(context, account.mPolicyKey);
1408e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        }
1409e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        // Update the account with a null policyKey (the key we've gotten is
1410e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        // temporary and cannot be used for syncing)
1411e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        PolicyServiceProxy.setAccountPolicy(context, account.mId, policy, null);
1412e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        // Make sure mAccount is current (with latest policy key)
1413e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        account.refresh(context);
1414e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        if (pp.getRemoteWipe()) {
1415e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // We've gotten a remote wipe command
1416e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            ExchangeService.alwaysLog("!!! Remote wipe request received");
1417e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // Start by setting the account to security hold
1418e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            PolicyServiceProxy.setAccountHoldFlag(context, account, true);
1419e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // Force a stop to any running syncs for this account (except this one)
1420e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            ExchangeService.stopNonAccountMailboxSyncsForAccount(account.mId);
1421e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank
1422e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // First, we've got to acknowledge it, but wrap the wipe in try/catch so that
1423e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // we wipe the device regardless of any errors in acknowledgment
1424e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            try {
1425e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                ExchangeService.alwaysLog("!!! Acknowledging remote wipe to server");
1426e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                acknowledgeRemoteWipe(svc, pp.getSecuritySyncKey());
1427e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            } catch (Exception e) {
1428e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                // Because remote wipe is such a high priority task, we don't want to
1429e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                // circumvent it if there's an exception in acknowledgment
1430c55d6dfd1ade376f080568b4f307f76d4984a761Marc Blank            }
1431e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // Then, tell SecurityPolicy to wipe the device
1432e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            ExchangeService.alwaysLog("!!! Executing remote wipe");
1433e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            PolicyServiceProxy.remoteWipe(context);
1434e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            return false;
1435e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        } else if (pp.hasSupportablePolicySet() && PolicyServiceProxy.isActive(context, policy)) {
1436e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // See if the required policies are in force; if they are, acknowledge the policies
1437e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // to the server and get the final policy key
1438e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // NOTE: For EAS 14.0, we already have the acknowledgment in the ProvisionParser
1439e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            String securitySyncKey;
1440e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            if (svc.mProtocolVersionDouble == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
1441e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                securitySyncKey = pp.getSecuritySyncKey();
1442e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            } else {
1443e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                securitySyncKey = acknowledgeProvision(svc, pp.getSecuritySyncKey(),
1444e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        PROVISION_STATUS_OK);
1445e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            }
1446e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            if (securitySyncKey != null) {
1447e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                // If attachment policies have changed, fix up any affected attachment records
1448e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                if (oldPolicy != null) {
1449e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    if ((oldPolicy.mDontAllowAttachments != policy.mDontAllowAttachments) ||
1450e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                            (oldPolicy.mMaxAttachmentSize != policy.mMaxAttachmentSize)) {
1451e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        Policy.setAttachmentFlagsForNewPolicy(context, account, policy);
1452d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                    }
1453bbc1811956084987767779e31525c5013dab59d8Marc Blank                }
1454e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                // Write the final policy key to the Account and say we've been successful
1455e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                PolicyServiceProxy.setAccountPolicy(context, account.mId, policy, securitySyncKey);
1456e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                // Release any mailboxes that might be in a security hold
1457e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                ExchangeService.releaseSecurityHold(account);
1458e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                return true;
145920da011530088036d2bf45d3836d6986a4b5d423Marc Blank            }
146020da011530088036d2bf45d3836d6986a4b5d423Marc Blank        }
146120da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return false;
146220da011530088036d2bf45d3836d6986a4b5d423Marc Blank    }
146320da011530088036d2bf45d3836d6986a4b5d423Marc Blank
1464e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    private static String getPolicyType(Double protocolVersion) {
1465e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        return (protocolVersion >=
1466d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE;
1467b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank    }
1468b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
146920da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
147020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Obtain a set of policies from the server and determine whether those policies are supported
147120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * by the device.
147232c569e053c3159956e13b2e5b47eb9d167186ceMarc Blank     * @return the ProvisionParser (holds policies and key) if we receive policies; null otherwise
147320da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @throws IOException
147420da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
1475e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    public static ProvisionParser canProvision(EasSyncService svc) throws IOException {
14768692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        Serializer s = new Serializer();
1477e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        Double protocolVersion = svc.mProtocolVersionDouble;
147877186bb1a174432ef272584374942d8b9228e39cMarc Blank        s.start(Tags.PROVISION_PROVISION);
1479e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        if (svc.mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) {
14808b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank            // Send settings information in 14.1 and greater
148177186bb1a174432ef272584374942d8b9228e39cMarc Blank            s.start(Tags.SETTINGS_DEVICE_INFORMATION).start(Tags.SETTINGS_SET);
148277186bb1a174432ef272584374942d8b9228e39cMarc Blank            s.data(Tags.SETTINGS_MODEL, Build.MODEL);
148377186bb1a174432ef272584374942d8b9228e39cMarc Blank            //s.data(Tags.SETTINGS_IMEI, "");
148477186bb1a174432ef272584374942d8b9228e39cMarc Blank            //s.data(Tags.SETTINGS_FRIENDLY_NAME, "Friendly Name");
148577186bb1a174432ef272584374942d8b9228e39cMarc Blank            s.data(Tags.SETTINGS_OS, "Android " + Build.VERSION.RELEASE);
148677186bb1a174432ef272584374942d8b9228e39cMarc Blank            //s.data(Tags.SETTINGS_OS_LANGUAGE, "");
148777186bb1a174432ef272584374942d8b9228e39cMarc Blank            //s.data(Tags.SETTINGS_PHONE_NUMBER, "");
148877186bb1a174432ef272584374942d8b9228e39cMarc Blank            //s.data(Tags.SETTINGS_MOBILE_OPERATOR, "");
1489e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            s.data(Tags.SETTINGS_USER_AGENT, EasSyncService.USER_AGENT);
149077186bb1a174432ef272584374942d8b9228e39cMarc Blank            s.end().end();  // SETTINGS_SET, SETTINGS_DEVICE_INFORMATION
149177186bb1a174432ef272584374942d8b9228e39cMarc Blank        }
149277186bb1a174432ef272584374942d8b9228e39cMarc Blank        s.start(Tags.PROVISION_POLICIES);
1493e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        s.start(Tags.PROVISION_POLICY);
1494e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType(protocolVersion));
1495e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        s.end().end().end().done(); // PROVISION_POLICY, PROVISION_POLICIES, PROVISION_PROVISION
1496e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        EasResponse resp = svc.sendHttpClientPost("Provision", s.toByteArray());
149754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank        try {
1498bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            int code = resp.getStatus();
149954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            if (code == HttpStatus.SC_OK) {
1500bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                InputStream is = resp.getInputStream();
1501bc278f1e81ef1d44c28093e830ebe84faa960843Yu Ping Hu                ProvisionParser pp = new ProvisionParser(svc.mContext, is);
150254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                if (pp.parse()) {
150354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // The PolicySet in the ProvisionParser will have the requirements for all KNOWN
150454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // policies.  If others are required, hasSupportablePolicySet will be false
15058b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                    if (pp.hasSupportablePolicySet() &&
1506e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                            svc.mProtocolVersionDouble == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
15078b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                        // In EAS 14.0, we need the final security key in order to use the settings
15088b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                        // command
1509e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        String policyKey = acknowledgeProvision(svc, pp.getSecuritySyncKey(),
15108b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                                PROVISION_STATUS_OK);
15118b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                        if (policyKey != null) {
15128b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                            pp.setSecuritySyncKey(policyKey);
15138b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                        }
15148b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                    } else if (!pp.hasSupportablePolicySet())  {
151554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        // Try to acknowledge using the "partial" status (i.e. we can partially
151654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        // accommodate the required policies).  The server will agree to this if the
151754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        // "allow non-provisionable devices" setting is enabled on the server
1518afbb79493033d7108fa8dfaa88e0427db84de09fMarc Blank                        ExchangeService.log("PolicySet is NOT fully supportable");
1519e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        if (acknowledgeProvision(svc, pp.getSecuritySyncKey(),
1520e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                                PROVISION_STATUS_PARTIAL) != null) {
1521e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                            // The server's ok with our inability to support policies, so we'll
1522e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                            // clear them
1523e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                            pp.clearUnsupportablePolicies();
1524883a5c77034a3b839127167bc20d05830c8350fbAndy Stadler                        }
152554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    }
152632c569e053c3159956e13b2e5b47eb9d167186ceMarc Blank                    return pp;
15278692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank                }
15288692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank            }
152954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank        } finally {
1530bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            resp.close();
15318692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank        }
15328b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank
153320da011530088036d2bf45d3836d6986a4b5d423Marc Blank        // On failures, simply return null
153420da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return null;
153520da011530088036d2bf45d3836d6986a4b5d423Marc Blank    }
153620da011530088036d2bf45d3836d6986a4b5d423Marc Blank
153720da011530088036d2bf45d3836d6986a4b5d423Marc Blank    /**
153820da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * Acknowledge that we support the policies provided by the server, and that these policies
153920da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * are in force.
154020da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @param tempKey the initial (temporary) policy key sent by the server
154120da011530088036d2bf45d3836d6986a4b5d423Marc Blank     * @throws IOException
154220da011530088036d2bf45d3836d6986a4b5d423Marc Blank     */
1543e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    private static void acknowledgeRemoteWipe(EasSyncService svc, String tempKey)
1544e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            throws IOException {
1545e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        acknowledgeProvisionImpl(svc, tempKey, PROVISION_STATUS_OK, true);
15462a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    }
15472a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank
1548e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    private static String acknowledgeProvision(EasSyncService svc, String tempKey, String result)
1549e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            throws IOException {
1550e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        return acknowledgeProvisionImpl(svc, tempKey, result, false);
15512a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank    }
15522a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank
1553e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    private static String acknowledgeProvisionImpl(EasSyncService svc, String tempKey,
1554e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            String status, boolean remoteWipe) throws IOException {
155520da011530088036d2bf45d3836d6986a4b5d423Marc Blank        Serializer s = new Serializer();
155620da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
155720da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.start(Tags.PROVISION_POLICY);
1558b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
1559b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank        // Use the proper policy type, depending on EAS version
1560e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType(svc.mProtocolVersionDouble));
1561b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank
156220da011530088036d2bf45d3836d6986a4b5d423Marc Blank        s.data(Tags.PROVISION_POLICY_KEY, tempKey);
15637dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank        s.data(Tags.PROVISION_STATUS, status);
15642ca2e1caef16fde39df2c795ac4c9477f9a8c6d9Marc Blank        s.end().end(); // PROVISION_POLICY, PROVISION_POLICIES
15652a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        if (remoteWipe) {
15662a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank            s.start(Tags.PROVISION_REMOTE_WIPE);
15677dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank            s.data(Tags.PROVISION_STATUS, PROVISION_STATUS_OK);
15682a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank            s.end();
15692a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank        }
15702ca2e1caef16fde39df2c795ac4c9477f9a8c6d9Marc Blank        s.end().done(); // PROVISION_PROVISION
1571e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        EasResponse resp = svc.sendHttpClientPost("Provision", s.toByteArray());
157254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank        try {
1573bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            int code = resp.getStatus();
157454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            if (code == HttpStatus.SC_OK) {
1575bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                InputStream is = resp.getInputStream();
1576bc278f1e81ef1d44c28093e830ebe84faa960843Yu Ping Hu                ProvisionParser pp = new ProvisionParser(svc.mContext, is);
157754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                if (pp.parse()) {
157854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // Return the final policy key from the ProvisionParser
1579e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    String result = (pp.getSecuritySyncKey() == null) ? "failed" : "confirmed";
1580e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    ExchangeService.log("Provision " + result + " for " +
1581afbb79493033d7108fa8dfaa88e0427db84de09fMarc Blank                            (PROVISION_STATUS_PARTIAL.equals(status) ? "PART" : "FULL") + " set");
15823ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank                    return pp.getSecuritySyncKey();
158354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                }
158454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            }
158554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank        } finally {
1586bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            resp.close();
158720da011530088036d2bf45d3836d6986a4b5d423Marc Blank        }
1588afbb79493033d7108fa8dfaa88e0427db84de09fMarc Blank        // On failures, log issue and return null
1589e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        ExchangeService.log("Provisioning failed for" +
1590afbb79493033d7108fa8dfaa88e0427db84de09fMarc Blank                (PROVISION_STATUS_PARTIAL.equals(status) ? "PART" : "FULL") + " set");
159120da011530088036d2bf45d3836d6986a4b5d423Marc Blank        return null;
15928692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank    }
15938692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank
15948b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank    private boolean sendSettings() throws IOException {
15958b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        Serializer s = new Serializer();
15968b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        s.start(Tags.SETTINGS_SETTINGS);
15978b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        s.start(Tags.SETTINGS_DEVICE_INFORMATION).start(Tags.SETTINGS_SET);
15988b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        s.data(Tags.SETTINGS_MODEL, Build.MODEL);
15998b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        s.data(Tags.SETTINGS_OS, "Android " + Build.VERSION.RELEASE);
16008b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        s.data(Tags.SETTINGS_USER_AGENT, USER_AGENT);
16018b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        s.end().end().end().done(); // SETTINGS_SET, SETTINGS_DEVICE_INFORMATION, SETTINGS_SETTINGS
16028b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        EasResponse resp = sendHttpClientPost("Settings", s.toByteArray());
16038b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        try {
16048b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank            int code = resp.getStatus();
16058b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank            if (code == HttpStatus.SC_OK) {
16068b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                InputStream is = resp.getInputStream();
1607bc278f1e81ef1d44c28093e830ebe84faa960843Yu Ping Hu                SettingsParser sp = new SettingsParser(is);
16088b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank                return sp.parse();
16098b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank            }
16108b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        } finally {
16118b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank            resp.close();
16128b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        }
16138b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        // On failures, simply return false
16148b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank        return false;
16158b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank    }
16168b4543adb2e1eda9b0c0d3d7d89e03bff9e9fe44Marc Blank
1617ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
1618ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Common code to sync E+PIM data
1619883a5c77034a3b839127167bc20d05830c8350fbAndy Stadler     * @param target an EasMailbox, EasContacts, or EasCalendar object
1620ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
16217c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank    public void sync(AbstractSyncAdapter target) throws IOException {
1622ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        Mailbox mailbox = target.mMailbox;
1623ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1624ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        boolean moreAvailable = true;
16258efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        int loopingCount = 0;
1626b20a744b01fef0082b106f44612a3d0fec361800Marc Blank        while (!mStop && (moreAvailable || hasPendingRequests())) {
1627385a0be662509754e687bcfa9813208b050bf951Marc Blank            // If we have no connectivity, just exit cleanly. ExchangeService will start us up again
16281b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            // when connectivity has returned
16291b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            if (!hasConnectivity()) {
16301b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                userLog("No connectivity in sync; finishing sync");
16311b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                mExitStatus = EXIT_DONE;
16321b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank                return;
1633aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank            }
1634aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank
1635aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank            // Every time through the loop we check to see if we're still syncable
1636aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank            if (!target.isSyncable()) {
1637aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank                mExitStatus = EXIT_DONE;
1638aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank                return;
16391b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank            }
1640ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
16417531be7774769c84b499b1de5dc46da3a9468316Marc Blank            // Now, handle various requests
1642d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            while (true) {
16437531be7774769c84b499b1de5dc46da3a9468316Marc Blank                Request req = null;
1644b20a744b01fef0082b106f44612a3d0fec361800Marc Blank
1645b20a744b01fef0082b106f44612a3d0fec361800Marc Blank                if (mRequestQueue.isEmpty()) {
1646b20a744b01fef0082b106f44612a3d0fec361800Marc Blank                    break;
1647b20a744b01fef0082b106f44612a3d0fec361800Marc Blank                } else {
1648b20a744b01fef0082b106f44612a3d0fec361800Marc Blank                    req = mRequestQueue.peek();
1649d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                }
16507531be7774769c84b499b1de5dc46da3a9468316Marc Blank
16517531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // Our two request types are PartRequest (loading attachment) and
16527531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // MeetingResponseRequest (respond to a meeting request)
16537531be7774769c84b499b1de5dc46da3a9468316Marc Blank                if (req instanceof PartRequest) {
1654c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank                    TrafficStats.setThreadStatsTag(
1655c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank                            TrafficFlags.getAttachmentFlags(mContext, mAccount));
1656bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blank                    new AttachmentLoader(this, (PartRequest)req).loadAttachment();
1657c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank                    TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, mAccount));
16587531be7774769c84b499b1de5dc46da3a9468316Marc Blank                } else if (req instanceof MeetingResponseRequest) {
16597531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    sendMeetingResponse((MeetingResponseRequest)req);
16604471a6960d352242cc65bddf7888cc5335840c74Marc Blank                } else if (req instanceof MessageMoveRequest) {
16614471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    messageMoveRequest((MessageMoveRequest)req);
16627531be7774769c84b499b1de5dc46da3a9468316Marc Blank                }
16637531be7774769c84b499b1de5dc46da3a9468316Marc Blank
16647531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // If there's an exception handling the request, we'll throw it
16657531be7774769c84b499b1de5dc46da3a9468316Marc Blank                // Otherwise, we remove the request
1666b20a744b01fef0082b106f44612a3d0fec361800Marc Blank                mRequestQueue.remove();
1667b20a744b01fef0082b106f44612a3d0fec361800Marc Blank            }
1668b20a744b01fef0082b106f44612a3d0fec361800Marc Blank
1669b20a744b01fef0082b106f44612a3d0fec361800Marc Blank            // Don't sync if we've got nothing to do
1670b20a744b01fef0082b106f44612a3d0fec361800Marc Blank            if (!moreAvailable) {
1671b20a744b01fef0082b106f44612a3d0fec361800Marc Blank                continue;
1672d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            }
1673d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank
16747c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            Serializer s = new Serializer();
1675c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank
1676ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String className = target.getCollectionName();
167748af7392c82262d17700e3fbdccf3a582809d449Marc Blank            String syncKey = target.getSyncKey();
16788d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            userLog("sync, sending ", className, " syncKey: ", syncKey);
16797c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.start(Tags.SYNC_SYNC)
16807c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                .start(Tags.SYNC_COLLECTIONS)
1681e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank                .start(Tags.SYNC_COLLECTION);
1682e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank            // The "Class" element is removed in EAS 12.1 and later versions
1683e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank            if (mProtocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) {
1684e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank                s.data(Tags.SYNC_CLASS, className);
1685e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank            }
1686e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank            s.data(Tags.SYNC_SYNC_KEY, syncKey)
1687e489e48b50bf8a44ddeccd12efa029f8a7b5f85bMarc Blank                .data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId);
1688ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
168916b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            // Start with the default timeout
169016b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank            int timeout = COMMAND_TIMEOUT;
1691e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            boolean initialSync = syncKey.equals("0");
1692e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // EAS doesn't allow GetChanges in an initial sync; sending other options
1693e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // appears to cause the server to delay its response in some cases, and this delay
1694e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // can be long enough to result in an IOException and total failure to sync.
1695e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // Therefore, we don't send any options with the initial sync.
1696e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            // Set the truncation amount, body preference, lookback, etc.
1697e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            target.sendSyncOptions(mProtocolVersionDouble, s, initialSync);
1698e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            if (initialSync) {
1699e489e48b50bf8a44ddeccd12efa029f8a7b5f85bMarc Blank                // Use enormous timeout for initial sync, which empirically can take a while longer
1700847322e4b75287643a8381e28401ee5cf4945a44Yu Ping Hu                timeout = (int)(120 * DateUtils.SECOND_IN_MILLIS);
1701ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1702ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Send our changes up to the server
170361c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank            if (mUpsyncFailed) {
170461c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                if (Eas.USER_LOG) {
1705942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy                    LogUtils.d(TAG, "Inhibiting upsync this cycle");
170661c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                }
170761c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank            } else {
170861c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                target.sendLocalChanges(s);
170961c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank            }
1710ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
17117c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.end().end().end().done();
1712bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            EasResponse resp = sendHttpClientPost("Sync", new ByteArrayEntity(s.toByteArray()),
171316b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank                    timeout);
171454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            try {
1715bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                int code = resp.getStatus();
171654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                if (code == HttpStatus.SC_OK) {
171754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // In EAS 12.1, we can get "empty" sync responses, which indicate that there are
171854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    // no changes in the mailbox; handle that case here
1719bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    // There are two cases here; if we get back a compressed stream (GZIP), we won't
1720bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    // know until we try to parse it (and generate an EmptyStreamException). If we
1721bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    // get uncompressed data, the response will be empty (i.e. have zero length)
1722bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    boolean emptyStream = false;
1723bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    if (!resp.isEmpty()) {
1724bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        InputStream is = resp.getInputStream();
1725bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        try {
1726bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                            moreAvailable = target.parse(is);
172761c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                            // If we inhibited upsync, we need yet another sync
172861c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                            if (mUpsyncFailed) {
172961c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                                moreAvailable = true;
173061c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                            }
173161c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank
1732bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                            if (target.isLooping()) {
1733bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                loopingCount++;
1734bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                userLog("** Looping: " + loopingCount);
1735bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                // After the maximum number of loops, we'll set moreAvailable to
1736bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                // false and allow the sync loop to terminate
1737bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                if (moreAvailable && (loopingCount > MAX_LOOPING_COUNT)) {
1738bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                    userLog("** Looping force stopped");
1739bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                    moreAvailable = false;
1740bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                }
1741bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                            } else {
1742bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                                loopingCount = 0;
1743bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                            }
174461c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank
174561c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                            // Cleanup clears out the updated/deleted tables, and we don't want to
174661c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                            // do that if our upsync failed; clear the flag otherwise
174761c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                            if (!mUpsyncFailed) {
174861c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                                target.cleanup();
174961c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                            } else {
175061c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                                mUpsyncFailed = false;
175161c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank                            }
1752bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        } catch (EmptyStreamException e) {
1753bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                            userLog("Empty stream detected in GZIP response");
1754bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                            emptyStream = true;
175577186bb1a174432ef272584374942d8b9228e39cMarc Blank                        } catch (CommandStatusException e) {
175677186bb1a174432ef272584374942d8b9228e39cMarc Blank                            // TODO 14.1
175777186bb1a174432ef272584374942d8b9228e39cMarc Blank                            int status = e.mStatus;
175877186bb1a174432ef272584374942d8b9228e39cMarc Blank                            if (CommandStatus.isNeedsProvisioning(status)) {
1759c379d9df9eb8a5ee3ce0a308a146e8d2c73ef8e3Marc Blank                                mExitStatus = EXIT_SECURITY_FAILURE;
176077186bb1a174432ef272584374942d8b9228e39cMarc Blank                            } else if (CommandStatus.isDeniedAccess(status)) {
176177186bb1a174432ef272584374942d8b9228e39cMarc Blank                                mExitStatus = EXIT_ACCESS_DENIED;
176277186bb1a174432ef272584374942d8b9228e39cMarc Blank                            } else if (CommandStatus.isTransientError(status)) {
176377186bb1a174432ef272584374942d8b9228e39cMarc Blank                                mExitStatus = EXIT_IO_ERROR;
176477186bb1a174432ef272584374942d8b9228e39cMarc Blank                            } else {
176577186bb1a174432ef272584374942d8b9228e39cMarc Blank                                mExitStatus = EXIT_EXCEPTION;
176677186bb1a174432ef272584374942d8b9228e39cMarc Blank                            }
176777186bb1a174432ef272584374942d8b9228e39cMarc Blank                            return;
1768bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        }
1769bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    } else {
1770bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                        emptyStream = true;
1771bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    }
1772bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank
1773bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    if (emptyStream) {
1774fd7aac1fbda199ec62f8ea7f1d1edaf15018ba37Marc Blank                        // Make sure we get rid of updates/deletes
1775fd7aac1fbda199ec62f8ea7f1d1edaf15018ba37Marc Blank                        target.cleanup();
177654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        // If this happens, exit cleanly, and change the interval from push to ping
177754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        // if necessary
177854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        userLog("Empty sync response; finishing");
177954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        if (mMailbox.mSyncInterval == Mailbox.CHECK_INTERVAL_PUSH) {
178054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            userLog("Changing mailbox from push to ping");
178154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            ContentValues cv = new ContentValues();
178254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PING);
178354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            mContentResolver.update(
178454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                                    ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId),
178554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                                    cv, null, null);
178654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        }
178754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        if (mRequestQueue.isEmpty()) {
178854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            mExitStatus = EXIT_DONE;
178954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            return;
179054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        } else {
179154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                            continue;
179254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        }
1793b20a744b01fef0082b106f44612a3d0fec361800Marc Blank                    }
17948d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                } else {
179554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    userLog("Sync response error: ", code);
1796ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu                    if (resp.isProvisionError()) {
1797c379d9df9eb8a5ee3ce0a308a146e8d2c73ef8e3Marc Blank                        mExitStatus = EXIT_SECURITY_FAILURE;
1798ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu                    } else if (resp.isAuthError()) {
179954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        mExitStatus = EXIT_LOGIN_FAILURE;
180054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    } else {
180154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                        mExitStatus = EXIT_IO_ERROR;
180254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    }
180354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank                    return;
1804ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
180554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank            } finally {
1806bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                resp.close();
1807ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1808ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
18091b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank        mExitStatus = EXIT_DONE;
1810ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1811ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
181222e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank    protected boolean setupService() {
1813d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank        synchronized(getSynchronizer()) {
1814d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank            mThread = Thread.currentThread();
1815d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
1816d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank            TAG = mThread.getName();
1817d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank        }
18187ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        // Make sure account and mailbox are always the latest from the database
18197ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
182022e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (mAccount == null) return false;
18217ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId);
182222e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (mMailbox == null) return false;
1823ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
182422e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        if (ha == null) return false;
1825ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mHostAddress = ha.mAddress;
1826ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mUserName = ha.mLogin;
1827ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mPassword = ha.mPassword;
182836a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo
182936a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo        try {
1830e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            setConnectionParameters(ha);
183136a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo        } catch (CertificateException e) {
183236a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo            userLog("Couldn't retrieve certificate for connection");
183336a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo            return false;
183436a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo        }
18353852792f1c79dcfca6dc9be67f11e4a53788a462Marc Blank
1836fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        // Set up our protocol version from the Account
18373852792f1c79dcfca6dc9be67f11e4a53788a462Marc Blank        mProtocolVersion = mAccount.mProtocolVersion;
1838fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        // If it hasn't been set up, start with default version
1839fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank        if (mProtocolVersion == null) {
1840d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank            mProtocolVersion = Eas.DEFAULT_PROTOCOL_VERSION;
184154250c7c23b43b644961a8f241a3a3edcc9c796eMarc Blank        }
1842cb5c48824628e93e98ca24edec46f05c54851af1Marc Blank        mProtocolVersionDouble = Eas.getProtocolVersionDouble(mProtocolVersion);
18435a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo
18445a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo        // Do checks to address historical policy sets.
18455a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo        Policy policy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey);
184655c8432b7d7e183cffdcf441b71a04c2d301d96cBen Komalo        if ((policy != null) && policy.mRequireEncryptionExternal) {
18475a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo            // External storage encryption is not supported at this time. In a previous release,
18485a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo            // prior to the system supporting true removable storage on Honeycomb, we accepted
18495a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo            // this since we emulated external storage on partitions that could be encrypted.
18505a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo            // If that was set before, we must clear it out now that the system supports true
18515a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo            // removable storage (which can't be encrypted).
18525a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo            resetSecurityPolicies();
18535a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo        }
185422e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank        return true;
18557ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    }
18567ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank
18575a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo    /**
18585a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo     * Clears out the security policies associated with the account, forcing a provision error
18595a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo     * and a re-sync of the policy information for the account.
18605a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo     */
1861e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    @SuppressWarnings("deprecation")
1862e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    void resetSecurityPolicies() {
18635a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo        ContentValues cv = new ContentValues();
18645a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo        cv.put(AccountColumns.SECURITY_FLAGS, 0);
18655a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo        cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
18665a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo        long accountId = mAccount.mId;
18675a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo        mContentResolver.update(ContentUris.withAppendedId(
18685a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo                Account.CONTENT_URI, accountId), cv, null, null);
18695a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo    }
18705a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo
1871e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo    @Override
18727ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank    public void run() {
18730164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank        try {
18740164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            // Make sure account and mailbox are still valid
18750164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            if (!setupService()) return;
18760164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            // If we've been stopped, we're done
18770164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            if (mStop) return;
1878fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank
18790164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            // Whether or not we're the account mailbox
18800164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            try {
18810164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                mDeviceId = ExchangeService.getDeviceId(mContext);
18820164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                int trafficFlags = TrafficFlags.getSyncFlags(mContext, mAccount);
18830164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                if ((mMailbox == null) || (mAccount == null)) {
18840164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    return;
18850164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                } else {
18860164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    AbstractSyncAdapter target;
18870164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    if (mMailbox.mType == Mailbox.TYPE_CONTACTS) {
1888ff9fed5ccdd57d5408e759f0ca6c41e79e09e999Martin Hibdon                        // ContactsSyncAdapter is gone, and this class is deprecated.
1889ff9fed5ccdd57d5408e759f0ca6c41e79e09e999Martin Hibdon                        // Just leaving this commented out here for reference.
1890ff9fed5ccdd57d5408e759f0ca6c41e79e09e999Martin Hibdon//                        TrafficStats.setThreadStatsTag(trafficFlags | TrafficFlags.DATA_CONTACTS);
1891ff9fed5ccdd57d5408e759f0ca6c41e79e09e999Martin Hibdon//                        target = new ContactsSyncAdapter(this);
1892ff9fed5ccdd57d5408e759f0ca6c41e79e09e999Martin Hibdon                        target = null;
18930164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    } else if (mMailbox.mType == Mailbox.TYPE_CALENDAR) {
1894de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // CalendarSyncAdapter is gone, and this class is deprecated.
1895de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        // Just leaving this commented out here for reference.
1896de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                        TrafficStats.setThreadStatsTag(trafficFlags | TrafficFlags.DATA_CALENDAR);
1897de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon//                        target = new CalendarSyncAdapter(this);
1898de6b8b15730f59f3178e4a4223ddf00c6acd16c2Martin Hibdon                        target = null;
18990164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    } else {
19000164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        TrafficStats.setThreadStatsTag(trafficFlags | TrafficFlags.DATA_EMAIL);
19010164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        target = new EmailSyncAdapter(this);
19020164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    }
19030164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    // We loop because someone might have put a request in while we were syncing
19040164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    // and we've missed that opportunity...
19050164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    do {
19060164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        if (mRequestTime != 0) {
19070164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            userLog("Looping for user request...");
19080164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            mRequestTime = 0;
19090164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        }
19100164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        sync(target);
19110164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    } while (mRequestTime != 0);
1912ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
19130164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            } catch (EasAuthenticationException e) {
19140164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                userLog("Caught authentication error");
19150164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                mExitStatus = EXIT_LOGIN_FAILURE;
19160164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            } catch (IOException e) {
19170164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                String message = e.getMessage();
19180164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                userLog("Caught IOException: ", (message == null) ? "No message" : message);
19190164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                mExitStatus = EXIT_IO_ERROR;
19200164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            } catch (Exception e) {
19210164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                userLog("Uncaught exception in EasSyncService", e);
19220164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            } finally {
19230164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                int status;
1924ac7a749800da2d51a55845344e9c91f6332913b8Marc Blank                ExchangeService.done(this);
19250164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                if (!mStop) {
19260164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    userLog("Sync finished");
19270164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    switch (mExitStatus) {
19280164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        case EXIT_IO_ERROR:
19290164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            status = EmailServiceStatus.CONNECTION_ERROR;
19300164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            break;
19310164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        case EXIT_DONE:
19320164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            status = EmailServiceStatus.SUCCESS;
19330164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            ContentValues cv = new ContentValues();
19340164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
19350164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            String s = "S" + mSyncReason + ':' + status + ':' + mChangeCount;
19360164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            cv.put(Mailbox.SYNC_STATUS, s);
19370164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI,
19380164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                                    mMailboxId), cv, null, null);
19390164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            break;
19400164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        case EXIT_LOGIN_FAILURE:
19410164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            status = EmailServiceStatus.LOGIN_FAILED;
19420164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            break;
19430164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        case EXIT_SECURITY_FAILURE:
19440164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            status = EmailServiceStatus.SECURITY_FAILURE;
19450164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            // Ask for a new folder list. This should wake up the account mailbox; a
19460164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            // security error in account mailbox should start provisioning
19470164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            ExchangeService.reloadFolderList(mContext, mAccount.mId, true);
19480164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            break;
19490164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        case EXIT_ACCESS_DENIED:
19500164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            status = EmailServiceStatus.ACCESS_DENIED;
19510164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            break;
19520164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                        default:
19530164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            status = EmailServiceStatus.REMOTE_EXCEPTION;
19540164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            errorLog("Sync ended due to an exception.");
19550164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                            break;
1956ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
19570164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                } else {
19580164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    userLog("Stopped sync finished.");
19590164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                    status = EmailServiceStatus.SUCCESS;
19605c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank                }
19612fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank
19620164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                // Make sure ExchangeService knows about this
19630164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank                ExchangeService.kick("sync finished");
19640164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank            }
19650164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank        } catch (ProviderUnavailableException e) {
1966942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy            LogUtils.e(TAG, "EmailProvider unavailable; sync ended prematurely");
19670b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo        }
1968ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1969ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
1970