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