EasSyncService.java revision db2f99c98a6eea4cf33daaba3c349b8c263c3e42
1ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/* 2ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Copyright (C) 2008-2009 Marc Blank 3ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed to The Android Open Source Project. 4ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * 5ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed under the Apache License, Version 2.0 (the "License"); 6ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * you may not use this file except in compliance with the License. 7ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * You may obtain a copy of the License at 8ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * 9ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * http://www.apache.org/licenses/LICENSE-2.0 10ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * 11ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Unless required by applicable law or agreed to in writing, software 12ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * distributed under the License is distributed on an "AS IS" BASIS, 13ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * See the License for the specific language governing permissions and 15ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * limitations under the License. 16ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */ 17ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 18ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankpackage com.android.exchange; 19ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 20d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.ContentResolver; 21d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.ContentUris; 22d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.ContentValues; 23d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.Context; 24d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.content.Entity; 25d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.database.Cursor; 26c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blankimport android.net.TrafficStats; 27d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.net.Uri; 28d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.os.Build; 29d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.os.Bundle; 30d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.os.RemoteException; 31d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.os.SystemClock; 32d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.provider.CalendarContract.Attendees; 33d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.provider.CalendarContract.Events; 34d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.text.TextUtils; 35d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.util.Base64; 36d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.util.Log; 37d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blankimport android.util.Xml; 38d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blank 39c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blankimport com.android.emailcommon.TrafficFlags; 40c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.Address; 41c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.MeetingInfo; 42c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.MessagingException; 43c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.PackedString; 447372782488977df778a33d990401ce9e397f646bMarc Blankimport com.android.emailcommon.provider.Account; 45c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.AccountColumns; 46c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.MailboxColumns; 47c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Message; 48c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.MessageColumns; 49c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.SyncColumns; 50c936e6dc6b6979350083a8ab28a24ba8cdda45e0Ben Komaloimport com.android.emailcommon.provider.HostAuth; 514d8774462ace9a45154b2df418b9f2fe7a9c685dBen Komaloimport com.android.emailcommon.provider.Mailbox; 523ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blankimport com.android.emailcommon.provider.Policy; 530164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blankimport com.android.emailcommon.provider.ProviderUnavailableException; 5458dac270fd3a2fb1ff6cb5287ef9b61ea5080e41Marc Blankimport com.android.emailcommon.service.EmailServiceConstants; 5558dac270fd3a2fb1ff6cb5287ef9b61ea5080e41Marc Blankimport com.android.emailcommon.service.EmailServiceProxy; 5658dac270fd3a2fb1ff6cb5287ef9b61ea5080e41Marc Blankimport com.android.emailcommon.service.EmailServiceStatus; 57e0ac5d26481e68c25b976136a6d376c39977e779Ben Komaloimport com.android.emailcommon.utility.EmailClientConnectionManager; 58c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.utility.Utility; 5977186bb1a174432ef272584374942d8b9228e39cMarc Blankimport com.android.exchange.CommandStatusException.CommandStatus; 607c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.AbstractSyncAdapter; 6148af7392c82262d17700e3fbdccf3a582809d449Marc Blankimport com.android.exchange.adapter.AccountSyncAdapter; 62bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blankimport com.android.exchange.adapter.AttachmentLoader; 635862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport com.android.exchange.adapter.CalendarSyncAdapter; 647c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.ContactsSyncAdapter; 657c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.EmailSyncAdapter; 667c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.FolderSyncParser; 6796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadlerimport com.android.exchange.adapter.GalParser; 687531be7774769c84b499b1de5dc46da3a9468316Marc Blankimport com.android.exchange.adapter.MeetingResponseParser; 694471a6960d352242cc65bddf7888cc5335840c74Marc Blankimport com.android.exchange.adapter.MoveItemsParser; 70c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.exchange.adapter.Parser.EasParserException; 71bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blankimport com.android.exchange.adapter.Parser.EmptyStreamException; 727c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.PingParser; 738692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blankimport com.android.exchange.adapter.ProvisionParser; 747c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.Serializer; 757c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.adapter.Tags; 7696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadlerimport com.android.exchange.provider.GalResult; 779c93cb2281bc503e0ad4e1a1691a152a4e4accd0Andy Stadlerimport com.android.exchange.provider.MailboxUtilities; 785c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport com.android.exchange.utility.CalendarUtilities; 79e0ac5d26481e68c25b976136a6d376c39977e779Ben Komaloimport com.google.common.annotations.VisibleForTesting; 80ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 818047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.Header; 8200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.HttpEntity; 8300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.HttpResponse; 841b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blankimport org.apache.http.HttpStatus; 858047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.HttpClient; 868047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.methods.HttpOptions; 8700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.client.methods.HttpPost; 888047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.client.methods.HttpRequestBase; 898047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.entity.ByteArrayEntity; 904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.apache.http.entity.StringEntity; 9100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport org.apache.http.impl.client.DefaultHttpClient; 928047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.BasicHttpParams; 938047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.HttpConnectionParams; 948047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport org.apache.http.params.HttpParams; 954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlPullParser; 964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlPullParserException; 974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlPullParserFactory; 984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport org.xmlpull.v1.XmlSerializer; 9900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank 1004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blankimport java.io.ByteArrayOutputStream; 10100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.IOException; 10200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.InputStream; 1035b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blankimport java.lang.Thread.State; 10400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.net.URI; 105e44d5875af006f4217718a1c0fc0e235af3863afMarc Blankimport java.security.cert.CertificateException; 10600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.ArrayList; 107ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankimport java.util.HashMap; 10800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank 109ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blankpublic class EasSyncService extends AbstractSyncService { 1109b0ebbbe8e94b8da8b95ef4a84d76ab1e8f6a676Marc Blank // DO NOT CHECK IN SET TO TRUE 1119b0ebbbe8e94b8da8b95ef4a84d76ab1e8f6a676Marc Blank public static final boolean DEBUG_GAL_SERVICE = false; 112a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler 113ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID = 114ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SERVER_ID + "=?"; 1159d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank private static final String WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING = 1169d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL + 117f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank '=' + Mailbox.CHECK_INTERVAL_PING; 11822bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank private static final String AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX = " AND " + 119f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank MailboxColumns.SYNC_INTERVAL + " IN (" + Mailbox.CHECK_INTERVAL_PING + 120f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank ',' + Mailbox.CHECK_INTERVAL_PUSH + ") AND " + MailboxColumns.TYPE + "!=\"" + 1217c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank Mailbox.TYPE_EAS_ACCOUNT_MAILBOX + '\"'; 1229d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank private static final String WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX = 1239d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SYNC_INTERVAL + 124f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank '=' + Mailbox.CHECK_INTERVAL_PUSH_HOLD; 1258047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank 1268047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank static private final String PING_COMMAND = "Ping"; 12716b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank // Command timeout is the the time allowed for reading data from an open connection before an 12816b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank // IOException is thrown. After a small added allowance, our watchdog alarm goes off (allowing 12916b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank // us to detect a silently dropped connection). The allowance is defined below. 1300565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank static public final int COMMAND_TIMEOUT = 30*SECONDS; 13116b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank // Connection timeout is the time given to connect to the server before reporting an IOException 1329e029e58959e581637a1288a1ff9ef44cad63576Marc Blank static private final int CONNECTION_TIMEOUT = 20*SECONDS; 13316b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank // The extra time allowed beyond the COMMAND_TIMEOUT before which our watchdog alarm triggers 1345660b2e69436f2839de0659ada4880570c02b4a6Marc Blank static private final int WATCHDOG_TIMEOUT_ALLOWANCE = 30*SECONDS; 135c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank 1363bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank // The amount of time the account mailbox will sleep if there are no pingable mailboxes 1373bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank // This could happen if the sync time is set to "never"; we always want to check in from time 1383bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank // to time, however, for folder list/policy changes 1393bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank static private final int ACCOUNT_MAILBOX_SLEEP_TIME = 20*MINUTES; 1403bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank static private final String ACCOUNT_MAILBOX_SLEEP_TEXT = 1413bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank "Account mailbox sleeping for " + (ACCOUNT_MAILBOX_SLEEP_TIME / MINUTES) + "m"; 1423bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank 1434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank static private final String AUTO_DISCOVER_SCHEMA_PREFIX = 1444d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank "http://schemas.microsoft.com/exchange/autodiscover/mobilesync/"; 1454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank static private final String AUTO_DISCOVER_PAGE = "/autodiscover/autodiscover.xml"; 1464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank static private final int AUTO_DISCOVER_REDIRECT_CODE = 451; 1474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 1486534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank static public final int INTERNAL_SERVER_ERROR_CODE = 500; 1498f29d002c63fdd370e201ea92c532beac4e818e9Marc Blank 150b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank static public final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML"; 151b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank static public final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML"; 152b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank 153a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank static public final int MESSAGE_FLAG_MOVED_MESSAGE = 1 << Message.FLAG_SYNC_ADAPTER_SHIFT; 154a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank 1551b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank /** 1561b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * We start with an 8 minute timeout, and increase/decrease by 3 minutes at a time. There's 1571b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * no point having a timeout shorter than 5 minutes, I think; at that point, we can just let 1581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * the ping exception out. The maximum I use is 17 minutes, which is really an empirical 1591b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * choice; too long and we risk silent connection loss and loss of push for that period. Too 1601b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * short and we lose efficiency/battery life. 1611b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * 1621b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * If we ever have to drop the ping timeout, we'll never increase it again. There's no point 1631b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * going into hysteresis; the NAT timeout isn't going to change without a change in connection, 1641b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * which will cause the sync service to be restarted at the starting heartbeat and going through 1651b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank * the process again. 1661b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank */ 1671b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank static private final int PING_MINUTES = 60; // in seconds 1681b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank static private final int PING_FUDGE_LOW = 10; 1691b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank static private final int PING_STARTING_HEARTBEAT = (8*PING_MINUTES)-PING_FUDGE_LOW; 1701b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank static private final int PING_HEARTBEAT_INCREMENT = 3*PING_MINUTES; 1711b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank 1728efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank // Maximum number of times we'll allow a sync to "loop" with MoreAvailable true before 1738efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank // forcing it to stop. This number has been determined empirically. 1748efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank static private final int MAX_LOOPING_COUNT = 100; 1758efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank 1761b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank static private final int PROTOCOL_PING_STATUS_COMPLETED = 1; 17796293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank 1785b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // The amount of time we allow for a thread to release its post lock after receiving an alert 1795b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank static private final int POST_LOCK_TIMEOUT = 10*SECONDS; 1805b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank 181c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank // Fallbacks (in minutes) for ping loop failures 182252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank static private final int MAX_PING_FAILURES = 1; 183c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank static private final int PING_FALLBACK_INBOX = 5; 18427cf341571fac3d8dbe866f503c34fc31e02bf85Marc Blank static private final int PING_FALLBACK_PIM = 25; 185d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank 1867dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank // The EAS protocol Provision status for "we implement all of the policies" 1877dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank static private final String PROVISION_STATUS_OK = "1"; 1887dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank // The EAS protocol Provision status meaning "we partially implement the policies" 1897dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank static private final String PROVISION_STATUS_PARTIAL = "2"; 1907dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank 1910209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank static /*package*/ final String DEVICE_TYPE = "Android"; 1920209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank static private final String USER_AGENT = DEVICE_TYPE + '/' + Build.VERSION.RELEASE + '-' + 1930209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank Eas.CLIENT_VERSION; 1940209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank 195ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // Reasonable default 196d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank public String mProtocolVersion = Eas.DEFAULT_PROTOCOL_VERSION; 19700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank public Double mProtocolVersionDouble; 19885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank protected String mDeviceId = null; 1997dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank /*package*/ String mAuthString = null; 2008eeede0d74ba2b139ce96d883541f12a562cc17eMarc Blank /*package*/ String mCmdString = null; 201ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank public String mHostAddress; 202ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank public String mUserName; 203ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank public String mPassword; 204e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo 205e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo // The parameters for the connection must be modified through setConnectionParameters 2061b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank private boolean mSsl = true; 2075843b85178a359446f81770ed7734604a1b2fa7dMarc Blank private boolean mTrustSsl = false; 208e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo private String mClientCertAlias = null; 209e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo 210ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank public ContentResolver mContentResolver; 211c936e6dc6b6979350083a8ab28a24ba8cdda45e0Ben Komalo private final String[] mBindArguments = new String[2]; 2121b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank private ArrayList<String> mPingChangeList; 2135b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // The HttpPost in progress 2145b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank private volatile HttpPost mPendingPost = null; 2151b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // Our heartbeat when we are waiting for ping boxes to be ready 2161b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank /*package*/ int mPingForceHeartbeat = 2*PING_MINUTES; 2171b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // The minimum heartbeat we will send 2181b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank /*package*/ int mPingMinHeartbeat = (5*PING_MINUTES)-PING_FUDGE_LOW; 2191b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // The maximum heartbeat we will send 2201b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank /*package*/ int mPingMaxHeartbeat = (17*PING_MINUTES)-PING_FUDGE_LOW; 2211b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank // The ping time (in seconds) 2221b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank /*package*/ int mPingHeartbeat = PING_STARTING_HEARTBEAT; 2231b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank // The longest successful ping heartbeat 2241b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank private int mPingHighWaterMark = 0; 2251b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank // Whether we've ever lowered the heartbeat 2261b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank /*package*/ boolean mPingHeartbeatDropped = false; 227e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank // Whether a POST was aborted due to alarm (watchdog alarm) 228ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank private boolean mPostAborted = false; 229e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank // Whether a POST was aborted due to reset 230e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank private boolean mPostReset = false; 231ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank // Whether or not the sync service is valid (usable) 232ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank public boolean mIsValid = true; 233ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 23461c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank // Whether the most recent upsync failed (status 7) 23561c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank public boolean mUpsyncFailed = false; 23661c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank 237ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank public EasSyncService(Context _context, Mailbox _mailbox) { 238ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank super(_context, _mailbox); 239ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank mContentResolver = _context.getContentResolver(); 240ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank if (mAccount == null) { 241ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank mIsValid = false; 242ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank return; 243ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank } 244ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv); 245ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank if (ha == null) { 246ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank mIsValid = false; 247ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank return; 248ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank } 249ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0; 250c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank mTrustSsl = (ha.mFlags & HostAuth.FLAG_TRUST_ALL) != 0; 251ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 252ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 253ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank private EasSyncService(String prefix) { 254ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank super(prefix); 255ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 256ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 257ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank public EasSyncService() { 258ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank this("EAS Validation"); 259ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 260ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 2615b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank /** 2625b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank * Try to wake up a sync thread that is waiting on an HttpClient POST and has waited past its 2635b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank * socket timeout without having thrown an Exception 2645b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank * 2655b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank * @return true if the POST was successfully stopped; false if we've failed and interrupted 2665b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank * the thread 2675b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank */ 268e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo @Override 2695b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank public boolean alarm() { 2705b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank HttpPost post; 2711d7c38cd7be83e124b0c7e952b85234d4ee64f47Marc Blank if (mThread == null) return true; 2725b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank String threadName = mThread.getName(); 2735b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank 2745b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // Synchronize here so that we are guaranteed to have valid mPendingPost and mPostLock 2755b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // executePostWithTimeout (which executes the HttpPost) also uses this lock 2761b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank synchronized(getSynchronizer()) { 2775b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // Get a reference to the current post lock 2785b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank post = mPendingPost; 2795b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank if (post != null) { 2805b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank if (Eas.USER_LOG) { 2815b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank URI uri = post.getURI(); 2825b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank if (uri != null) { 2835b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank String query = uri.getQuery(); 2845b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank if (query == null) { 2855b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank query = "POST"; 2865b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank } 2875b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank userLog(threadName, ": Alert, aborting ", query); 2885b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank } else { 2895b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank userLog(threadName, ": Alert, no URI?"); 290e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } 291e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } 2925b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // Abort the POST 293ddd7a7dabdd39feb3f769a3141eb4b0dd54db9e1Marc Blank mPostAborted = true; 2945b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank post.abort(); 295e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } else { 2965b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // If there's no POST, we're done 297e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank userLog("Alert, no pending POST"); 2985b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank return true; 2995b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank } 3005b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank } 3015b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank 3025b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // Wait for the POST to finish 3035b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank try { 3045b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank Thread.sleep(POST_LOCK_TIMEOUT); 3055b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank } catch (InterruptedException e) { 3065b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank } 3075b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank 3085b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank State s = mThread.getState(); 3095b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank if (Eas.USER_LOG) { 3105b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank userLog(threadName + ": State = " + s.name()); 3115b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank } 3125b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank 3135b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank synchronized (getSynchronizer()) { 3145b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // If the thread is still hanging around and the same post is pending, let's try to 3155b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // stop the thread with an interrupt. 3165b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank if ((s != State.TERMINATED) && (mPendingPost != null) && (mPendingPost == post)) { 3175b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank mStop = true; 3185b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank mThread.interrupt(); 3195b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank userLog("Interrupting..."); 3205b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // Let the caller know we had to interrupt the thread 3215b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank return false; 322e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } 323e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } 3245b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank // Let the caller know that the alarm was handled normally 3255b4baf900a589e0ed00289631f6d0e1f85a95ecdMarc Blank return true; 326e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } 327e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank 328e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank @Override 329e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank public void reset() { 330e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank synchronized(getSynchronizer()) { 331e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank if (mPendingPost != null) { 332e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank URI uri = mPendingPost.getURI(); 333e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank if (uri != null) { 334e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank String query = uri.getQuery(); 335e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank if (query.startsWith("Cmd=Ping")) { 336e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank userLog("Reset, aborting Ping"); 337e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank mPostReset = true; 338e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank mPendingPost.abort(); 339e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } 340e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } 3411b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 342ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 343ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 344ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 345ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank @Override 346ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank public void stop() { 347ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank mStop = true; 348c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank synchronized(getSynchronizer()) { 349c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank if (mPendingPost != null) { 350c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank mPendingPost.abort(); 351c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank } 352c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank } 3535c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank } 354ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 3551b205668ed6ae85dd5cdd842cfa38b48676cd134Marc Blank @Override 3561b205668ed6ae85dd5cdd842cfa38b48676cd134Marc Blank public void addRequest(Request request) { 3571b205668ed6ae85dd5cdd842cfa38b48676cd134Marc Blank // Don't allow duplicates of requests; just refuse them 3581b205668ed6ae85dd5cdd842cfa38b48676cd134Marc Blank if (mRequestQueue.contains(request)) return; 3591b205668ed6ae85dd5cdd842cfa38b48676cd134Marc Blank // Add the request 3601b205668ed6ae85dd5cdd842cfa38b48676cd134Marc Blank super.addRequest(request); 3611b205668ed6ae85dd5cdd842cfa38b48676cd134Marc Blank } 3621b205668ed6ae85dd5cdd842cfa38b48676cd134Marc Blank 363d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank private void setupProtocolVersion(EasSyncService service, Header versionHeader) 364d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank throws MessagingException { 365d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank // The string is a comma separated list of EAS versions in ascending order 36677186bb1a174432ef272584374942d8b9228e39cMarc Blank // e.g. 1.0,2.0,2.5,12.0,12.1,14.0,14.1 367d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank String supportedVersions = versionHeader.getValue(); 368d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank userLog("Server supports versions: ", supportedVersions); 369d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank String[] supportedVersionsArray = supportedVersions.split(","); 370d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank String ourVersion = null; 371d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank // Find the most recent version we support 372d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank for (String version: supportedVersionsArray) { 373d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank if (version.equals(Eas.SUPPORTED_PROTOCOL_EX2003) || 374e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank version.equals(Eas.SUPPORTED_PROTOCOL_EX2007) || 37577186bb1a174432ef272584374942d8b9228e39cMarc Blank version.equals(Eas.SUPPORTED_PROTOCOL_EX2007_SP1) || 37677186bb1a174432ef272584374942d8b9228e39cMarc Blank version.equals(Eas.SUPPORTED_PROTOCOL_EX2010) || 37777186bb1a174432ef272584374942d8b9228e39cMarc Blank version.equals(Eas.SUPPORTED_PROTOCOL_EX2010_SP1)) { 378d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank ourVersion = version; 3798692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank } 380d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank } 381d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank // If we don't support any of the servers supported versions, throw an exception here 382d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank // This will cause validation to fail 383d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank if (ourVersion == null) { 384d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank Log.w(TAG, "No supported EAS versions: " + supportedVersions); 385d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank throw new MessagingException(MessagingException.PROTOCOL_VERSION_UNSUPPORTED); 386d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank } else { 387d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank service.mProtocolVersion = ourVersion; 388cb5c48824628e93e98ca24edec46f05c54851af1Marc Blank service.mProtocolVersionDouble = Eas.getProtocolVersionDouble(ourVersion); 38982c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank Account account = service.mAccount; 39082c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank if (account != null) { 39182c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank account.mProtocolVersion = ourVersion; 39282c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank // Fixup search flags, if they're not set 39382c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank if (service.mProtocolVersionDouble >= 12.0 && 39482c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank (account.mFlags & Account.FLAGS_SUPPORTS_SEARCH) == 0) { 395097cd98bed66adfffc623f977b776974296cc3faMarc Blank if (account.isSaved()) { 396097cd98bed66adfffc623f977b776974296cc3faMarc Blank ContentValues cv = new ContentValues(); 397097cd98bed66adfffc623f977b776974296cc3faMarc Blank account.mFlags |= 398097cd98bed66adfffc623f977b776974296cc3faMarc Blank Account.FLAGS_SUPPORTS_GLOBAL_SEARCH + Account.FLAGS_SUPPORTS_SEARCH; 399097cd98bed66adfffc623f977b776974296cc3faMarc Blank cv.put(AccountColumns.FLAGS, account.mFlags); 400097cd98bed66adfffc623f977b776974296cc3faMarc Blank account.update(service.mContext, cv); 401097cd98bed66adfffc623f977b776974296cc3faMarc Blank } 40282c3ed7521f8419c05a8b139018321c27a9f1587Marc Blank } 4038692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank } 4048692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank } 4058692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank } 4068692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank 40726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank /** 40826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank * Create an EasSyncService for the specified account 40926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank * 41026d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank * @param context the caller's context 41126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank * @param account the account 41226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank * @return the service, or null if the account is on hold or hasn't been initialized 41326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank */ 41477e0f0dc7b064e0a320d11c33ce9da2f8ae5ba3bMarc Blank public static EasSyncService setupServiceForAccount(Context context, Account account) { 41526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank // Just return null if we're on security hold 41626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) { 41726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank return null; 41826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank } 41926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank // If there's no protocol version, we're not initialized 42026d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank String protocolVersion = account.mProtocolVersion; 42126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank if (protocolVersion == null) { 42226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank return null; 42326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank } 42426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank EasSyncService svc = new EasSyncService("OutOfBand"); 42526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank HostAuth ha = HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv); 42626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank svc.mProtocolVersion = protocolVersion; 42726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank svc.mProtocolVersionDouble = Eas.getProtocolVersionDouble(protocolVersion); 42826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank svc.mContext = context; 42926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank svc.mHostAddress = ha.mAddress; 43026d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank svc.mUserName = ha.mLogin; 43126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank svc.mPassword = ha.mPassword; 43226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank try { 43336a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo svc.setConnectionParameters( 43436a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo (ha.mFlags & HostAuth.FLAG_SSL) != 0, 43536a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo (ha.mFlags & HostAuth.FLAG_TRUST_ALL) != 0, 43636a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo ha.mClientCertAlias); 43726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank svc.mDeviceId = ExchangeService.getDeviceId(context); 43826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank } catch (IOException e) { 43926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank return null; 44036a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo } catch (CertificateException e) { 44136a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo return null; 44226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank } 44326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank svc.mAccount = account; 44426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank return svc; 44526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank } 44626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank 447fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank @Override 44891e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo public Bundle validateAccount(HostAuth hostAuth, Context context) { 4497b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank Bundle bundle = new Bundle(); 4507b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank int resultCode = MessagingException.NO_ERROR; 451ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank try { 45291e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo userLog("Testing EAS: ", hostAuth.mAddress, ", ", hostAuth.mLogin, 45387b42fc62ef62eed2188d11da4f0a8e0b635230aBen Komalo ", ssl = ", hostAuth.shouldUseSsl() ? "1" : "0"); 454ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank EasSyncService svc = new EasSyncService("%TestAccount%"); 4559d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank svc.mContext = context; 45691e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo svc.mHostAddress = hostAuth.mAddress; 45791e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo svc.mUserName = hostAuth.mLogin; 45891e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo svc.mPassword = hostAuth.mPassword; 459e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo 46091e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo svc.setConnectionParameters( 46187b42fc62ef62eed2188d11da4f0a8e0b635230aBen Komalo hostAuth.shouldUseSsl(), 46287b42fc62ef62eed2188d11da4f0a8e0b635230aBen Komalo hostAuth.shouldTrustAllServerCerts(), 46387b42fc62ef62eed2188d11da4f0a8e0b635230aBen Komalo hostAuth.mClientCertAlias); 464704cb199ced39727d84103c7170fc888a54f6c97Marc Blank // We mustn't use the "real" device id or we'll screw up current accounts 465704cb199ced39727d84103c7170fc888a54f6c97Marc Blank // Any string will do, but we'll go for "validate" 466704cb199ced39727d84103c7170fc888a54f6c97Marc Blank svc.mDeviceId = "validate"; 46777186bb1a174432ef272584374942d8b9228e39cMarc Blank svc.mAccount = new Account(); 46891e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo svc.mAccount.mEmailAddress = hostAuth.mLogin; 469bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = svc.sendHttpClientOptions(); 47054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 471bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 47254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Validation (OPTIONS) response: " + code); 47354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (code == HttpStatus.SC_OK) { 47454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // No exception means successful validation 475bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank Header commands = resp.getHeader("MS-ASProtocolCommands"); 476bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank Header versions = resp.getHeader("ms-asprotocolversions"); 47754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Make sure we've got the right protocol version set up 47854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 47954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (commands == null || versions == null) { 48054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("OPTIONS response without commands or versions"); 48154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // We'll treat this as a protocol exception 48254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank throw new MessagingException(0); 48354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 48454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank setupProtocolVersion(svc, versions); 48554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } catch (MessagingException e) { 48654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, 48754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank MessagingException.PROTOCOL_VERSION_UNSUPPORTED); 48854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank return bundle; 4897b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank } 4908692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank 49154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Run second test here for provisioning failures using FolderSync 49254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Try folder sync"); 49354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Send "0" as the sync key for new accounts; otherwise, use the current key 49454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank String syncKey = "0"; 49591e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo Account existingAccount = Utility.findExistingAccount( 49691e6d2a4ff733417090b06f8644615a0fb9160c0Ben Komalo context, -1L, hostAuth.mAddress, hostAuth.mLogin); 49754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (existingAccount != null && existingAccount.mSyncKey != null) { 49854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank syncKey = existingAccount.mSyncKey; 49954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 50054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank Serializer s = new Serializer(); 50154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey) 50254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank .end().end().done(); 50354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank resp = svc.sendHttpClientPost("FolderSync", s.toByteArray()); 504bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank code = resp.getStatus(); 50554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // We'll get one of the following responses if policies are required 5066534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank if (EasResponse.isProvisionError(code)) { 50777186bb1a174432ef272584374942d8b9228e39cMarc Blank throw new CommandStatusException(CommandStatus.NEEDS_PROVISIONING); 50854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } else if (code == HttpStatus.SC_NOT_FOUND) { 50954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // We get a 404 from OWA addresses (which are NOT EAS addresses) 51054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank resultCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED; 5111b52d3a2a7526810c18c040665d75a467b12677cMarc Blank } else if (code == HttpStatus.SC_UNAUTHORIZED) { 5120b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo resultCode = resp.isMissingCertificate() 51336a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo ? MessagingException.CLIENT_CERTIFICATE_REQUIRED 5140b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo : MessagingException.AUTHENTICATION_FAILED; 51554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } else if (code != HttpStatus.SC_OK) { 51654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Fail generically with anything other than success 51754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Unexpected response for FolderSync: ", code); 51854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank resultCode = MessagingException.UNSPECIFIED_EXCEPTION; 51954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } else { 52077186bb1a174432ef272584374942d8b9228e39cMarc Blank // We need to parse the result to see if we've got a provisioning issue 52177186bb1a174432ef272584374942d8b9228e39cMarc Blank // (EAS 14.0 only) 52277186bb1a174432ef272584374942d8b9228e39cMarc Blank if (!resp.isEmpty()) { 52377186bb1a174432ef272584374942d8b9228e39cMarc Blank InputStream is = resp.getInputStream(); 52477186bb1a174432ef272584374942d8b9228e39cMarc Blank // Create the parser with statusOnly set to true; we only care about 52577186bb1a174432ef272584374942d8b9228e39cMarc Blank // seeing if a CommandStatusException is thrown (indicating a 52677186bb1a174432ef272584374942d8b9228e39cMarc Blank // provisioning failure) 52777186bb1a174432ef272584374942d8b9228e39cMarc Blank new FolderSyncParser(is, new AccountSyncAdapter(svc), true).parse(); 52877186bb1a174432ef272584374942d8b9228e39cMarc Blank } 52954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Validation successful"); 53054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 5313d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo } else if (EasResponse.isAuthError(code)) { 53254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Authentication failed"); 5330b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo resultCode = resp.isMissingCertificate() 53436a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo ? MessagingException.CLIENT_CERTIFICATE_REQUIRED 5350b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo : MessagingException.AUTHENTICATION_FAILED; 53654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } else if (code == INTERNAL_SERVER_ERROR_CODE) { 53754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // For Exchange 2003, this could mean an authentication failure OR server error 53854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Internal server error"); 53954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank resultCode = MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR; 5407b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank } else { 54154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // TODO Need to catch other kinds of errors (e.g. policy) For now, report code. 54254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Validation failed, reporting I/O error: ", code); 54354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank resultCode = MessagingException.IOERROR; 54454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 54577186bb1a174432ef272584374942d8b9228e39cMarc Blank } catch (CommandStatusException e) { 54677186bb1a174432ef272584374942d8b9228e39cMarc Blank int status = e.mStatus; 54777186bb1a174432ef272584374942d8b9228e39cMarc Blank if (CommandStatus.isNeedsProvisioning(status)) { 54877186bb1a174432ef272584374942d8b9228e39cMarc Blank // Get the policies and see if we are able to support them 54977186bb1a174432ef272584374942d8b9228e39cMarc Blank ProvisionParser pp = svc.canProvision(); 5503524b49b0e8eda6a3f5d46f08fa90b44fcb5318eMarc Blank if (pp != null && pp.hasSupportablePolicySet()) { 55177186bb1a174432ef272584374942d8b9228e39cMarc Blank // Set the proper result code and save the PolicySet in our Bundle 55277186bb1a174432ef272584374942d8b9228e39cMarc Blank resultCode = MessagingException.SECURITY_POLICIES_REQUIRED; 55377186bb1a174432ef272584374942d8b9228e39cMarc Blank bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, 5543ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank pp.getPolicy()); 55577186bb1a174432ef272584374942d8b9228e39cMarc Blank } else 55677186bb1a174432ef272584374942d8b9228e39cMarc Blank // If not, set the proper code (the account will not be created) 55777186bb1a174432ef272584374942d8b9228e39cMarc Blank resultCode = MessagingException.SECURITY_POLICIES_UNSUPPORTED; 5583524b49b0e8eda6a3f5d46f08fa90b44fcb5318eMarc Blank bundle.putStringArray( 5593524b49b0e8eda6a3f5d46f08fa90b44fcb5318eMarc Blank EmailServiceProxy.VALIDATE_BUNDLE_UNSUPPORTED_POLICIES, 5603524b49b0e8eda6a3f5d46f08fa90b44fcb5318eMarc Blank pp.getUnsupportedPolicies()); 56177186bb1a174432ef272584374942d8b9228e39cMarc Blank } else if (CommandStatus.isDeniedAccess(status)) { 56277186bb1a174432ef272584374942d8b9228e39cMarc Blank userLog("Denied access: ", CommandStatus.toString(status)); 56377186bb1a174432ef272584374942d8b9228e39cMarc Blank resultCode = MessagingException.ACCESS_DENIED; 56477186bb1a174432ef272584374942d8b9228e39cMarc Blank } else if (CommandStatus.isTransientError(status)) { 56577186bb1a174432ef272584374942d8b9228e39cMarc Blank userLog("Transient error: ", CommandStatus.toString(status)); 56677186bb1a174432ef272584374942d8b9228e39cMarc Blank resultCode = MessagingException.IOERROR; 56777186bb1a174432ef272584374942d8b9228e39cMarc Blank } else { 56877186bb1a174432ef272584374942d8b9228e39cMarc Blank userLog("Unexpected response: ", CommandStatus.toString(status)); 56977186bb1a174432ef272584374942d8b9228e39cMarc Blank resultCode = MessagingException.UNSPECIFIED_EXCEPTION; 57077186bb1a174432ef272584374942d8b9228e39cMarc Blank } 57154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 572bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 573bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 574ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } catch (IOException e) { 575e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank Throwable cause = e.getCause(); 576e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank if (cause != null && cause instanceof CertificateException) { 57736a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo // This could be because the server's certificate failed to validate. 578e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank userLog("CertificateException caught: ", e.getMessage()); 5797b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank resultCode = MessagingException.GENERAL_SECURITY; 580e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank } 581e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank userLog("IOException caught: ", e.getMessage()); 5827b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank resultCode = MessagingException.IOERROR; 58336a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo } catch (CertificateException e) { 58436a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo // This occurs if the client certificate the user specified is invalid/inaccessible. 58536a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo userLog("CertificateException caught: ", e.getMessage()); 58636a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo resultCode = MessagingException.CLIENT_CERTIFICATE_ERROR; 587ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 5887b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, resultCode); 5897b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank return bundle; 590ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 591ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 5924d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank /** 5934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * Gets the redirect location from the HTTP headers and uses that to modify the HttpPost so that 5944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * it can be reused 5954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * 5964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @param resp the HttpResponse that indicates a redirect (451) 5974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @param post the HttpPost that was originally sent to the server 5984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @return the HttpPost, updated with the redirect location 5994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank */ 6004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank private HttpPost getRedirect(HttpResponse resp, HttpPost post) { 6014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank Header locHeader = resp.getFirstHeader("X-MS-Location"); 6024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (locHeader != null) { 6034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String loc = locHeader.getValue(); 6044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // If we've gotten one and it shows signs of looking like an address, we try 6054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // sending our request there 6064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (loc != null && loc.startsWith("http")) { 6074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank post.setURI(URI.create(loc)); 6084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank return post; 6094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 6104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 6114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank return null; 6124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 6134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 6144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank /** 6154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * Send the POST command to the autodiscover server, handling a redirect, if necessary, and 61683ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler * return the HttpResponse. If we get a 401 (unauthorized) error and we're using the 61783ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler * full email address, try the bare user name instead (e.g. foo instead of foo@bar.com) 6184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * 6194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @param client the HttpClient to be used for the request 6204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @param post the HttpPost we're going to send 62183ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler * @param canRetry whether we can retry using the bare name on an authentication failure (401) 6224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @return an HttpResponse from the original or redirect server 6234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @throws IOException on any IOException within the HttpClient code 6244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @throws MessagingException 6254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank */ 626bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank private EasResponse postAutodiscover(HttpClient client, HttpPost post, boolean canRetry) 6274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throws IOException, MessagingException { 6284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank userLog("Posting autodiscover to: " + post.getURI()); 629bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = executePostWithTimeout(client, post, COMMAND_TIMEOUT); 630bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 6314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // On a redirect, try the new location 6324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (code == AUTO_DISCOVER_REDIRECT_CODE) { 633bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank post = getRedirect(resp.mResponse, post); 6344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (post != null) { 6354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank userLog("Posting autodiscover to redirect: " + post.getURI()); 636adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank return executePostWithTimeout(client, post, COMMAND_TIMEOUT); 6374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 63883ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // 401 (Unauthorized) is for true auth errors when used in Autodiscover 63952f7f7effdd320594cd1c5c8c67443ad51eca3afMarc Blank } else if (code == HttpStatus.SC_UNAUTHORIZED) { 64083ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler if (canRetry && mUserName.contains("@")) { 64183ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // Try again using the bare user name 64283ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler int atSignIndex = mUserName.indexOf('@'); 64383ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler mUserName = mUserName.substring(0, atSignIndex); 64483ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler cacheAuthAndCmdString(); 64583ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler userLog("401 received; trying username: ", mUserName); 64683ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // Recreate the basic authentication string and reset the header 64783ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler post.removeHeaders("Authorization"); 64883ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler post.setHeader("Authorization", mAuthString); 64983ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler return postAutodiscover(client, post, false); 65083ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler } 6514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throw new MessagingException(MessagingException.AUTHENTICATION_FAILED); 65283ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // 403 (and others) we'll just punt on 6534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (code != HttpStatus.SC_OK) { 6544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // We'll try the next address if this doesn't work 6554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank userLog("Code: " + code + ", throwing IOException"); 6564d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throw new IOException(); 6574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 6584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank return resp; 6594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 6604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 6614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank /** 6624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * Use the Exchange 2007 AutoDiscover feature to try to retrieve server information using 6634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * only an email address and the password 6644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * 6654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @param userName the user's email address 6664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @param password the user's password 6674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank * @return a HostAuth ready to be saved in an Account or null (failure) 6684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank */ 6694d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank public Bundle tryAutodiscover(String userName, String password) throws RemoteException { 6704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank XmlSerializer s = Xml.newSerializer(); 6714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank ByteArrayOutputStream os = new ByteArrayOutputStream(1024); 6724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank HostAuth hostAuth = new HostAuth(); 6734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank Bundle bundle = new Bundle(); 6744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE, 6754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank MessagingException.NO_ERROR); 6764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank try { 6774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // Build the XML document that's sent to the autodiscover server(s) 6784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.setOutput(os, "UTF-8"); 6794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.startDocument("UTF-8", false); 6804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.startTag(null, "Autodiscover"); 6814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.attribute(null, "xmlns", AUTO_DISCOVER_SCHEMA_PREFIX + "requestschema/2006"); 6824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.startTag(null, "Request"); 6834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.startTag(null, "EMailAddress").text(userName).endTag(null, "EMailAddress"); 6844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.startTag(null, "AcceptableResponseSchema"); 6854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.text(AUTO_DISCOVER_SCHEMA_PREFIX + "responseschema/2006"); 6864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.endTag(null, "AcceptableResponseSchema"); 6874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.endTag(null, "Request"); 6884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.endTag(null, "Autodiscover"); 6894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank s.endDocument(); 6904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String req = os.toString(); 6914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 6924d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // Initialize the user name and password 6934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank mUserName = userName; 6944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank mPassword = password; 69583ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // Make sure the authentication string is recreated and cached 69683ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler cacheAuthAndCmdString(); 6974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 6984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // Split out the domain name 6994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank int amp = userName.indexOf('@'); 7004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // The UI ensures that userName is a valid email address 7014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (amp < 0) { 7024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throw new RemoteException(); 7034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 7044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String domain = userName.substring(amp + 1); 7054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 7064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // There are up to four attempts here; the two URLs that we're supposed to try per the 7074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // specification, and up to one redirect for each (handled in postAutodiscover) 70883ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // Note: The expectation is that, of these four attempts, only a single server will 70983ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // actually be identified as the autodiscover server. For the identified server, 71083ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // we may also try a 2nd connection with a different format (bare name). 7114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 7124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // Try the domain first and see if we can get a response 7134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank HttpPost post = new HttpPost("https://" + domain + AUTO_DISCOVER_PAGE); 71420da011530088036d2bf45d3836d6986a4b5d423Marc Blank setHeaders(post, false); 7154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank post.setHeader("Content-Type", "text/xml"); 7164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank post.setEntity(new StringEntity(req)); 7174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank HttpClient client = getHttpClient(COMMAND_TIMEOUT); 718bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp; 7194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank try { 72083ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler resp = postAutodiscover(client, post, true /*canRetry*/); 7214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } catch (IOException e1) { 722adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank userLog("IOException in autodiscover; trying alternate address"); 7234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // We catch the IOException here because we have an alternate address to try 7244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank post.setURI(URI.create("https://autodiscover." + domain + AUTO_DISCOVER_PAGE)); 7254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // If we fail here, we're out of options, so we let the outer try catch the 7264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // IOException and return null 72783ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler resp = postAutodiscover(client, post, true /*canRetry*/); 7284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 7294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 7304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank try { 73154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Get the "final" code; if it's not 200, just return null 732bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 73354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Code: " + code); 73454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (code != HttpStatus.SC_OK) return null; 73554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank 736bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank InputStream is = resp.getInputStream(); 7374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // The response to Autodiscover is regular XML (not WBXML) 7384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // If we ever get an error in this process, we'll just punt and return null 7394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 7404d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank XmlPullParser parser = factory.newPullParser(); 7414d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank parser.setInput(is, "UTF-8"); 7424d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank int type = parser.getEventType(); 7434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (type == XmlPullParser.START_DOCUMENT) { 7444d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank type = parser.next(); 7454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (type == XmlPullParser.START_TAG) { 7464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String name = parser.getName(); 7474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (name.equals("Autodiscover")) { 7484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank hostAuth = new HostAuth(); 7494d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank parseAutodiscover(parser, hostAuth); 7504d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // On success, we'll have a server address and login 75183ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler if (hostAuth.mAddress != null) { 7524d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // Fill in the rest of the HostAuth 75383ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // We use the user name and password that were successful during 75483ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler // the autodiscover process 75583ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler hostAuth.mLogin = mUserName; 75683ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler hostAuth.mPassword = mPassword; 757e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo // Note: there is no way we can auto-discover the proper client 758e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo // SSL certificate to use, if one is needed. 7594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank hostAuth.mPort = 443; 7604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank hostAuth.mProtocol = "eas"; 7614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank hostAuth.mFlags = 7624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank HostAuth.FLAG_SSL | HostAuth.FLAG_AUTHENTICATE; 7634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank bundle.putParcelable( 7644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH, hostAuth); 7654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else { 7664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE, 7674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank MessagingException.UNSPECIFIED_EXCEPTION); 7684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 7694d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 7704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 7714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 7724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } catch (XmlPullParserException e1) { 7734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // This would indicate an I/O error of some sort 7744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // We will simply return null and user can configure manually 77554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 776bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 7774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 7784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // There's no reason at all for exceptions to be thrown, and it's ok if so. 7794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // We just won't do auto-discover; user can configure manually 7804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } catch (IllegalArgumentException e) { 7814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE, 7824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank MessagingException.UNSPECIFIED_EXCEPTION); 7834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } catch (IllegalStateException e) { 7844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE, 7854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank MessagingException.UNSPECIFIED_EXCEPTION); 7864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } catch (IOException e) { 7874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank userLog("IOException in Autodiscover", e); 7884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE, 7894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank MessagingException.IOERROR); 7904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } catch (MessagingException e) { 7914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE, 7924d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank MessagingException.AUTHENTICATION_FAILED); 7934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 7944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank return bundle; 7954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 7964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 7974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank void parseServer(XmlPullParser parser, HostAuth hostAuth) 7984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throws XmlPullParserException, IOException { 7994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank boolean mobileSync = false; 8004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank while (true) { 8014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank int type = parser.next(); 8024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (type == XmlPullParser.END_TAG && parser.getName().equals("Server")) { 8034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank break; 8044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (type == XmlPullParser.START_TAG) { 8054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String name = parser.getName(); 8064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (name.equals("Type")) { 8074d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (parser.nextText().equals("MobileSync")) { 8084d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank mobileSync = true; 8094d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8104d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (mobileSync && name.equals("Url")) { 8114d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String url = parser.nextText().toLowerCase(); 8124d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // This will look like https://<server address>/Microsoft-Server-ActiveSync 8134d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // We need to extract the <server address> 8144d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (url.startsWith("https://") && 8154d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank url.endsWith("/microsoft-server-activesync")) { 8164d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank int lastSlash = url.lastIndexOf('/'); 8174d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank hostAuth.mAddress = url.substring(8, lastSlash); 8184d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank userLog("Autodiscover, server: " + hostAuth.mAddress); 8194d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8204d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8214d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8224d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8234d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8244d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 8254d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank void parseSettings(XmlPullParser parser, HostAuth hostAuth) 8264d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throws XmlPullParserException, IOException { 8274d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank while (true) { 8284d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank int type = parser.next(); 8294d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (type == XmlPullParser.END_TAG && parser.getName().equals("Settings")) { 8304d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank break; 8314d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (type == XmlPullParser.START_TAG) { 8324d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String name = parser.getName(); 8334d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (name.equals("Server")) { 8344d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank parseServer(parser, hostAuth); 8354d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8364d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8374d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8384d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8394d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 8404d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank void parseAction(XmlPullParser parser, HostAuth hostAuth) 8414d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throws XmlPullParserException, IOException { 8424d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank while (true) { 8434d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank int type = parser.next(); 8444d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (type == XmlPullParser.END_TAG && parser.getName().equals("Action")) { 8454d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank break; 8464d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (type == XmlPullParser.START_TAG) { 8474d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String name = parser.getName(); 8484d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (name.equals("Error")) { 8494d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank // Should parse the error 8504d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (name.equals("Redirect")) { 8514d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank Log.d(TAG, "Redirect: " + parser.nextText()); 8524d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (name.equals("Settings")) { 8534d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank parseSettings(parser, hostAuth); 8544d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8554d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8564d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8574d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8584d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 8594d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank void parseUser(XmlPullParser parser, HostAuth hostAuth) 8604d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throws XmlPullParserException, IOException { 8614d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank while (true) { 8624d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank int type = parser.next(); 8634d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (type == XmlPullParser.END_TAG && parser.getName().equals("User")) { 8644d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank break; 8654d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (type == XmlPullParser.START_TAG) { 8664d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String name = parser.getName(); 8674d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (name.equals("EMailAddress")) { 8684d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String addr = parser.nextText(); 86983ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler userLog("Autodiscover, email: " + addr); 8704d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (name.equals("DisplayName")) { 8714d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String dn = parser.nextText(); 8724d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank userLog("Autodiscover, user: " + dn); 8734d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8744d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8754d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8764d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8774d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 8784d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank void parseResponse(XmlPullParser parser, HostAuth hostAuth) 8794d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throws XmlPullParserException, IOException { 8804d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank while (true) { 8814d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank int type = parser.next(); 8824d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (type == XmlPullParser.END_TAG && parser.getName().equals("Response")) { 8834d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank break; 8844d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (type == XmlPullParser.START_TAG) { 8854d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank String name = parser.getName(); 8864d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (name.equals("User")) { 8874d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank parseUser(parser, hostAuth); 8884d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (name.equals("Action")) { 8894d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank parseAction(parser, hostAuth); 8904d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8914d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8924d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8934d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 8944d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 8954d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank void parseAutodiscover(XmlPullParser parser, HostAuth hostAuth) 8964d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank throws XmlPullParserException, IOException { 8974d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank while (true) { 8984d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank int type = parser.nextTag(); 8994d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank if (type == XmlPullParser.END_TAG && parser.getName().equals("Autodiscover")) { 9004d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank break; 9014d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } else if (type == XmlPullParser.START_TAG && parser.getName().equals("Response")) { 9024d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank parseResponse(parser, hostAuth); 9034d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 9044d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 9054d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank } 9064d14c368c1123394068c2f29caf7d3d04c56770aMarc Blank 90796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler /** 90896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler * Contact the GAL and obtain a list of matching accounts 90996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler * @param context caller's context 91096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler * @param accountId the account Id to search 91196bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler * @param filter the characters entered so far 9125aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * @return a result record or null for no data 91396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler * 91496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler * TODO: shorter timeout for interactive lookup 91596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler * TODO: make watchdog actually work (it doesn't understand our service w/Mailbox == 0) 91696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler * TODO: figure out why sendHttpClientPost() hangs - possibly pool exhaustion 91796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler */ 918469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank static public GalResult searchGal(Context context, long accountId, String filter, int limit) { 91926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank Account acct = Account.restoreAccountWithId(context, accountId); 92096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler if (acct != null) { 92126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank EasSyncService svc = setupServiceForAccount(context, acct); 92226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank if (svc == null) return null; 92396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler try { 92496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler Serializer s = new Serializer(); 92596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE); 92696bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler s.data(Tags.SEARCH_NAME, "GAL").data(Tags.SEARCH_QUERY, filter); 92796bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler s.start(Tags.SEARCH_OPTIONS); 928469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank s.data(Tags.SEARCH_RANGE, "0-" + Integer.toString(limit - 1)); 92996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler s.end().end().end().done(); 930bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = svc.sendHttpClientPost("Search", s.toByteArray()); 93154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 932bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 93354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (code == HttpStatus.SC_OK) { 934bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank InputStream is = resp.getInputStream(); 93554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 93654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank GalParser gp = new GalParser(is, svc); 93754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (gp.parse()) { 93854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank return gp.getGalResult(); 93954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 94054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 94154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank is.close(); 94254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 943a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler } else { 94454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank svc.userLog("GAL lookup returned " + code); 94554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 94654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 947bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 94896bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler } 94996bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler } catch (IOException e) { 95096bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler // GAL is non-critical; we'll just go on 951a6bcdcbd31fc4a07ccba54e74ac4ca7e476262feAndrew Stadler svc.userLog("GAL lookup exception " + e); 95296bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler } 95396bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler } 95496bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler return null; 95596bdc2bfdd4d316259380dfba37c4d22dab7aaa0Andrew Stadler } 9567531be7774769c84b499b1de5dc46da3a9468316Marc Blank /** 9575c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank * Send an email responding to a Message that has been marked as a meeting request. The message 9585c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank * will consist a little bit of event information and an iCalendar attachment 9595c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank * @param msg the meeting request email 9605c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank */ 961346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank private void sendMeetingResponseMail(Message msg, int response) { 9625c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Get the meeting information; we'd better have some... 96377186bb1a174432ef272584374942d8b9228e39cMarc Blank if (msg.mMeetingInfo == null) return; 9645c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank PackedString meetingInfo = new PackedString(msg.mMeetingInfo); 9655c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 9665c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // This will come as "First Last" <box@server.blah>, so we use Address to 9675c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // parse it into parts; we only need the email address part for the ics file 9685c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Address[] addrs = Address.parse(meetingInfo.get(MeetingInfo.MEETING_ORGANIZER_EMAIL)); 9695c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // It shouldn't be possible, but handle it anyway 9705c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (addrs.length != 1) return; 9715c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank String organizerEmail = addrs[0].getAddress(); 9725c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 9735c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank String dtStamp = meetingInfo.get(MeetingInfo.MEETING_DTSTAMP); 9745c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank String dtStart = meetingInfo.get(MeetingInfo.MEETING_DTSTART); 9755c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank String dtEnd = meetingInfo.get(MeetingInfo.MEETING_DTEND); 9765c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 9775c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // What we're doing here is to create an Entity that looks like an Event as it would be 9785c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // stored by CalendarProvider 9795c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ContentValues entityValues = new ContentValues(); 9805c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Entity entity = new Entity(entityValues); 9815c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 9825c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Fill in times, location, title, and organizer 9835c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank entityValues.put("DTSTAMP", 9845c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank CalendarUtilities.convertEmailDateTimeToCalendarDateTime(dtStamp)); 9858e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda entityValues.put(Events.DTSTART, Utility.parseEmailDateTimeToMillis(dtStart)); 9868e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda entityValues.put(Events.DTEND, Utility.parseEmailDateTimeToMillis(dtEnd)); 9875c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank entityValues.put(Events.EVENT_LOCATION, meetingInfo.get(MeetingInfo.MEETING_LOCATION)); 9885c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank entityValues.put(Events.TITLE, meetingInfo.get(MeetingInfo.MEETING_TITLE)); 9895c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank entityValues.put(Events.ORGANIZER, organizerEmail); 9905c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 9915c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Add ourselves as an attendee, using our account email address 9925c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ContentValues attendeeValues = new ContentValues(); 9935c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP, 9945c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Attendees.RELATIONSHIP_ATTENDEE); 9955c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank attendeeValues.put(Attendees.ATTENDEE_EMAIL, mAccount.mEmailAddress); 9965c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank entity.addSubValue(Attendees.CONTENT_URI, attendeeValues); 9975c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 9985c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Add the organizer 9995c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ContentValues organizerValues = new ContentValues(); 10005c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank organizerValues.put(Attendees.ATTENDEE_RELATIONSHIP, 10015c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Attendees.RELATIONSHIP_ORGANIZER); 10025c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank organizerValues.put(Attendees.ATTENDEE_EMAIL, organizerEmail); 10035c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank entity.addSubValue(Attendees.CONTENT_URI, organizerValues); 10045c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 10055c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Create a message from the Entity we've built. The message will have fields like 10065c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // to, subject, date, and text filled in. There will also be an "inline" attachment 10075c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // which is in iCalendar format 1008346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank int flag; 1009346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank switch(response) { 1010346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank case EmailServiceConstants.MEETING_REQUEST_ACCEPTED: 1011346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank flag = Message.FLAG_OUTGOING_MEETING_ACCEPT; 1012346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank break; 1013346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank case EmailServiceConstants.MEETING_REQUEST_DECLINED: 1014346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank flag = Message.FLAG_OUTGOING_MEETING_DECLINE; 1015346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank break; 1016346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank case EmailServiceConstants.MEETING_REQUEST_TENTATIVE: 1017346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank default: 1018346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank flag = Message.FLAG_OUTGOING_MEETING_TENTATIVE; 1019346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank break; 1020346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank } 10215c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Message outgoingMsg = 1022346afd9d453de9c15ba92b2af7a0e1b1146a9231Marc Blank CalendarUtilities.createMessageForEntity(mContext, entity, flag, 10235c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank meetingInfo.get(MeetingInfo.MEETING_UID), mAccount); 10245c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Assuming we got a message back (we might not if the event has been deleted), send it 10255c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (outgoingMsg != null) { 10265c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank EasOutboxService.sendMessage(mContext, mAccount.mId, outgoingMsg); 10275c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 10285c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 10295c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 10305c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank /** 10314471a6960d352242cc65bddf7888cc5335840c74Marc Blank * Responds to a move request. The MessageMoveRequest is basically our 10324471a6960d352242cc65bddf7888cc5335840c74Marc Blank * wrapper for the MoveItems service call 10334471a6960d352242cc65bddf7888cc5335840c74Marc Blank * @param req the request (message id and "to" mailbox id) 10344471a6960d352242cc65bddf7888cc5335840c74Marc Blank * @throws IOException 10354471a6960d352242cc65bddf7888cc5335840c74Marc Blank */ 10364471a6960d352242cc65bddf7888cc5335840c74Marc Blank protected void messageMoveRequest(MessageMoveRequest req) throws IOException { 10374471a6960d352242cc65bddf7888cc5335840c74Marc Blank // Retrieve the message and mailbox; punt if either are null 10384471a6960d352242cc65bddf7888cc5335840c74Marc Blank Message msg = Message.restoreMessageWithId(mContext, req.mMessageId); 10394471a6960d352242cc65bddf7888cc5335840c74Marc Blank if (msg == null) return; 10404471a6960d352242cc65bddf7888cc5335840c74Marc Blank Cursor c = mContentResolver.query(ContentUris.withAppendedId(Message.UPDATED_CONTENT_URI, 10414471a6960d352242cc65bddf7888cc5335840c74Marc Blank msg.mId), new String[] {MessageColumns.MAILBOX_KEY}, null, null, null); 1042db2f99c98a6eea4cf33daaba3c349b8c263c3e42Marc Blank if (c == null) throw new ProviderUnavailableException(); 10434471a6960d352242cc65bddf7888cc5335840c74Marc Blank Mailbox srcMailbox = null; 10444471a6960d352242cc65bddf7888cc5335840c74Marc Blank try { 10454471a6960d352242cc65bddf7888cc5335840c74Marc Blank if (!c.moveToNext()) return; 10464471a6960d352242cc65bddf7888cc5335840c74Marc Blank srcMailbox = Mailbox.restoreMailboxWithId(mContext, c.getLong(0)); 10474471a6960d352242cc65bddf7888cc5335840c74Marc Blank } finally { 10484471a6960d352242cc65bddf7888cc5335840c74Marc Blank c.close(); 10494471a6960d352242cc65bddf7888cc5335840c74Marc Blank } 10504471a6960d352242cc65bddf7888cc5335840c74Marc Blank if (srcMailbox == null) return; 10514471a6960d352242cc65bddf7888cc5335840c74Marc Blank Mailbox dstMailbox = Mailbox.restoreMailboxWithId(mContext, req.mMailboxId); 10524471a6960d352242cc65bddf7888cc5335840c74Marc Blank if (dstMailbox == null) return; 10534471a6960d352242cc65bddf7888cc5335840c74Marc Blank Serializer s = new Serializer(); 10544471a6960d352242cc65bddf7888cc5335840c74Marc Blank s.start(Tags.MOVE_MOVE_ITEMS).start(Tags.MOVE_MOVE); 10554471a6960d352242cc65bddf7888cc5335840c74Marc Blank s.data(Tags.MOVE_SRCMSGID, msg.mServerId); 10564471a6960d352242cc65bddf7888cc5335840c74Marc Blank s.data(Tags.MOVE_SRCFLDID, srcMailbox.mServerId); 10574471a6960d352242cc65bddf7888cc5335840c74Marc Blank s.data(Tags.MOVE_DSTFLDID, dstMailbox.mServerId); 10584471a6960d352242cc65bddf7888cc5335840c74Marc Blank s.end().end().done(); 1059bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = sendHttpClientPost("MoveItems", s.toByteArray()); 106054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 1061bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int status = resp.getStatus(); 106254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (status == HttpStatus.SC_OK) { 1063bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (!resp.isEmpty()) { 1064bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank InputStream is = resp.getInputStream(); 1065bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank MoveItemsParser p = new MoveItemsParser(is, this); 1066bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank p.parse(); 1067bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int statusCode = p.getStatusCode(); 1068bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank ContentValues cv = new ContentValues(); 1069bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (statusCode == MoveItemsParser.STATUS_CODE_REVERT) { 1070bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // Restore the old mailbox id 1071bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv.put(MessageColumns.MAILBOX_KEY, srcMailbox.mServerId); 1072bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank mContentResolver.update( 1073bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank ContentUris.withAppendedId(Message.CONTENT_URI, req.mMessageId), 1074bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv, null, null); 1075bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } else if (statusCode == MoveItemsParser.STATUS_CODE_SUCCESS) { 1076bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // Update with the new server id 1077bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv.put(SyncColumns.SERVER_ID, p.getNewServerId()); 1078bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv.put(Message.FLAGS, msg.mFlags | MESSAGE_FLAG_MOVED_MESSAGE); 1079bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank mContentResolver.update( 1080bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank ContentUris.withAppendedId(Message.CONTENT_URI, req.mMessageId), 1081bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv, null, null); 1082bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 1083bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (statusCode == MoveItemsParser.STATUS_CODE_SUCCESS 1084bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank || statusCode == MoveItemsParser.STATUS_CODE_REVERT) { 1085bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // If we revert or succeed, we no longer need the update information 1086bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // OR the now-duplicate email (the new copy will be synced down) 1087bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank mContentResolver.delete(ContentUris.withAppendedId( 1088bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank Message.UPDATED_CONTENT_URI, req.mMessageId), null, null); 1089bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } else { 1090bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // In this case, we're retrying, so do nothing. The request will be 1091bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // handled next sync 109254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 1093bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 10943d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo } else if (EasResponse.isAuthError(status)) { 109554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank throw new EasAuthenticationException(); 109654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } else { 109754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Move items request failed, code: " + status); 109854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank throw new IOException(); 109954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 110054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 1101bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 11024471a6960d352242cc65bddf7888cc5335840c74Marc Blank } 11034471a6960d352242cc65bddf7888cc5335840c74Marc Blank } 11044471a6960d352242cc65bddf7888cc5335840c74Marc Blank 11054471a6960d352242cc65bddf7888cc5335840c74Marc Blank /** 11067531be7774769c84b499b1de5dc46da3a9468316Marc Blank * Responds to a meeting request. The MeetingResponseRequest is basically our 11077531be7774769c84b499b1de5dc46da3a9468316Marc Blank * wrapper for the meetingResponse service call 11087531be7774769c84b499b1de5dc46da3a9468316Marc Blank * @param req the request (message id and response code) 11097531be7774769c84b499b1de5dc46da3a9468316Marc Blank * @throws IOException 11107531be7774769c84b499b1de5dc46da3a9468316Marc Blank */ 11117531be7774769c84b499b1de5dc46da3a9468316Marc Blank protected void sendMeetingResponse(MeetingResponseRequest req) throws IOException { 11124f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank // Retrieve the message and mailbox; punt if either are null 11137531be7774769c84b499b1de5dc46da3a9468316Marc Blank Message msg = Message.restoreMessageWithId(mContext, req.mMessageId); 11144f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank if (msg == null) return; 11154f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, msg.mMailboxKey); 11164f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank if (mailbox == null) return; 11177531be7774769c84b499b1de5dc46da3a9468316Marc Blank Serializer s = new Serializer(); 11187531be7774769c84b499b1de5dc46da3a9468316Marc Blank s.start(Tags.MREQ_MEETING_RESPONSE).start(Tags.MREQ_REQUEST); 11197531be7774769c84b499b1de5dc46da3a9468316Marc Blank s.data(Tags.MREQ_USER_RESPONSE, Integer.toString(req.mResponse)); 11204f9719f9e1af38a85ec1d2a0df48e0836a2ff94eMarc Blank s.data(Tags.MREQ_COLLECTION_ID, mailbox.mServerId); 11217531be7774769c84b499b1de5dc46da3a9468316Marc Blank s.data(Tags.MREQ_REQ_ID, msg.mServerId); 11227531be7774769c84b499b1de5dc46da3a9468316Marc Blank s.end().end().done(); 1123bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = sendHttpClientPost("MeetingResponse", s.toByteArray()); 112454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 1125bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int status = resp.getStatus(); 112654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (status == HttpStatus.SC_OK) { 1127bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (!resp.isEmpty()) { 1128bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank InputStream is = resp.getInputStream(); 112954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank new MeetingResponseParser(is, this).parse(); 113054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank String meetingInfo = msg.mMeetingInfo; 113154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (meetingInfo != null) { 113254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank String responseRequested = new PackedString(meetingInfo).get( 113354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank MeetingInfo.MEETING_RESPONSE_REQUESTED); 113454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // If there's no tag, or a non-zero tag, we send the response mail 113554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if ("0".equals(responseRequested)) { 113654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank return; 113754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 113839fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank } 113954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank sendMeetingResponseMail(msg, req.mResponse); 114039fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank } 11413d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo } else if (EasResponse.isAuthError(status)) { 114254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank throw new EasAuthenticationException(); 114354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } else { 114454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Meeting response request failed, code: " + status); 114554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank throw new IOException(); 11467531be7774769c84b499b1de5dc46da3a9468316Marc Blank } 114754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 1148bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 114954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 11507531be7774769c84b499b1de5dc46da3a9468316Marc Blank } 11517531be7774769c84b499b1de5dc46da3a9468316Marc Blank 115283ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler /** 115383ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler * Using mUserName and mPassword, create and cache mAuthString and mCacheString, which are used 115483ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler * in all HttpPost commands. This should be called if these strings are null, or if mUserName 115583ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler * and/or mPassword are changed 115683ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler */ 115783ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler private void cacheAuthAndCmdString() { 11588eeede0d74ba2b139ce96d883541f12a562cc17eMarc Blank String safeUserName = Uri.encode(mUserName); 115983ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler String cs = mUserName + ':' + mPassword; 116083ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler mAuthString = "Basic " + Base64.encodeToString(cs.getBytes(), Base64.NO_WRAP); 116183ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + 11620209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank "&DeviceType=" + DEVICE_TYPE; 116383ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler } 116483ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler 1165e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo @VisibleForTesting 1166e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo String makeUriString(String cmd, String extra) { 11678eeede0d74ba2b139ce96d883541f12a562cc17eMarc Blank // Cache the authentication string and the command string 116883ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler if (mAuthString == null || mCmdString == null) { 116983ffeefe6055b946692ff36ff17eea40ba6c423fAndrew Stadler cacheAuthAndCmdString(); 1170ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1171e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo String scheme = EmailClientConnectionManager.makeScheme(mSsl, mTrustSsl, mClientCertAlias); 1172e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo String uriString = scheme + "://" + mHostAddress + "/Microsoft-Server-ActiveSync"; 1173ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank if (cmd != null) { 1174e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo uriString += "?Cmd=" + cmd + mCmdString; 1175ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1176ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank if (extra != null) { 1177e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo uriString += extra; 1178ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1179e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo return uriString; 1180ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1181ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 118220da011530088036d2bf45d3836d6986a4b5d423Marc Blank /** 118320da011530088036d2bf45d3836d6986a4b5d423Marc Blank * Set standard HTTP headers, using a policy key if required 118420da011530088036d2bf45d3836d6986a4b5d423Marc Blank * @param method the method we are going to send 118520da011530088036d2bf45d3836d6986a4b5d423Marc Blank * @param usePolicyKey whether or not a policy key should be sent in the headers 118620da011530088036d2bf45d3836d6986a4b5d423Marc Blank */ 11877dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank /*package*/ void setHeaders(HttpRequestBase method, boolean usePolicyKey) { 11888047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank method.setHeader("Authorization", mAuthString); 11898047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank method.setHeader("MS-ASProtocolVersion", mProtocolVersion); 11908047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank method.setHeader("Connection", "keep-alive"); 11910209d8248a237f68b4ae6ed1917a6ea28d30eb32Marc Blank method.setHeader("User-Agent", USER_AGENT); 1192bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank method.setHeader("Accept-Encoding", "gzip"); 11937dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank if (usePolicyKey) { 11947dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank // If there's an account in existence, use its key; otherwise (we're creating the 11957dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank // account), send "0". The server will respond with code 449 if there are policies 11967dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank // to be enforced 11977dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank String key = "0"; 11987dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank if (mAccount != null) { 11997dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank String accountKey = mAccount.mSecuritySyncKey; 12007dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank if (!TextUtils.isEmpty(accountKey)) { 12017dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank key = accountKey; 12027dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank } 120395fcf9c6db1886fdd0d3f98259671cbe3f7ec0d5Marc Blank } 120420da011530088036d2bf45d3836d6986a4b5d423Marc Blank method.setHeader("X-MS-PolicyKey", key); 120520da011530088036d2bf45d3836d6986a4b5d423Marc Blank } 12068047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank } 1207ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 1208e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo protected void setConnectionParameters( 120936a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo boolean useSsl, boolean trustAllServerCerts, String clientCertAlias) 121036a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo throws CertificateException { 1211e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo 1212e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo EmailClientConnectionManager connManager = getClientConnectionManager(); 1213e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo 1214e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo mSsl = useSsl; 1215e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo mTrustSsl = trustAllServerCerts; 1216e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo mClientCertAlias = clientCertAlias; 1217e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo 1218e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo // Register the new alias, if needed. 1219e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo if (mClientCertAlias != null) { 1220e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo // Ensure that the connection manager knows to use the proper client certificate 1221e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo // when establishing connections for this service. 1222e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo connManager.registerClientCert(mContext, mClientCertAlias, mTrustSsl); 1223e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo } 1224e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo } 1225e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo 1226e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo private EmailClientConnectionManager getClientConnectionManager() { 1227385a0be662509754e687bcfa9813208b050bf951Marc Blank return ExchangeService.getClientConnectionManager(); 1228e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank } 1229e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank 12308047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank private HttpClient getHttpClient(int timeout) { 12318047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank HttpParams params = new BasicHttpParams(); 123216b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); 12338047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank HttpConnectionParams.setSoTimeout(params, timeout); 1234e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank HttpConnectionParams.setSocketBufferSize(params, 8192); 12354824be85422975124ee5296bcbc6403be3d206ecMarc Blank HttpClient client = new DefaultHttpClient(getClientConnectionManager(), params); 1236e44d5875af006f4217718a1c0fc0e235af3863afMarc Blank return client; 12378047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank } 1238ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 1239bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blank public EasResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException { 12401b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank return sendHttpClientPost(cmd, new ByteArrayEntity(bytes), COMMAND_TIMEOUT); 12419e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank } 12429e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank 1243bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank protected EasResponse sendHttpClientPost(String cmd, HttpEntity entity) throws IOException { 12441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank return sendHttpClientPost(cmd, entity, COMMAND_TIMEOUT); 12451b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 12461b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank 1247bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank protected EasResponse sendPing(byte[] bytes, int heartbeat) throws IOException { 12481b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank Thread.currentThread().setName(mAccount.mDisplayName + ": Ping"); 12491b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank if (Eas.USER_LOG) { 12502033dfc4e2e6e352b34565112266084d72c443f1Marc Blank userLog("Send ping, timeout: " + heartbeat + "s, high: " + mPingHighWaterMark + 's'); 12511b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 12522033dfc4e2e6e352b34565112266084d72c443f1Marc Blank return sendHttpClientPost(PING_COMMAND, new ByteArrayEntity(bytes), (heartbeat+5)*SECONDS); 12531b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 12541b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank 1255adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank /** 1256adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank * Convenience method for executePostWithTimeout for use other than with the Ping command 1257adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank */ 1258bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank protected EasResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout) 1259adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank throws IOException { 1260adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank return executePostWithTimeout(client, method, timeout, false); 1261adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } 1262adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank 1263adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank /** 1264adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank * Handle executing an HTTP POST command with proper timeout, watchdog, and ping behavior 1265adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank * @param client the HttpClient 1266adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank * @param method the HttpPost 1267adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank * @param timeout the timeout before failure, in ms 1268adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank * @param isPingCommand whether the POST is for the Ping command (requires wakelock logic) 1269adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank * @return the HttpResponse 1270adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank * @throws IOException 1271adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank */ 1272bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank protected EasResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout, 1273adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank boolean isPingCommand) throws IOException { 1274adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank synchronized(getSynchronizer()) { 1275adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank mPendingPost = method; 127616b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank long alarmTime = timeout + WATCHDOG_TIMEOUT_ALLOWANCE; 1277adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank if (isPingCommand) { 1278385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.runAsleep(mMailboxId, alarmTime); 1279adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } else { 1280385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.setWatchdogAlarm(mMailboxId, alarmTime); 1281adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } 1282adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } 1283adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank try { 12843d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo return EasResponse.fromHttpRequest(getClientConnectionManager(), client, method); 1285adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } finally { 1286adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank synchronized(getSynchronizer()) { 1287adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank if (isPingCommand) { 1288385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.runAwake(mMailboxId); 1289adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } else { 1290385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.clearWatchdogAlarm(mMailboxId); 1291adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } 1292adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank mPendingPost = null; 1293adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } 1294adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } 1295adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank } 1296adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank 12970565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank public EasResponse sendHttpClientPost(String cmd, HttpEntity entity, int timeout) 12981b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank throws IOException { 12991b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank HttpClient client = getHttpClient(timeout); 1300c4bc56c4057d2d7596b75c60ee792676251804f5Marc Blank boolean isPingCommand = cmd.equals(PING_COMMAND); 130185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank 130285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank // Split the mail sending commands 130385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank String extra = null; 130485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank boolean msg = false; 130585f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank if (cmd.startsWith("SmartForward&") || cmd.startsWith("SmartReply&")) { 13065843b85178a359446f81770ed7734604a1b2fa7dMarc Blank int cmdLength = cmd.indexOf('&'); 130785f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank extra = cmd.substring(cmdLength); 130885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank cmd = cmd.substring(0, cmdLength); 130985f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank msg = true; 131085f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank } else if (cmd.startsWith("SendMail&")) { 131185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank msg = true; 131285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank } 131385f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank 131485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank String us = makeUriString(cmd, extra); 13158047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank HttpPost method = new HttpPost(URI.create(us)); 1316c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank // Send the proper Content-Type header; it's always wbxml except for messages when 1317c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank // the EAS protocol version is < 14.0 131842f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank // If entity is null (e.g. for attachments), don't set this header 1319c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank if (msg && (mProtocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE)) { 13208047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank method.setHeader("Content-Type", "message/rfc822"); 132142f47790b84483b15fdf4c7f53b283dd1d56d3faMarc Blank } else if (entity != null) { 13228047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml"); 1323ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 132420da011530088036d2bf45d3836d6986a4b5d423Marc Blank setHeaders(method, !cmd.equals(PING_COMMAND)); 13259e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank method.setEntity(entity); 1326adf9fb5f9830ff7933ddc3066cefa5abb52a2773Marc Blank return executePostWithTimeout(client, method, timeout, isPingCommand); 13278047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank } 13288047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank 1329bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank protected EasResponse sendHttpClientOptions() throws IOException { 13308047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank HttpClient client = getHttpClient(COMMAND_TIMEOUT); 13318047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank String us = makeUriString("OPTIONS", null); 13328047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank HttpOptions method = new HttpOptions(URI.create(us)); 133320da011530088036d2bf45d3836d6986a4b5d423Marc Blank setHeaders(method, false); 13343d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo return EasResponse.fromHttpRequest(getClientConnectionManager(), client, method); 1335ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1336ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 1337e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo private String getTargetCollectionClassFromCursor(Cursor c) { 1338ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN); 1339ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank if (type == Mailbox.TYPE_CONTACTS) { 1340ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank return "Contacts"; 1341ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } else if (type == Mailbox.TYPE_CALENDAR) { 1342ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank return "Calendar"; 1343ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } else { 1344ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank return "Email"; 1345ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1346ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1347ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 134820da011530088036d2bf45d3836d6986a4b5d423Marc Blank /** 134920da011530088036d2bf45d3836d6986a4b5d423Marc Blank * Negotiate provisioning with the server. First, get policies form the server and see if 135020da011530088036d2bf45d3836d6986a4b5d423Marc Blank * the policies are supported by the device. Then, write the policies to the account and 135120da011530088036d2bf45d3836d6986a4b5d423Marc Blank * tell SecurityPolicy that we have policies in effect. Finally, see if those policies are 135220da011530088036d2bf45d3836d6986a4b5d423Marc Blank * active; if so, acknowledge the policies to the server and get a final policy key that we 135320da011530088036d2bf45d3836d6986a4b5d423Marc Blank * use in future EAS commands and write this key to the account. 135420da011530088036d2bf45d3836d6986a4b5d423Marc Blank * @return whether or not provisioning has been successful 135520da011530088036d2bf45d3836d6986a4b5d423Marc Blank * @throws IOException 135620da011530088036d2bf45d3836d6986a4b5d423Marc Blank */ 135720da011530088036d2bf45d3836d6986a4b5d423Marc Blank private boolean tryProvision() throws IOException { 135820da011530088036d2bf45d3836d6986a4b5d423Marc Blank // First, see if provisioning is even possible, i.e. do we support the policies required 135920da011530088036d2bf45d3836d6986a4b5d423Marc Blank // by the server 136020da011530088036d2bf45d3836d6986a4b5d423Marc Blank ProvisionParser pp = canProvision(); 136120da011530088036d2bf45d3836d6986a4b5d423Marc Blank if (pp != null) { 136220da011530088036d2bf45d3836d6986a4b5d423Marc Blank // Get the policies from ProvisionParser 13633ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank Policy policy = pp.getPolicy(); 1364d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank Policy oldPolicy = null; 1365d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank // Grab the old policy (if any) 1366d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank if (mAccount.mPolicyKey > 0) { 1367d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank oldPolicy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey); 1368d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank } 136920da011530088036d2bf45d3836d6986a4b5d423Marc Blank // Update the account with a null policyKey (the key we've gotten is 137020da011530088036d2bf45d3836d6986a4b5d423Marc Blank // temporary and cannot be used for syncing) 1371e0f2fe59805655f68a899472bfc10159d9d4fa5fMarc Blank Policy.setAccountPolicy(mContext, mAccount, policy, null); 137207ac0fba502f3ef8ebcc724ace23b45e78fd422fMarc Blank // Make sure that SecurityPolicy is up-to-date 13733ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank SecurityPolicyDelegate.policiesUpdated(mContext, mAccount.mId); 1374bbc1811956084987767779e31525c5013dab59d8Marc Blank if (pp.getRemoteWipe()) { 13752a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank // We've gotten a remote wipe command 13763e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank ExchangeService.alwaysLog("!!! Remote wipe request received"); 13773e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank // Start by setting the account to security hold 1378c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank SecurityPolicyDelegate.setAccountHoldFlag(mContext, mAccount, true); 13793e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank // Force a stop to any running syncs for this account (except this one) 13803e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank ExchangeService.stopNonAccountMailboxSyncsForAccount(mAccount.mId); 13813e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank 13822b82632e75c2154ab3eda63fbfb00f6236a297ddMarc Blank // If we're not the admin, we can't do the wipe, so just return 1383c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank if (!SecurityPolicyDelegate.isActiveAdmin(mContext)) { 13843e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank ExchangeService.alwaysLog("!!! Not device admin; can't wipe"); 13853e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank return false; 13863e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank } 13873e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank 1388bbc1811956084987767779e31525c5013dab59d8Marc Blank // First, we've got to acknowledge it, but wrap the wipe in try/catch so that 1389bbc1811956084987767779e31525c5013dab59d8Marc Blank // we wipe the device regardless of any errors in acknowledgment 13902a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank try { 13913e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank ExchangeService.alwaysLog("!!! Acknowledging remote wipe to server"); 13923ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank acknowledgeRemoteWipe(pp.getSecuritySyncKey()); 13932a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank } catch (Exception e) { 13942a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank // Because remote wipe is such a high priority task, we don't want to 13952a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank // circumvent it if there's an exception in acknowledgment 13962a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank } 13972a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank // Then, tell SecurityPolicy to wipe the device 13983e8f6373729ed72f7f1cdab951b3f9de275aaee6Marc Blank ExchangeService.alwaysLog("!!! Executing remote wipe"); 1399c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank SecurityPolicyDelegate.remoteWipe(mContext); 14002a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank return false; 14013ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank } else if (SecurityPolicyDelegate.isActive(mContext, policy)) { 1402bbc1811956084987767779e31525c5013dab59d8Marc Blank // See if the required policies are in force; if they are, acknowledge the policies 1403bbc1811956084987767779e31525c5013dab59d8Marc Blank // to the server and get the final policy key 14043ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank String securitySyncKey = acknowledgeProvision(pp.getSecuritySyncKey(), 14053ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank PROVISION_STATUS_OK); 14063ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank if (securitySyncKey != null) { 1407d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank // If attachment policies have changed, fix up any affected attachment records 1408d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank if (oldPolicy != null) { 1409d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank if ((oldPolicy.mDontAllowAttachments != policy.mDontAllowAttachments) || 1410d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank (oldPolicy.mMaxAttachmentSize != policy.mMaxAttachmentSize)) { 1411d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank Policy.setAttachmentFlagsForNewPolicy(mContext, mAccount, policy); 1412d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank } 1413d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank } 1414bbc1811956084987767779e31525c5013dab59d8Marc Blank // Write the final policy key to the Account and say we've been successful 1415e0f2fe59805655f68a899472bfc10159d9d4fa5fMarc Blank Policy.setAccountPolicy(mContext, mAccount, policy, securitySyncKey); 1416e5987a354240977e8daa094fe0638531cbbf7162Marc Blank // Release any mailboxes that might be in a security hold 1417385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.releaseSecurityHold(mAccount); 1418bbc1811956084987767779e31525c5013dab59d8Marc Blank return true; 1419bbc1811956084987767779e31525c5013dab59d8Marc Blank } 142020da011530088036d2bf45d3836d6986a4b5d423Marc Blank } else { 142120da011530088036d2bf45d3836d6986a4b5d423Marc Blank // Notify that we are blocked because of policies 1422c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank SecurityPolicyDelegate.policiesRequired(mContext, mAccount.mId); 142320da011530088036d2bf45d3836d6986a4b5d423Marc Blank } 142420da011530088036d2bf45d3836d6986a4b5d423Marc Blank } 142520da011530088036d2bf45d3836d6986a4b5d423Marc Blank return false; 142620da011530088036d2bf45d3836d6986a4b5d423Marc Blank } 142720da011530088036d2bf45d3836d6986a4b5d423Marc Blank 1428b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank private String getPolicyType() { 1429d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank return (mProtocolVersionDouble >= 1430d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE; 1431b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank } 1432b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank 143320da011530088036d2bf45d3836d6986a4b5d423Marc Blank /** 143420da011530088036d2bf45d3836d6986a4b5d423Marc Blank * Obtain a set of policies from the server and determine whether those policies are supported 143520da011530088036d2bf45d3836d6986a4b5d423Marc Blank * by the device. 143632c569e053c3159956e13b2e5b47eb9d167186ceMarc Blank * @return the ProvisionParser (holds policies and key) if we receive policies; null otherwise 143720da011530088036d2bf45d3836d6986a4b5d423Marc Blank * @throws IOException 143820da011530088036d2bf45d3836d6986a4b5d423Marc Blank */ 143920da011530088036d2bf45d3836d6986a4b5d423Marc Blank private ProvisionParser canProvision() throws IOException { 14408692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank Serializer s = new Serializer(); 144177186bb1a174432ef272584374942d8b9228e39cMarc Blank s.start(Tags.PROVISION_PROVISION); 144277186bb1a174432ef272584374942d8b9228e39cMarc Blank if (mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) { 144377186bb1a174432ef272584374942d8b9228e39cMarc Blank // Send settings information in 14.0 and greater 144477186bb1a174432ef272584374942d8b9228e39cMarc Blank s.start(Tags.SETTINGS_DEVICE_INFORMATION).start(Tags.SETTINGS_SET); 144577186bb1a174432ef272584374942d8b9228e39cMarc Blank s.data(Tags.SETTINGS_MODEL, Build.MODEL); 144677186bb1a174432ef272584374942d8b9228e39cMarc Blank //s.data(Tags.SETTINGS_IMEI, ""); 144777186bb1a174432ef272584374942d8b9228e39cMarc Blank //s.data(Tags.SETTINGS_FRIENDLY_NAME, "Friendly Name"); 144877186bb1a174432ef272584374942d8b9228e39cMarc Blank s.data(Tags.SETTINGS_OS, "Android " + Build.VERSION.RELEASE); 144977186bb1a174432ef272584374942d8b9228e39cMarc Blank //s.data(Tags.SETTINGS_OS_LANGUAGE, ""); 145077186bb1a174432ef272584374942d8b9228e39cMarc Blank //s.data(Tags.SETTINGS_PHONE_NUMBER, ""); 145177186bb1a174432ef272584374942d8b9228e39cMarc Blank //s.data(Tags.SETTINGS_MOBILE_OPERATOR, ""); 145277186bb1a174432ef272584374942d8b9228e39cMarc Blank s.data(Tags.SETTINGS_USER_AGENT, USER_AGENT); 145377186bb1a174432ef272584374942d8b9228e39cMarc Blank s.end().end(); // SETTINGS_SET, SETTINGS_DEVICE_INFORMATION 145477186bb1a174432ef272584374942d8b9228e39cMarc Blank } 145577186bb1a174432ef272584374942d8b9228e39cMarc Blank s.start(Tags.PROVISION_POLICIES); 145677186bb1a174432ef272584374942d8b9228e39cMarc Blank s.start(Tags.PROVISION_POLICY).data(Tags.PROVISION_POLICY_TYPE, getPolicyType()).end(); 145777186bb1a174432ef272584374942d8b9228e39cMarc Blank s.end(); // PROVISION_POLICIES 145877186bb1a174432ef272584374942d8b9228e39cMarc Blank s.end().done(); // PROVISION_PROVISION 1459bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = sendHttpClientPost("Provision", s.toByteArray()); 146054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 1461bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 146254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (code == HttpStatus.SC_OK) { 1463bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank InputStream is = resp.getInputStream(); 146454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank ProvisionParser pp = new ProvisionParser(is, this); 146554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (pp.parse()) { 146654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // The PolicySet in the ProvisionParser will have the requirements for all KNOWN 146754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // policies. If others are required, hasSupportablePolicySet will be false 146832c569e053c3159956e13b2e5b47eb9d167186ceMarc Blank if (!pp.hasSupportablePolicySet()) { 146954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Try to acknowledge using the "partial" status (i.e. we can partially 147054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // accommodate the required policies). The server will agree to this if the 147154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // "allow non-provisionable devices" setting is enabled on the server 14723ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank String policyKey = acknowledgeProvision(pp.getSecuritySyncKey(), 147354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank PROVISION_STATUS_PARTIAL); 147454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Return either the parser (success) or null (failure) 1475883a5c77034a3b839127167bc20d05830c8350fbAndy Stadler if (policyKey != null) { 1476883a5c77034a3b839127167bc20d05830c8350fbAndy Stadler pp.clearUnsupportedPolicies(); 1477883a5c77034a3b839127167bc20d05830c8350fbAndy Stadler } 147854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 147932c569e053c3159956e13b2e5b47eb9d167186ceMarc Blank return pp; 14808692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank } 14818692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank } 148254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 1483bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 14848692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank } 148520da011530088036d2bf45d3836d6986a4b5d423Marc Blank // On failures, simply return null 148620da011530088036d2bf45d3836d6986a4b5d423Marc Blank return null; 148720da011530088036d2bf45d3836d6986a4b5d423Marc Blank } 148820da011530088036d2bf45d3836d6986a4b5d423Marc Blank 148920da011530088036d2bf45d3836d6986a4b5d423Marc Blank /** 149020da011530088036d2bf45d3836d6986a4b5d423Marc Blank * Acknowledge that we support the policies provided by the server, and that these policies 149120da011530088036d2bf45d3836d6986a4b5d423Marc Blank * are in force. 149220da011530088036d2bf45d3836d6986a4b5d423Marc Blank * @param tempKey the initial (temporary) policy key sent by the server 149320da011530088036d2bf45d3836d6986a4b5d423Marc Blank * @return the final policy key, which can be used for syncing 149420da011530088036d2bf45d3836d6986a4b5d423Marc Blank * @throws IOException 149520da011530088036d2bf45d3836d6986a4b5d423Marc Blank */ 14962a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank private void acknowledgeRemoteWipe(String tempKey) throws IOException { 14977dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank acknowledgeProvisionImpl(tempKey, PROVISION_STATUS_OK, true); 14982a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank } 14992a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank 15007dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank private String acknowledgeProvision(String tempKey, String result) throws IOException { 15017dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank return acknowledgeProvisionImpl(tempKey, result, false); 15022a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank } 15032a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank 15047dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank private String acknowledgeProvisionImpl(String tempKey, String status, 15057dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank boolean remoteWipe) throws IOException { 150620da011530088036d2bf45d3836d6986a4b5d423Marc Blank Serializer s = new Serializer(); 150720da011530088036d2bf45d3836d6986a4b5d423Marc Blank s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES); 150820da011530088036d2bf45d3836d6986a4b5d423Marc Blank s.start(Tags.PROVISION_POLICY); 1509b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank 1510b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank // Use the proper policy type, depending on EAS version 1511b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType()); 1512b37fc8b9b5f8d1806252051b823bbd1e887f482cMarc Blank 151320da011530088036d2bf45d3836d6986a4b5d423Marc Blank s.data(Tags.PROVISION_POLICY_KEY, tempKey); 15147dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank s.data(Tags.PROVISION_STATUS, status); 15152ca2e1caef16fde39df2c795ac4c9477f9a8c6d9Marc Blank s.end().end(); // PROVISION_POLICY, PROVISION_POLICIES 15162a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank if (remoteWipe) { 15172a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank s.start(Tags.PROVISION_REMOTE_WIPE); 15187dde8276e9e168d933ebfdb86b4f2d8ec5d7b872Marc Blank s.data(Tags.PROVISION_STATUS, PROVISION_STATUS_OK); 15192a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank s.end(); 15202a53f1091aa5a9bb5ca5bc15c730c7e550745ac8Marc Blank } 15212ca2e1caef16fde39df2c795ac4c9477f9a8c6d9Marc Blank s.end().done(); // PROVISION_PROVISION 1522bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = sendHttpClientPost("Provision", s.toByteArray()); 152354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 1524bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 152554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (code == HttpStatus.SC_OK) { 1526bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank InputStream is = resp.getInputStream(); 152754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank ProvisionParser pp = new ProvisionParser(is, this); 152854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (pp.parse()) { 152954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Return the final policy key from the ProvisionParser 15303ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank return pp.getSecuritySyncKey(); 153154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 153254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 153354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 1534bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 153520da011530088036d2bf45d3836d6986a4b5d423Marc Blank } 153620da011530088036d2bf45d3836d6986a4b5d423Marc Blank // On failures, return null 153720da011530088036d2bf45d3836d6986a4b5d423Marc Blank return null; 15388692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank } 15398692940e9f576e7ebbb47f17abd2bc825e42c021Marc Blank 1540ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank /** 1541a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank * Translate exit status code to service status code (used in callbacks) 1542a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank * @param exitStatus the service's exit status 1543a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank * @return the corresponding service status 1544a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank */ 1545a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank private int exitStatusToServiceStatus(int exitStatus) { 1546a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank switch(exitStatus) { 1547a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank case EXIT_SECURITY_FAILURE: 1548a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank return EmailServiceStatus.SECURITY_FAILURE; 1549a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank case EXIT_LOGIN_FAILURE: 1550a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank return EmailServiceStatus.LOGIN_FAILED; 1551a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank default: 1552a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank return EmailServiceStatus.SUCCESS; 1553a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank } 1554a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank } 1555a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank 1556a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank /** 1557ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Performs FolderSync 1558ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * 1559ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * @throws IOException 1560ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * @throws EasParserException 1561ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */ 1562a26c8a8ff1a1d26ed182ed12eb289a372e4a8bb4Marc Blank public void runAccountMailbox() throws IOException, EasParserException { 15639b894d4403addee4214c8faf94fa47222da7e2b0Marc Blank // Check that the account's mailboxes are consistent 15649b894d4403addee4214c8faf94fa47222da7e2b0Marc Blank MailboxUtilities.checkMailboxConsistency(mContext, mAccount.mId); 1565fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank // Initialize exit status to success 15667b4c3ab965df57afdcddc5bce109467458c409f4Marc Blank mExitStatus = EXIT_DONE; 1567ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank try { 1568fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank try { 1569385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.callback() 1570fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank .syncMailboxListStatus(mAccount.mId, EmailServiceStatus.IN_PROGRESS, 0); 1571fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank } catch (RemoteException e1) { 1572fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank // Don't care if this fails 1573fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank } 1574fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank 1575ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank if (mAccount.mSyncKey == null) { 1576ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank mAccount.mSyncKey = "0"; 15771b275b9408d5b856e2482fa3951827489e9585ccMarc Blank userLog("Account syncKey INIT to 0"); 15789387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler ContentValues cv = new ContentValues(); 15799387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey); 15809387711d77c0d3f186f82d9b9512f8a15b4a60dfAndrew Stadler mAccount.update(mContext, cv); 1581ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1582ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 15839d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank boolean firstSync = mAccount.mSyncKey.equals("0"); 15849d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank if (firstSync) { 15851b275b9408d5b856e2482fa3951827489e9585ccMarc Blank userLog("Initial FolderSync"); 15861b275b9408d5b856e2482fa3951827489e9585ccMarc Blank } 15871b275b9408d5b856e2482fa3951827489e9585ccMarc Blank 15884626078bf9d930b2007162db142b5961b38e2166Marc Blank // When we first start up, change all mailboxes to push. 1589ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank ContentValues cv = new ContentValues(); 1590f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH); 1591ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank if (mContentResolver.update(Mailbox.CONTENT_URI, cv, 15929d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank WHERE_ACCOUNT_AND_SYNC_INTERVAL_PING, 15939d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank new String[] {Long.toString(mAccount.mId)}) > 0) { 1594ee54efdb6e7caac88437dbb9f02737f8d44c3b8aMarc Blank ExchangeService.kick("change ping boxes to push"); 1595ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1596ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 1597fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank // Determine our protocol version, if we haven't already and save it in the Account 159885a57898618f48773884e8bc34ca1e8995cc3690Marc Blank // Also re-check protocol version at least once a day (in case of upgrade) 159966c325218d8d28c25828442d36680e99cdac79c0Marc Blank if (mAccount.mProtocolVersion == null || firstSync || 1600c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank ((System.currentTimeMillis() - mMailbox.mSyncTime) > DAYS)) { 16011b275b9408d5b856e2482fa3951827489e9585ccMarc Blank userLog("Determine EAS protocol version"); 1602bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = sendHttpClientOptions(); 1603bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank try { 1604bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 1605bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank userLog("OPTIONS response: ", code); 1606bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (code == HttpStatus.SC_OK) { 1607bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank Header header = resp.getHeader("MS-ASProtocolCommands"); 1608bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank userLog(header.getValue()); 1609bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank header = resp.getHeader("ms-asprotocolversions"); 1610bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank try { 1611bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank setupProtocolVersion(this, header); 1612bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } catch (MessagingException e) { 1613bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // Since we've already validated, this can't really happen 1614bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // But if it does, we'll rethrow this... 1615bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank throw new IOException(); 1616bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 1617bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // Save the protocol version 1618bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv.clear(); 1619bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // Save the protocol version in the account; if we're using 12.0 or greater, 1620bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // set the flag for support of SmartForward 1621bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv.put(Account.PROTOCOL_VERSION, mProtocolVersion); 1622bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (mProtocolVersionDouble >= 12.0) { 1623bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv.put(Account.FLAGS, 1624d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blank mAccount.mFlags | 1625d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blank Account.FLAGS_SUPPORTS_SMART_FORWARD | 1626d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blank Account.FLAGS_SUPPORTS_SEARCH | 1627d586a4a9e631e79abad807f2bb328a3856e9eb23Marc Blank Account.FLAGS_SUPPORTS_GLOBAL_SEARCH); 1628bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 1629bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank mAccount.update(mContext, cv); 1630bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv.clear(); 1631bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // Save the sync time of the account mailbox to current time 1632bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis()); 1633bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank mMailbox.update(mContext, cv); 1634bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } else { 1635bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank errorLog("OPTIONS command failed; throwing IOException"); 1636ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank throw new IOException(); 1637ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1638bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } finally { 1639bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 16400f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank } 16410f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank } 16421b275b9408d5b856e2482fa3951827489e9585ccMarc Blank 164377424af660458104b732bdcb718874b17d0cab3aMarc Blank // Change all pushable boxes to push when we start the account mailbox 164477424af660458104b732bdcb718874b17d0cab3aMarc Blank if (mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH) { 1645fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank cv.clear(); 1646f4ec9557c58b0c5918e3ae4cde23e1355dc0a2afMarc Blank cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH); 164777424af660458104b732bdcb718874b17d0cab3aMarc Blank if (mContentResolver.update(Mailbox.CONTENT_URI, cv, 1648385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.WHERE_IN_ACCOUNT_AND_PUSHABLE, 164977424af660458104b732bdcb718874b17d0cab3aMarc Blank new String[] {Long.toString(mAccount.mId)}) > 0) { 1650ee54efdb6e7caac88437dbb9f02737f8d44c3b8aMarc Blank userLog("Push account; set pushable boxes to push..."); 165177424af660458104b732bdcb718874b17d0cab3aMarc Blank } 165277424af660458104b732bdcb718874b17d0cab3aMarc Blank } 165377424af660458104b732bdcb718874b17d0cab3aMarc Blank 165477424af660458104b732bdcb718874b17d0cab3aMarc Blank while (!mStop) { 1655fa0f067a6552d837591606b9f58be183cc12bf72Marc Blank // If we're not allowed to sync (e.g. roaming policy), leave now 1656fa0f067a6552d837591606b9f58be183cc12bf72Marc Blank if (!ExchangeService.canAutoSync(mAccount)) return; 1657afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler userLog("Sending Account syncKey: ", mAccount.mSyncKey); 1658afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler Serializer s = new Serializer(); 1659afb04a3af2f954affbca09cca462321f46349e00Andrew Stadler s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY) 166020da011530088036d2bf45d3836d6986a4b5d423Marc Blank .text(mAccount.mSyncKey).end().end().done(); 1661bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = sendHttpClientPost("FolderSync", s.toByteArray()); 166254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 166354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (mStop) break; 1664bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 166554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (code == HttpStatus.SC_OK) { 1666bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (!resp.isEmpty()) { 1667bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank InputStream is = resp.getInputStream(); 1668bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // Returns true if we need to sync again 1669bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (new FolderSyncParser(is, new AccountSyncAdapter(this)).parse()) { 1670bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank continue; 167154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 1672bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 16736534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank } else if (EasResponse.isProvisionError(code)) { 167477186bb1a174432ef272584374942d8b9228e39cMarc Blank throw new CommandStatusException(CommandStatus.NEEDS_PROVISIONING); 16753d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo } else if (EasResponse.isAuthError(code)) { 167654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank mExitStatus = EXIT_LOGIN_FAILURE; 167720da011530088036d2bf45d3836d6986a4b5d423Marc Blank return; 1678094e6da9f5c5728eb10a3572717db2ba55718df3Marc Blank } else { 167954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("FolderSync response error: ", code); 168054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 168154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 1682bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 16830f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank } 1684ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 16859d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank // Change all push/hold boxes to push 1686fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank cv.clear(); 16879d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank cv.put(Mailbox.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH); 16889d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank if (mContentResolver.update(Mailbox.CONTENT_URI, cv, 16899d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank WHERE_PUSH_HOLD_NOT_ACCOUNT_MAILBOX, 16909d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank new String[] {Long.toString(mAccount.mId)}) > 0) { 1691ee54efdb6e7caac88437dbb9f02737f8d44c3b8aMarc Blank userLog("Set push/hold boxes to push..."); 16929d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank } 16939d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank 16940f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank try { 1695385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.callback() 1696a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank .syncMailboxListStatus(mAccount.mId, exitStatusToServiceStatus(mExitStatus), 1697a76ee25c71ea2c4a7cac944c512f217b200a5093Marc Blank 0); 16980f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank } catch (RemoteException e1) { 16990f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank // Don't care if this fails 17000f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank } 1701b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank 1702b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank // Before each run of the pingLoop, if this Account has a PolicySet, make sure it's 1703b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank // active; otherwise, clear out the key/flag. This should cause a provisioning 1704b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank // error on the next POST, and start the security sequence over again 1705b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank String key = mAccount.mSecuritySyncKey; 1706b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank if (!TextUtils.isEmpty(key)) { 17073ba50d433ee89b252cd1dd9fd3eee1f2baa3945bMarc Blank Policy policy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey); 170855c8432b7d7e183cffdcf441b71a04c2d301d96cBen Komalo if ((policy != null) && !SecurityPolicyDelegate.isActive(mContext, policy)) { 17095a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo resetSecurityPolicies(); 1710b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank } 1711b25739e9cf48fb450a6906d8f82ddd58a8ade40cMarc Blank } 1712fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank 17130f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank // Wait for push notifications. 17140f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank String threadName = Thread.currentThread().getName(); 17150f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank try { 17160f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank runPingLoop(); 17170f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank } catch (StaleFolderListException e) { 17180f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank // We break out if we get told about a stale folder list 17190f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank userLog("Ping interrupted; folder list requires sync..."); 17201b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank } catch (IllegalHeartbeatException e) { 17211b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // If we're sending an illegal heartbeat, reset either the min or the max to 17221b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // that heartbeat 17231b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank resetHeartbeats(e.mLegalHeartbeat); 17240f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank } finally { 17250f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank Thread.currentThread().setName(threadName); 17260f2a0c93a85ce615ffe521bbc6f5217afae7094bMarc Blank } 1727ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 172877186bb1a174432ef272584374942d8b9228e39cMarc Blank } catch (CommandStatusException e) { 172977186bb1a174432ef272584374942d8b9228e39cMarc Blank // If the sync error is a provisioning failure (perhaps policies changed), 173077186bb1a174432ef272584374942d8b9228e39cMarc Blank // let's try the provisioning procedure 173177186bb1a174432ef272584374942d8b9228e39cMarc Blank // Provisioning must only be attempted for the account mailbox - trying to 173277186bb1a174432ef272584374942d8b9228e39cMarc Blank // provision any other mailbox may result in race conditions and the 173377186bb1a174432ef272584374942d8b9228e39cMarc Blank // creation of multiple policy keys. 173477186bb1a174432ef272584374942d8b9228e39cMarc Blank int status = e.mStatus; 173577186bb1a174432ef272584374942d8b9228e39cMarc Blank if (CommandStatus.isNeedsProvisioning(status)) { 173677186bb1a174432ef272584374942d8b9228e39cMarc Blank if (!tryProvision()) { 173777186bb1a174432ef272584374942d8b9228e39cMarc Blank // Set the appropriate failure status 173877186bb1a174432ef272584374942d8b9228e39cMarc Blank mExitStatus = EXIT_SECURITY_FAILURE; 173977186bb1a174432ef272584374942d8b9228e39cMarc Blank return; 174077186bb1a174432ef272584374942d8b9228e39cMarc Blank } 174177186bb1a174432ef272584374942d8b9228e39cMarc Blank } else if (CommandStatus.isDeniedAccess(status)) { 174277186bb1a174432ef272584374942d8b9228e39cMarc Blank mExitStatus = EXIT_ACCESS_DENIED; 1743a3881891f7904fb4945d791e7f6d15c9e80f02d3Marc Blank try { 1744a3881891f7904fb4945d791e7f6d15c9e80f02d3Marc Blank ExchangeService.callback().syncMailboxListStatus(mAccount.mId, 1745a3881891f7904fb4945d791e7f6d15c9e80f02d3Marc Blank EmailServiceStatus.ACCESS_DENIED, 0); 1746a3881891f7904fb4945d791e7f6d15c9e80f02d3Marc Blank } catch (RemoteException e1) { 1747a3881891f7904fb4945d791e7f6d15c9e80f02d3Marc Blank // Don't care if this fails 1748a3881891f7904fb4945d791e7f6d15c9e80f02d3Marc Blank } 174977186bb1a174432ef272584374942d8b9228e39cMarc Blank return; 175077186bb1a174432ef272584374942d8b9228e39cMarc Blank } else { 175177186bb1a174432ef272584374942d8b9228e39cMarc Blank userLog("Unexpected status: " + CommandStatus.toString(status)); 175277186bb1a174432ef272584374942d8b9228e39cMarc Blank mExitStatus = EXIT_EXCEPTION; 175377186bb1a174432ef272584374942d8b9228e39cMarc Blank } 175477186bb1a174432ef272584374942d8b9228e39cMarc Blank } catch (IOException e) { 1755fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank // We catch this here to send the folder sync status callback 1756fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank // A folder sync failed callback will get sent from run() 1757fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank try { 17584d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank if (!mStop) { 1759a7faafa9b7d64164aea98ae524ddcb79171dc798Marc Blank // NOTE: The correct status is CONNECTION_ERROR, but the UI displays this, and 1760a7faafa9b7d64164aea98ae524ddcb79171dc798Marc Blank // it's not really appropriate for EAS as this is not unexpected for a ping and 1761a7faafa9b7d64164aea98ae524ddcb79171dc798Marc Blank // connection errors are retried in any case 1762385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.callback() 1763a3881891f7904fb4945d791e7f6d15c9e80f02d3Marc Blank .syncMailboxListStatus(mAccount.mId, EmailServiceStatus.SUCCESS, 0); 17644d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank } 1765fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank } catch (RemoteException e1) { 1766fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank // Don't care if this fails 1767fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank } 176896293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank throw e; 1769ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1770ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1771ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 17721b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank /** 17731b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank * Reset either our minimum or maximum ping heartbeat to a heartbeat known to be legal 17741b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank * @param legalHeartbeat a known legal heartbeat (from the EAS server) 17751b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank */ 17761b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank /*package*/ void resetHeartbeats(int legalHeartbeat) { 17771b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank userLog("Resetting min/max heartbeat, legal = " + legalHeartbeat); 17781b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // We are here because the current heartbeat (mPingHeartbeat) is invalid. Depending on 17791b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // whether the argument is above or below the current heartbeat, we can infer the need to 17801b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // change either the minimum or maximum heartbeat 17811b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank if (legalHeartbeat > mPingHeartbeat) { 17821b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // The legal heartbeat is higher than the ping heartbeat; therefore, our minimum was 17831b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // too low. We respond by raising either or both of the minimum heartbeat or the 17841b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // force heartbeat to the argument value 17851b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank if (mPingMinHeartbeat < legalHeartbeat) { 17861b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank mPingMinHeartbeat = legalHeartbeat; 17871b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank } 17881b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank if (mPingForceHeartbeat < legalHeartbeat) { 17891b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank mPingForceHeartbeat = legalHeartbeat; 17901b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank } 17911b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // If our minimum is now greater than the max, bring them together 17921b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank if (mPingMinHeartbeat > mPingMaxHeartbeat) { 17931b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank mPingMaxHeartbeat = legalHeartbeat; 17941b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank } 17951b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank } else if (legalHeartbeat < mPingHeartbeat) { 17961b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // The legal heartbeat is lower than the ping heartbeat; therefore, our maximum was 17971b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // too high. We respond by lowering the maximum to the argument value 17981b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank mPingMaxHeartbeat = legalHeartbeat; 17991b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // If our maximum is now less than the minimum, bring them together 18001b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank if (mPingMaxHeartbeat < mPingMinHeartbeat) { 18011b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank mPingMinHeartbeat = legalHeartbeat; 18021b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank } 18031b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank } 18041b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // Set current heartbeat to the legal heartbeat 18051b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank mPingHeartbeat = legalHeartbeat; 18061b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank // Allow the heartbeat logic to run 18071b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank mPingHeartbeatDropped = false; 18081b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank } 18091b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank 18104ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank private void pushFallback(long mailboxId) { 1811c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId); 18125ea28d42b2aee64747a35d8bf012afff1604bc4cMakoto Onuki if (mailbox == null) { 18135ea28d42b2aee64747a35d8bf012afff1604bc4cMakoto Onuki return; 18145ea28d42b2aee64747a35d8bf012afff1604bc4cMakoto Onuki } 1815c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank ContentValues cv = new ContentValues(); 18164626078bf9d930b2007162db142b5961b38e2166Marc Blank int mins = PING_FALLBACK_PIM; 1817c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank if (mailbox.mType == Mailbox.TYPE_INBOX) { 18184626078bf9d930b2007162db142b5961b38e2166Marc Blank mins = PING_FALLBACK_INBOX; 1819c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank } 18204626078bf9d930b2007162db142b5961b38e2166Marc Blank cv.put(Mailbox.SYNC_INTERVAL, mins); 18214626078bf9d930b2007162db142b5961b38e2166Marc Blank mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 18224626078bf9d930b2007162db142b5961b38e2166Marc Blank cv, null, null); 182327cf341571fac3d8dbe866f503c34fc31e02bf85Marc Blank errorLog("*** PING ERROR LOOP: Set " + mailbox.mDisplayName + " to " + mins + " min sync"); 1824ee54efdb6e7caac88437dbb9f02737f8d44c3b8aMarc Blank ExchangeService.kick("push fallback"); 1825368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank } 1826368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank 18274ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank /** 18284ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank * Simplistic attempt to determine a NAT timeout, based on experience with various carriers 18292675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank * and networks. The string "reset by peer" is very common in these situations, so we look for 18302675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank * that specifically. We may add additional tests here as more is learned. 18314ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank * @param message 18324ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank * @return whether this message is likely associated with a NAT failure 18334ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank */ 18344ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank private boolean isLikelyNatFailure(String message) { 18354ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank if (message == null) return false; 18362675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank if (message.contains("reset by peer")) { 18374ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank return true; 18384ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank } 18394ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank return false; 18404ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank } 18414ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank 18421b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank private void runPingLoop() throws IOException, StaleFolderListException, 184377186bb1a174432ef272584374942d8b9228e39cMarc Blank IllegalHeartbeatException, CommandStatusException { 18441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank int pingHeartbeat = mPingHeartbeat; 184574c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank userLog("runPingLoop"); 1846ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // Do push for all sync services here 1847c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank long endTime = System.currentTimeMillis() + (30*MINUTES); 18481b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank HashMap<String, Integer> pingErrorMap = new HashMap<String, Integer>(); 18492033dfc4e2e6e352b34565112266084d72c443f1Marc Blank ArrayList<String> readyMailboxes = new ArrayList<String>(); 18502033dfc4e2e6e352b34565112266084d72c443f1Marc Blank ArrayList<String> notReadyMailboxes = new ArrayList<String>(); 18517672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank int pingWaitCount = 0; 18525ae89886a6260783e269c54e19b94e919dd2ee32Marc Blank long inboxId = -1; 1853f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker 18547ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank while ((System.currentTimeMillis() < endTime) && !mStop) { 1855ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // Count of pushable mailboxes 1856ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank int pushCount = 0; 1857ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // Count of mailboxes that can be pushed right now 1858ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank int canPushCount = 0; 18598a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank // Count of uninitialized boxes 18608a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank int uninitCount = 0; 1861f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker 18627c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank Serializer s = new Serializer(); 1863ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, 186422bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId + 186522bc4e0e4f4a5e43e4eea8d59e1961860c507594Marc Blank AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null, null); 1866db2f99c98a6eea4cf33daaba3c349b8c263c3e42Marc Blank if (c == null) throw new ProviderUnavailableException(); 18672033dfc4e2e6e352b34565112266084d72c443f1Marc Blank notReadyMailboxes.clear(); 18682033dfc4e2e6e352b34565112266084d72c443f1Marc Blank readyMailboxes.clear(); 18695ae89886a6260783e269c54e19b94e919dd2ee32Marc Blank // Look for an inbox, and remember its id 18705ae89886a6260783e269c54e19b94e919dd2ee32Marc Blank if (inboxId == -1) { 18715ae89886a6260783e269c54e19b94e919dd2ee32Marc Blank inboxId = Mailbox.findMailboxOfType(mContext, mAccount.mId, Mailbox.TYPE_INBOX); 18725ae89886a6260783e269c54e19b94e919dd2ee32Marc Blank } 1873ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank try { 1874ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // Loop through our pushed boxes seeing what is available to push 1875ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank while (c.moveToNext()) { 1876ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank pushCount++; 1877ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // Two requirements for push: 1878385a0be662509754e687bcfa9813208b050bf951Marc Blank // 1) ExchangeService tells us the mailbox is syncable (not running/not stopped) 1879ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // 2) The syncKey isn't "0" (i.e. it's synced at least once) 1880ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank long mailboxId = c.getLong(Mailbox.CONTENT_ID_COLUMN); 1881385a0be662509754e687bcfa9813208b050bf951Marc Blank int pingStatus = ExchangeService.pingStatus(mailboxId); 188277424af660458104b732bdcb718874b17d0cab3aMarc Blank String mailboxName = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN); 1883385a0be662509754e687bcfa9813208b050bf951Marc Blank if (pingStatus == ExchangeService.PING_STATUS_OK) { 1884ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank String syncKey = c.getString(Mailbox.CONTENT_SYNC_KEY_COLUMN); 18857ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank if ((syncKey == null) || syncKey.equals("0")) { 1886a05c26d8d2cce3faa152096cb8116fce375c6d81Marc Blank // We can't push until the initial sync is done 188777424af660458104b732bdcb718874b17d0cab3aMarc Blank pushCount--; 18888a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank uninitCount++; 1889ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank continue; 1890ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1891ed5b71376cb6fc3f54d63268afbd798e0b0c0a1bMarc Blank 1892ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank if (canPushCount++ == 0) { 1893ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // Initialize the Ping command 189496293e01d2c94b7a811f06f56e5f115dd48bc03eMarc Blank s.start(Tags.PING_PING) 189505381a6662f28609e8005023515abb82af00e1d4Marc Blank .data(Tags.PING_HEARTBEAT_INTERVAL, 18961b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank Integer.toString(pingHeartbeat)) 18977c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank .start(Tags.PING_FOLDERS); 1898ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 18991b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank 1900ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank String folderClass = getTargetCollectionClassFromCursor(c); 19017c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank s.start(Tags.PING_FOLDER) 19027c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank .data(Tags.PING_ID, c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN)) 19037c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank .data(Tags.PING_CLASS, folderClass) 19047c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank .end(); 19052033dfc4e2e6e352b34565112266084d72c443f1Marc Blank readyMailboxes.add(mailboxName); 1906385a0be662509754e687bcfa9813208b050bf951Marc Blank } else if ((pingStatus == ExchangeService.PING_STATUS_RUNNING) || 1907385a0be662509754e687bcfa9813208b050bf951Marc Blank (pingStatus == ExchangeService.PING_STATUS_WAITING)) { 19082033dfc4e2e6e352b34565112266084d72c443f1Marc Blank notReadyMailboxes.add(mailboxName); 1909385a0be662509754e687bcfa9813208b050bf951Marc Blank } else if (pingStatus == ExchangeService.PING_STATUS_UNABLE) { 191077424af660458104b732bdcb718874b17d0cab3aMarc Blank pushCount--; 19110a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank userLog(mailboxName, " in error state; ignore"); 191277424af660458104b732bdcb718874b17d0cab3aMarc Blank continue; 1913ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1914ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1915ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } finally { 1916ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank c.close(); 1917ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 1918ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 19192033dfc4e2e6e352b34565112266084d72c443f1Marc Blank if (Eas.USER_LOG) { 19202033dfc4e2e6e352b34565112266084d72c443f1Marc Blank if (!notReadyMailboxes.isEmpty()) { 19212033dfc4e2e6e352b34565112266084d72c443f1Marc Blank userLog("Ping not ready for: " + notReadyMailboxes); 19222033dfc4e2e6e352b34565112266084d72c443f1Marc Blank } 19232033dfc4e2e6e352b34565112266084d72c443f1Marc Blank if (!readyMailboxes.isEmpty()) { 19242033dfc4e2e6e352b34565112266084d72c443f1Marc Blank userLog("Ping ready for: " + readyMailboxes); 19252033dfc4e2e6e352b34565112266084d72c443f1Marc Blank } 19262033dfc4e2e6e352b34565112266084d72c443f1Marc Blank } 1927f78833e76c1decf3a4a1371040a16205d1e59312Doug Zongker 19287672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank // If we've waited 10 seconds or more, just ping with whatever boxes are ready 19297672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank // But use a shorter than normal heartbeat 19302033dfc4e2e6e352b34565112266084d72c443f1Marc Blank boolean forcePing = !notReadyMailboxes.isEmpty() && (pingWaitCount > 5); 19317672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank 19327672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank if ((canPushCount > 0) && ((canPushCount == pushCount) || forcePing)) { 19337672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank // If all pingable boxes are ready for push, send Ping to the server 19347c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank s.end().end().done(); 19357672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank pingWaitCount = 0; 1936e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank mPostReset = false; 1937e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank mPostAborted = false; 19387c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank 19391b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank // If we've been stopped, this is a good time to return 19404d37107554db72f30c68e1df4a2fecd8d4b28d1cMarc Blank if (mStop) return; 1941368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank 194274c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank long pingTime = SystemClock.elapsedRealtime(); 19431b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank try { 19441b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank // Send the ping, wrapped by appropriate timeout/alarm 19457672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank if (forcePing) { 19467672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank userLog("Forcing ping after waiting for all boxes to be ready"); 19477672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank } 1948bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = 19491b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank sendPing(s.toByteArray(), forcePing ? mPingForceHeartbeat : pingHeartbeat); 1950c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank 195154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 1952bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 195354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Ping response: ", code); 1954368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank 1955fa0f067a6552d837591606b9f58be183cc12bf72Marc Blank // If we're not allowed to sync (e.g. roaming policy), terminate gracefully 1956fa0f067a6552d837591606b9f58be183cc12bf72Marc Blank // now; otherwise we might start a sync based on the response 1957fa0f067a6552d837591606b9f58be183cc12bf72Marc Blank if (!ExchangeService.canAutoSync(mAccount)) { 1958fa0f067a6552d837591606b9f58be183cc12bf72Marc Blank mStop = true; 1959fa0f067a6552d837591606b9f58be183cc12bf72Marc Blank } 1960fa0f067a6552d837591606b9f58be183cc12bf72Marc Blank 196154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Return immediately if we've been asked to stop during the ping 196254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (mStop) { 196354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Stopping pingLoop"); 196454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank return; 196554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 19661b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank 196754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (code == HttpStatus.SC_OK) { 196854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // Make sure to clear out any pending sync errors 196954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank ExchangeService.removeFromSyncErrorMap(mMailboxId); 1970bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (!resp.isEmpty()) { 1971bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank InputStream is = resp.getInputStream(); 197254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank int pingResult = parsePingResult(is, mContentResolver, 197354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank pingErrorMap); 197454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // If our ping completed (status = 1), and wasn't forced and we're 197554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // not at the maximum, try increasing timeout by two minutes 197654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (pingResult == PROTOCOL_PING_STATUS_COMPLETED && !forcePing) { 197754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (pingHeartbeat > mPingHighWaterMark) { 197854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank mPingHighWaterMark = pingHeartbeat; 197954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Setting high water mark at: ", mPingHighWaterMark); 198054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 198154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if ((pingHeartbeat < mPingMaxHeartbeat) && 198254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank !mPingHeartbeatDropped) { 198354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank pingHeartbeat += PING_HEARTBEAT_INCREMENT; 198454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (pingHeartbeat > mPingMaxHeartbeat) { 198554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank pingHeartbeat = mPingMaxHeartbeat; 198654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 198754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Increase ping heartbeat to ", pingHeartbeat, "s"); 19887672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank } 19891b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 199054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } else { 199154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Ping returned empty result; throwing IOException"); 199254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank throw new IOException(); 19931b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 19943d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo } else if (EasResponse.isAuthError(code)) { 199554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank mExitStatus = EXIT_LOGIN_FAILURE; 199654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Authorization error during Ping: ", code); 19971b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank throw new IOException(); 19981b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 199954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 2000bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 2001ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 20021b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } catch (IOException e) { 2003252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank String message = e.getMessage(); 20041b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank // If we get the exception that is indicative of a NAT timeout and if we 20051b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank // haven't yet "fixed" the timeout, back off by two minutes and "fix" it 2006252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank boolean hasMessage = message != null; 2007252e460a92f91d9549a3b41376410f7ac7263db8Marc Blank userLog("IOException runPingLoop: " + (hasMessage ? message : "[no message]")); 2008e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank if (mPostReset) { 2009385a0be662509754e687bcfa9813208b050bf951Marc Blank // Nothing to do in this case; this is ExchangeService telling us to try 2010385a0be662509754e687bcfa9813208b050bf951Marc Blank // another ping. 20114ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank } else if (mPostAborted || isLikelyNatFailure(message)) { 2012b9781ea88a58e746b74076ec499e9885e195acc9Marc Blank long pingLength = SystemClock.elapsedRealtime() - pingTime; 20131b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank if ((pingHeartbeat > mPingMinHeartbeat) && 20147ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank (pingHeartbeat > mPingHighWaterMark)) { 20151b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank pingHeartbeat -= PING_HEARTBEAT_INCREMENT; 20161b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank mPingHeartbeatDropped = true; 20171b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank if (pingHeartbeat < mPingMinHeartbeat) { 20181b5ae20491783b1761a5a2747427f4b99ae5e16eMarc Blank pingHeartbeat = mPingMinHeartbeat; 20191b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 20201b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank userLog("Decreased ping heartbeat to ", pingHeartbeat, "s"); 2021e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } else if (mPostAborted) { 2022e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank // There's no point in throwing here; this can happen in two cases 2023e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank // 1) An alarm, which indicates minutes without activity; no sense 2024e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank // backing off 2025385a0be662509754e687bcfa9813208b050bf951Marc Blank // 2) ExchangeService abort, due to sync of mailbox. Again, we want to 2026e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank // keep on trying to ping 2027e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank userLog("Ping aborted; retry"); 2028e8ea6833e0541f0a0a3ceb1d78c84ac9ce359210Marc Blank } else if (pingLength < 2000) { 2029b9781ea88a58e746b74076ec499e9885e195acc9Marc Blank userLog("Abort or NAT type return < 2 seconds; throwing IOException"); 203074c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank throw e; 203174c196e6645cd5547c3ff2e7b6be377c00f1ca74Marc Blank } else { 20324ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank userLog("NAT type IOException"); 20331b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 20342675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank } else if (hasMessage && message.contains("roken pipe")) { 20352675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank // The "broken pipe" error (uppercase or lowercase "b") seems to be an 20362675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank // internal error, so let's not throw an exception (which leads to delays) 20372675dc0ccc3bebaae73900c3446b2c6c08e40eefMarc Blank // but rather simply run through the loop again 20381b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } else { 20391b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank throw e; 20401b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 2041ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 20422033dfc4e2e6e352b34565112266084d72c443f1Marc Blank } else if (forcePing) { 20432033dfc4e2e6e352b34565112266084d72c443f1Marc Blank // In this case, there aren't any boxes that are pingable, but there are boxes 20442033dfc4e2e6e352b34565112266084d72c443f1Marc Blank // waiting (for IOExceptions) 20452033dfc4e2e6e352b34565112266084d72c443f1Marc Blank userLog("pingLoop waiting 60s for any pingable boxes"); 20462033dfc4e2e6e352b34565112266084d72c443f1Marc Blank sleep(60*SECONDS, true); 2047ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } else if (pushCount > 0) { 20481ec0390ce7c27890a46f877d858f7ac6b6a8920cMarc Blank // If we want to Ping, but can't just yet, wait a little bit 2049385a0be662509754e687bcfa9813208b050bf951Marc Blank // TODO Change sleep to wait and use notify from ExchangeService when a sync ends 20502033dfc4e2e6e352b34565112266084d72c443f1Marc Blank sleep(2*SECONDS, false); 20517672d9eac8a70bae0a4310b7ae83f861992de853Marc Blank pingWaitCount++; 20522033dfc4e2e6e352b34565112266084d72c443f1Marc Blank //userLog("pingLoop waited 2s for: ", (pushCount - canPushCount), " box(es)"); 20538a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank } else if (uninitCount > 0) { 20548a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank // In this case, we're doing an initial sync of at least one mailbox. Since this 20558a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank // is typically a one-time case, I'm ok with trying again every 10 seconds until 20568a1fe23c8b305f3f401d3155ceebbb9a975cb7c2Marc Blank // we're in one of the other possible states. 20572033dfc4e2e6e352b34565112266084d72c443f1Marc Blank userLog("pingLoop waiting for initial sync of ", uninitCount, " box(es)"); 20582033dfc4e2e6e352b34565112266084d72c443f1Marc Blank sleep(10*SECONDS, true); 20595ae89886a6260783e269c54e19b94e919dd2ee32Marc Blank } else if (inboxId == -1) { 20605ae89886a6260783e269c54e19b94e919dd2ee32Marc Blank // In this case, we're still syncing mailboxes, so sleep for only a short time 20615ae89886a6260783e269c54e19b94e919dd2ee32Marc Blank sleep(45*SECONDS, true); 2062ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } else { 20633bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank // We've got nothing to do, so we'll check again in 20 minutes at which time 20643bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank // we'll update the folder list, check for policy changes and/or remote wipe, etc. 20653bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank // Let the device sleep in the meantime... 20663bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank userLog(ACCOUNT_MAILBOX_SLEEP_TEXT); 20673bb795f3232db8a28e5ed1039267cb5a531f54e0Marc Blank sleep(ACCOUNT_MAILBOX_SLEEP_TIME, true); 2068ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2069ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 20702ff60f148dfc0ea1b35168a01d8c48de5658e073Marc Blank 20712ff60f148dfc0ea1b35168a01d8c48de5658e073Marc Blank // Save away the current heartbeat 20722ff60f148dfc0ea1b35168a01d8c48de5658e073Marc Blank mPingHeartbeat = pingHeartbeat; 2073ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2074ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 20754ea0f8f6310f68c327ca8f219301eecabedde27aMarc Blank private void sleep(long ms, boolean runAsleep) { 20762033dfc4e2e6e352b34565112266084d72c443f1Marc Blank if (runAsleep) { 2077385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.runAsleep(mMailboxId, ms+(5*SECONDS)); 20782033dfc4e2e6e352b34565112266084d72c443f1Marc Blank } 2079ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank try { 2080ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank Thread.sleep(ms); 2081ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } catch (InterruptedException e) { 2082ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // Doesn't matter whether we stop early; it's the thought that counts 20832033dfc4e2e6e352b34565112266084d72c443f1Marc Blank } finally { 20842033dfc4e2e6e352b34565112266084d72c443f1Marc Blank if (runAsleep) { 2085385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.runAwake(mMailboxId); 20862033dfc4e2e6e352b34565112266084d72c443f1Marc Blank } 2087ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2088ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2089ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 20908d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank private int parsePingResult(InputStream is, ContentResolver cr, 20918d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank HashMap<String, Integer> errorMap) 209277186bb1a174432ef272584374942d8b9228e39cMarc Blank throws IOException, StaleFolderListException, IllegalHeartbeatException, 209377186bb1a174432ef272584374942d8b9228e39cMarc Blank CommandStatusException { 20948047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank PingParser pp = new PingParser(is, this); 2095ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank if (pp.parse()) { 2096ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // True indicates some mailboxes need syncing... 2097ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // syncList has the serverId's of the mailboxes... 2098ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank mBindArguments[0] = Long.toString(mAccount.mId); 20991b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank mPingChangeList = pp.getSyncList(); 21001b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank for (String serverId: mPingChangeList) { 2101d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank mBindArguments[1] = serverId; 2102ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, 2103ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null); 2104db2f99c98a6eea4cf33daaba3c349b8c263c3e42Marc Blank if (c == null) throw new ProviderUnavailableException(); 2105ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank try { 2106ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank if (c.moveToFirst()) { 21078d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank 21088d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank /** 21098d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * Check the boxes reporting changes to see if there really were any... 21108d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * We do this because bugs in various Exchange servers can put us into a 21118d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * looping behavior by continually reporting changes in a mailbox, even when 21128d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * there aren't any. 21138d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * 21148d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * This behavior is seemingly random, and therefore we must code defensively 21158d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * by backing off of push behavior when it is detected. 21168d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * 21178d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * One known cause, on certain Exchange 2003 servers, is acknowledged by 21188d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * Microsoft, and the server hotfix for this case can be found at 21198d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank * http://support.microsoft.com/kb/923282 21208d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank */ 21218d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank 21228d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank // Check the status of the last sync 21238d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank String status = c.getString(Mailbox.CONTENT_SYNC_STATUS_COLUMN); 2124385a0be662509754e687bcfa9813208b050bf951Marc Blank int type = ExchangeService.getStatusType(status); 21258d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank // This check should always be true... 2126385a0be662509754e687bcfa9813208b050bf951Marc Blank if (type == ExchangeService.SYNC_PING) { 2127385a0be662509754e687bcfa9813208b050bf951Marc Blank int changeCount = ExchangeService.getStatusChangeCount(status); 21288d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank if (changeCount > 0) { 21298d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank errorMap.remove(serverId); 21308d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank } else if (changeCount == 0) { 21318d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank // This means that a ping reported changes in error; we keep a count 21328d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank // of consecutive errors of this kind 21338d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank String name = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN); 21348d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank Integer failures = errorMap.get(serverId); 21358d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank if (failures == null) { 21368d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank userLog("Last ping reported changes in error for: ", name); 21378d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank errorMap.put(serverId, 1); 21388d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank } else if (failures > MAX_PING_FAILURES) { 21398d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank // We'll back off of push for this box 21408d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank pushFallback(c.getLong(Mailbox.CONTENT_ID_COLUMN)); 21418d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank continue; 21428d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank } else { 21438d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank userLog("Last ping reported changes in error for: ", name); 21448d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank errorMap.put(serverId, failures + 1); 21458d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank } 21468d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank } 21478d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank } 21488d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank 21498d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank // If there were no problems with previous sync, we'll start another one 2150385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.startManualSync(c.getLong(Mailbox.CONTENT_ID_COLUMN), 2151385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.SYNC_PING, null); 2152ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2153ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } finally { 2154ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank c.close(); 2155ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2156ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2157ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 21581b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank return pp.getSyncStatus(); 2159ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2160ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 2161ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank /** 2162ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Common code to sync E+PIM data 2163ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * 2164883a5c77034a3b839127167bc20d05830c8350fbAndy Stadler * @param target an EasMailbox, EasContacts, or EasCalendar object 2165ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */ 21667c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank public void sync(AbstractSyncAdapter target) throws IOException { 2167ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank Mailbox mailbox = target.mMailbox; 2168ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 2169ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank boolean moreAvailable = true; 21708efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank int loopingCount = 0; 2171b20a744b01fef0082b106f44612a3d0fec361800Marc Blank while (!mStop && (moreAvailable || hasPendingRequests())) { 2172385a0be662509754e687bcfa9813208b050bf951Marc Blank // If we have no connectivity, just exit cleanly. ExchangeService will start us up again 21731b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank // when connectivity has returned 21741b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank if (!hasConnectivity()) { 21751b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank userLog("No connectivity in sync; finishing sync"); 21761b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank mExitStatus = EXIT_DONE; 21771b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank return; 2178aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank } 2179aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank 2180aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank // Every time through the loop we check to see if we're still syncable 2181aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank if (!target.isSyncable()) { 2182aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank mExitStatus = EXIT_DONE; 2183aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank return; 21841b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank } 2185ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 21867531be7774769c84b499b1de5dc46da3a9468316Marc Blank // Now, handle various requests 2187d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank while (true) { 21887531be7774769c84b499b1de5dc46da3a9468316Marc Blank Request req = null; 2189b20a744b01fef0082b106f44612a3d0fec361800Marc Blank 2190b20a744b01fef0082b106f44612a3d0fec361800Marc Blank if (mRequestQueue.isEmpty()) { 2191b20a744b01fef0082b106f44612a3d0fec361800Marc Blank break; 2192b20a744b01fef0082b106f44612a3d0fec361800Marc Blank } else { 2193b20a744b01fef0082b106f44612a3d0fec361800Marc Blank req = mRequestQueue.peek(); 2194d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank } 21957531be7774769c84b499b1de5dc46da3a9468316Marc Blank 21967531be7774769c84b499b1de5dc46da3a9468316Marc Blank // Our two request types are PartRequest (loading attachment) and 21977531be7774769c84b499b1de5dc46da3a9468316Marc Blank // MeetingResponseRequest (respond to a meeting request) 21987531be7774769c84b499b1de5dc46da3a9468316Marc Blank if (req instanceof PartRequest) { 2199c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank TrafficStats.setThreadStatsTag( 2200c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank TrafficFlags.getAttachmentFlags(mContext, mAccount)); 2201bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blank new AttachmentLoader(this, (PartRequest)req).loadAttachment(); 2202c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, mAccount)); 22037531be7774769c84b499b1de5dc46da3a9468316Marc Blank } else if (req instanceof MeetingResponseRequest) { 22047531be7774769c84b499b1de5dc46da3a9468316Marc Blank sendMeetingResponse((MeetingResponseRequest)req); 22054471a6960d352242cc65bddf7888cc5335840c74Marc Blank } else if (req instanceof MessageMoveRequest) { 22064471a6960d352242cc65bddf7888cc5335840c74Marc Blank messageMoveRequest((MessageMoveRequest)req); 22077531be7774769c84b499b1de5dc46da3a9468316Marc Blank } 22087531be7774769c84b499b1de5dc46da3a9468316Marc Blank 22097531be7774769c84b499b1de5dc46da3a9468316Marc Blank // If there's an exception handling the request, we'll throw it 22107531be7774769c84b499b1de5dc46da3a9468316Marc Blank // Otherwise, we remove the request 2211b20a744b01fef0082b106f44612a3d0fec361800Marc Blank mRequestQueue.remove(); 2212b20a744b01fef0082b106f44612a3d0fec361800Marc Blank } 2213b20a744b01fef0082b106f44612a3d0fec361800Marc Blank 2214b20a744b01fef0082b106f44612a3d0fec361800Marc Blank // Don't sync if we've got nothing to do 2215b20a744b01fef0082b106f44612a3d0fec361800Marc Blank if (!moreAvailable) { 2216b20a744b01fef0082b106f44612a3d0fec361800Marc Blank continue; 2217d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank } 2218d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank 22197c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank Serializer s = new Serializer(); 2220c5f958fb9eeafb267f23f2e25769d345be5b22adMarc Blank 2221ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank String className = target.getCollectionName(); 222248af7392c82262d17700e3fbdccf3a582809d449Marc Blank String syncKey = target.getSyncKey(); 22238d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank userLog("sync, sending ", className, " syncKey: ", syncKey); 22247c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank s.start(Tags.SYNC_SYNC) 22257c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank .start(Tags.SYNC_COLLECTIONS) 2226e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank .start(Tags.SYNC_COLLECTION); 2227e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank // The "Class" element is removed in EAS 12.1 and later versions 2228e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank if (mProtocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) { 2229e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank s.data(Tags.SYNC_CLASS, className); 2230e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank } 2231e7d9602fce0d4b404d68716da7eb0567da9dad47Marc Blank s.data(Tags.SYNC_SYNC_KEY, syncKey) 2232e489e48b50bf8a44ddeccd12efa029f8a7b5f85bMarc Blank .data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId); 2233ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 223416b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank // Start with the default timeout 223516b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank int timeout = COMMAND_TIMEOUT; 223648af7392c82262d17700e3fbdccf3a582809d449Marc Blank if (!syncKey.equals("0")) { 2237e489e48b50bf8a44ddeccd12efa029f8a7b5f85bMarc Blank // EAS doesn't allow GetChanges in an initial sync; sending other options 2238e489e48b50bf8a44ddeccd12efa029f8a7b5f85bMarc Blank // appears to cause the server to delay its response in some cases, and this delay 2239e489e48b50bf8a44ddeccd12efa029f8a7b5f85bMarc Blank // can be long enough to result in an IOException and total failure to sync. 2240e489e48b50bf8a44ddeccd12efa029f8a7b5f85bMarc Blank // Therefore, we don't send any options with the initial sync. 22414f15001bdfd11c79524b4e44d60041967779e763Marc Blank // Set the truncation amount, body preference, lookback, etc. 22424f15001bdfd11c79524b4e44d60041967779e763Marc Blank target.sendSyncOptions(mProtocolVersionDouble, s); 2243895d1e3132622653160516d420231ed366ab411bMarc Blank } else { 2244e489e48b50bf8a44ddeccd12efa029f8a7b5f85bMarc Blank // Use enormous timeout for initial sync, which empirically can take a while longer 2245e489e48b50bf8a44ddeccd12efa029f8a7b5f85bMarc Blank timeout = 120*SECONDS; 2246ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2247ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank // Send our changes up to the server 224861c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank if (mUpsyncFailed) { 224961c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank if (Eas.USER_LOG) { 225061c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank Log.d(TAG, "Inhibiting upsync this cycle"); 225161c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank } 225261c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank } else { 225361c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank target.sendLocalChanges(s); 225461c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank } 2255ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 22567c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank s.end().end().end().done(); 2257bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank EasResponse resp = sendHttpClientPost("Sync", new ByteArrayEntity(s.toByteArray()), 225816b445cd6c4de57ae144fe76449ac6953333f0e9Marc Blank timeout); 225954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank try { 2260bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank int code = resp.getStatus(); 226154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (code == HttpStatus.SC_OK) { 226254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // In EAS 12.1, we can get "empty" sync responses, which indicate that there are 226354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // no changes in the mailbox; handle that case here 2264bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // There are two cases here; if we get back a compressed stream (GZIP), we won't 2265bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // know until we try to parse it (and generate an EmptyStreamException). If we 2266bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // get uncompressed data, the response will be empty (i.e. have zero length) 2267bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank boolean emptyStream = false; 2268bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (!resp.isEmpty()) { 2269bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank InputStream is = resp.getInputStream(); 2270bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank try { 2271bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank moreAvailable = target.parse(is); 227261c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank // If we inhibited upsync, we need yet another sync 227361c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank if (mUpsyncFailed) { 227461c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank moreAvailable = true; 227561c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank } 227661c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank 2277bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (target.isLooping()) { 2278bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank loopingCount++; 2279bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank userLog("** Looping: " + loopingCount); 2280bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // After the maximum number of loops, we'll set moreAvailable to 2281bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank // false and allow the sync loop to terminate 2282bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (moreAvailable && (loopingCount > MAX_LOOPING_COUNT)) { 2283bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank userLog("** Looping force stopped"); 2284bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank moreAvailable = false; 2285bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 2286bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } else { 2287bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank loopingCount = 0; 2288bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 228961c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank 229061c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank // Cleanup clears out the updated/deleted tables, and we don't want to 229161c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank // do that if our upsync failed; clear the flag otherwise 229261c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank if (!mUpsyncFailed) { 229361c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank target.cleanup(); 229461c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank } else { 229561c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank mUpsyncFailed = false; 229661c48abf43082f3094f21fd4c645ad9cf4dfbd74Marc Blank } 2297bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } catch (EmptyStreamException e) { 2298bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank userLog("Empty stream detected in GZIP response"); 2299bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank emptyStream = true; 230077186bb1a174432ef272584374942d8b9228e39cMarc Blank } catch (CommandStatusException e) { 230177186bb1a174432ef272584374942d8b9228e39cMarc Blank // TODO 14.1 230277186bb1a174432ef272584374942d8b9228e39cMarc Blank int status = e.mStatus; 230377186bb1a174432ef272584374942d8b9228e39cMarc Blank if (CommandStatus.isNeedsProvisioning(status)) { 2304c379d9df9eb8a5ee3ce0a308a146e8d2c73ef8e3Marc Blank mExitStatus = EXIT_SECURITY_FAILURE; 230577186bb1a174432ef272584374942d8b9228e39cMarc Blank } else if (CommandStatus.isDeniedAccess(status)) { 230677186bb1a174432ef272584374942d8b9228e39cMarc Blank mExitStatus = EXIT_ACCESS_DENIED; 230777186bb1a174432ef272584374942d8b9228e39cMarc Blank } else if (CommandStatus.isTransientError(status)) { 230877186bb1a174432ef272584374942d8b9228e39cMarc Blank mExitStatus = EXIT_IO_ERROR; 230977186bb1a174432ef272584374942d8b9228e39cMarc Blank } else { 231077186bb1a174432ef272584374942d8b9228e39cMarc Blank mExitStatus = EXIT_EXCEPTION; 231177186bb1a174432ef272584374942d8b9228e39cMarc Blank } 231277186bb1a174432ef272584374942d8b9228e39cMarc Blank return; 2313bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 2314bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } else { 2315bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank emptyStream = true; 2316bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank } 2317bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank 2318bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank if (emptyStream) { 231954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // If this happens, exit cleanly, and change the interval from push to ping 232054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank // if necessary 232154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Empty sync response; finishing"); 232254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (mMailbox.mSyncInterval == Mailbox.CHECK_INTERVAL_PUSH) { 232354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Changing mailbox from push to ping"); 232454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank ContentValues cv = new ContentValues(); 232554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PING); 232654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank mContentResolver.update( 232754ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId), 232854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank cv, null, null); 232954ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 233054ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank if (mRequestQueue.isEmpty()) { 233154ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank mExitStatus = EXIT_DONE; 233254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank return; 233354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } else { 233454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank continue; 233554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 2336b20a744b01fef0082b106f44612a3d0fec361800Marc Blank } 23378d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank } else { 233854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank userLog("Sync response error: ", code); 23396534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank if (EasResponse.isProvisionError(code)) { 2340c379d9df9eb8a5ee3ce0a308a146e8d2c73ef8e3Marc Blank mExitStatus = EXIT_SECURITY_FAILURE; 23413d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo } else if (EasResponse.isAuthError(code)) { 234254ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank mExitStatus = EXIT_LOGIN_FAILURE; 234354ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } else { 234454ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank mExitStatus = EXIT_IO_ERROR; 234554ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } 234654ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank return; 2347ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 234854ca4d97c7c0955b902bea9eb2aa1cd54d435878Marc Blank } finally { 2349bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank resp.close(); 2350ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2351ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 23521b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blank mExitStatus = EXIT_DONE; 2353ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2354ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank 235522e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank protected boolean setupService() { 2356d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank synchronized(getSynchronizer()) { 2357d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank mThread = Thread.currentThread(); 2358d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); 2359d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank TAG = mThread.getName(); 2360d0bb59f5d1c7e1d8b6c2350d3ad769a2073a0230Marc Blank } 23617ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank // Make sure account and mailbox are always the latest from the database 23627ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank mAccount = Account.restoreAccountWithId(mContext, mAccount.mId); 236322e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank if (mAccount == null) return false; 23647ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId); 236522e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank if (mMailbox == null) return false; 2366ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv); 236722e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank if (ha == null) return false; 2368ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank mHostAddress = ha.mAddress; 2369ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank mUserName = ha.mLogin; 2370ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank mPassword = ha.mPassword; 237136a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo 237236a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo try { 237336a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo setConnectionParameters( 237436a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo (ha.mFlags & HostAuth.FLAG_SSL) != 0, 237536a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo (ha.mFlags & HostAuth.FLAG_TRUST_ALL) != 0, 237636a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo ha.mClientCertAlias); 237736a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo } catch (CertificateException e) { 237836a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo userLog("Couldn't retrieve certificate for connection"); 237936a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo try { 238036a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo ExchangeService.callback().syncMailboxStatus(mMailboxId, 238136a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo EmailServiceStatus.CLIENT_CERTIFICATE_ERROR, 0); 238236a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo } catch (RemoteException e1) { 238336a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo // Don't care if this fails. 238436a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo } 238536a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo return false; 238636a3e5257d0bb0be44479cea75a20c46b078db17Ben Komalo } 23873852792f1c79dcfca6dc9be67f11e4a53788a462Marc Blank 2388fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank // Set up our protocol version from the Account 23893852792f1c79dcfca6dc9be67f11e4a53788a462Marc Blank mProtocolVersion = mAccount.mProtocolVersion; 2390fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank // If it hasn't been set up, start with default version 2391fa088c04714957a4ebcd02a588e09878bbf4dbd4Marc Blank if (mProtocolVersion == null) { 2392d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank mProtocolVersion = Eas.DEFAULT_PROTOCOL_VERSION; 239354250c7c23b43b644961a8f241a3a3edcc9c796eMarc Blank } 2394cb5c48824628e93e98ca24edec46f05c54851af1Marc Blank mProtocolVersionDouble = Eas.getProtocolVersionDouble(mProtocolVersion); 23955a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo 23965a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo // Do checks to address historical policy sets. 23975a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo Policy policy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey); 239855c8432b7d7e183cffdcf441b71a04c2d301d96cBen Komalo if ((policy != null) && policy.mRequireEncryptionExternal) { 23995a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo // External storage encryption is not supported at this time. In a previous release, 24005a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo // prior to the system supporting true removable storage on Honeycomb, we accepted 24015a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo // this since we emulated external storage on partitions that could be encrypted. 24025a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo // If that was set before, we must clear it out now that the system supports true 24035a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo // removable storage (which can't be encrypted). 24045a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo resetSecurityPolicies(); 24055a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo } 240622e927cc109f3668ef3cf2fe46feef273c7ba53eMarc Blank return true; 24077ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank } 24087ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank 24095a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo /** 24105a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo * Clears out the security policies associated with the account, forcing a provision error 24115a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo * and a re-sync of the policy information for the account. 24125a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo */ 24135a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo private void resetSecurityPolicies() { 24145a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo ContentValues cv = new ContentValues(); 24155a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo cv.put(AccountColumns.SECURITY_FLAGS, 0); 24165a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo cv.putNull(AccountColumns.SECURITY_SYNC_KEY); 24175a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo long accountId = mAccount.mId; 24185a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo mContentResolver.update(ContentUris.withAppendedId( 24195a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo Account.CONTENT_URI, accountId), cv, null, null); 24205a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo SecurityPolicyDelegate.policiesRequired(mContext, accountId); 24215a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo } 24225a4d4bc3a4c5f03c0cd628ac000a9e8ac094d944Ben Komalo 2423e0ac5d26481e68c25b976136a6d376c39977e779Ben Komalo @Override 24247ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank public void run() { 24250164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank try { 24260164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // Make sure account and mailbox are still valid 24270164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank if (!setupService()) return; 24280164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // If we've been stopped, we're done 24290164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank if (mStop) return; 24300164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank if (mSyncReason >= ExchangeService.SYNC_CALLBACK_START) { 24310164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank try { 24320164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank ExchangeService.callback().syncMailboxStatus(mMailboxId, 24330164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank EmailServiceStatus.IN_PROGRESS, 0); 24340164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } catch (RemoteException e1) { 24350164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // Don't care if this fails 24360164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } 243716c1f8224d1334224f53d330bcb5d2ed0892fc2bMarc Blank } 2438fde52611eeae5ec6548f59c8e91e23bf65e6bd07Marc Blank 24390164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // Whether or not we're the account mailbox 24400164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank try { 24410164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank mDeviceId = ExchangeService.getDeviceId(mContext); 24420164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank int trafficFlags = TrafficFlags.getSyncFlags(mContext, mAccount); 24430164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank if ((mMailbox == null) || (mAccount == null)) { 24440164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank return; 24450164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } else if (mMailbox.mType == Mailbox.TYPE_EAS_ACCOUNT_MAILBOX) { 2446c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank TrafficStats.setThreadStatsTag(trafficFlags | TrafficFlags.DATA_EMAIL); 24470164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank runAccountMailbox(); 24480164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } else { 24490164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank AbstractSyncAdapter target; 24500164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank if (mMailbox.mType == Mailbox.TYPE_CONTACTS) { 24510164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank TrafficStats.setThreadStatsTag(trafficFlags | TrafficFlags.DATA_CONTACTS); 24520164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank target = new ContactsSyncAdapter( this); 24530164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } else if (mMailbox.mType == Mailbox.TYPE_CALENDAR) { 24540164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank TrafficStats.setThreadStatsTag(trafficFlags | TrafficFlags.DATA_CALENDAR); 24550164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank target = new CalendarSyncAdapter(this); 24560164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } else { 24570164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank TrafficStats.setThreadStatsTag(trafficFlags | TrafficFlags.DATA_EMAIL); 24580164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank target = new EmailSyncAdapter(this); 24590164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } 24600164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // We loop because someone might have put a request in while we were syncing 24610164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // and we've missed that opportunity... 24620164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank do { 24630164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank if (mRequestTime != 0) { 24640164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank userLog("Looping for user request..."); 24650164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank mRequestTime = 0; 24660164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } 24670164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank sync(target); 24680164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } while (mRequestTime != 0); 2469ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 24700164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } catch (EasAuthenticationException e) { 24710164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank userLog("Caught authentication error"); 24720164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank mExitStatus = EXIT_LOGIN_FAILURE; 24730164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } catch (IOException e) { 24740164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank String message = e.getMessage(); 24750164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank userLog("Caught IOException: ", (message == null) ? "No message" : message); 24760164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank mExitStatus = EXIT_IO_ERROR; 24770164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } catch (Exception e) { 24780164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank userLog("Uncaught exception in EasSyncService", e); 24790164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } finally { 24800164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank int status; 24810164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank 24820164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank if (!mStop) { 24830164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank userLog("Sync finished"); 24840164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank ExchangeService.done(this); 24850164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank switch (mExitStatus) { 24860164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank case EXIT_IO_ERROR: 24870164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank status = EmailServiceStatus.CONNECTION_ERROR; 24880164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank break; 24890164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank case EXIT_DONE: 24900164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank status = EmailServiceStatus.SUCCESS; 24910164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank ContentValues cv = new ContentValues(); 24920164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis()); 24930164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank String s = "S" + mSyncReason + ':' + status + ':' + mChangeCount; 24940164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank cv.put(Mailbox.SYNC_STATUS, s); 24950164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank mContentResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, 24960164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank mMailboxId), cv, null, null); 24970164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank break; 24980164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank case EXIT_LOGIN_FAILURE: 24990164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank status = EmailServiceStatus.LOGIN_FAILED; 25000164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank break; 25010164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank case EXIT_SECURITY_FAILURE: 25020164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank status = EmailServiceStatus.SECURITY_FAILURE; 25030164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // Ask for a new folder list. This should wake up the account mailbox; a 25040164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // security error in account mailbox should start provisioning 25050164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank ExchangeService.reloadFolderList(mContext, mAccount.mId, true); 25060164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank break; 25070164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank case EXIT_ACCESS_DENIED: 25080164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank status = EmailServiceStatus.ACCESS_DENIED; 25090164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank break; 25100164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank default: 25110164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank status = EmailServiceStatus.REMOTE_EXCEPTION; 25120164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank errorLog("Sync ended due to an exception."); 25130164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank break; 2514ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 25150164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } else { 25160164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank userLog("Stopped sync finished."); 25170164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank status = EmailServiceStatus.SUCCESS; 25185c5b1184a17ceee3b585d3e3eeba414a9b08fb19Marc Blank } 25192fc36123bfe4a3ee0f7d70052d49d08427c6d862Marc Blank 25200164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // Send a callback if this run was initiated by a service call 25210164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank if (mSyncReason >= ExchangeService.SYNC_CALLBACK_START) { 25220164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank try { 25230164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // Unless the user specifically asked for a sync, we don't want to report 25240164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // connection issues, as they are likely to be transient. In this case, we 25250164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // simply report success, so that the progress indicator terminates without 25260164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // putting up an error banner 25270164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank if (mSyncReason != ExchangeService.SYNC_UI_REQUEST && 25280164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank status == EmailServiceStatus.CONNECTION_ERROR) { 25290164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank status = EmailServiceStatus.SUCCESS; 25300164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } 25310164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank ExchangeService.callback().syncMailboxStatus(mMailboxId, status, 0); 25320164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } catch (RemoteException e1) { 25330164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // Don't care if this fails 2534733b1964dc192d57a856dd5b7c180afa9caa066eMarc Blank } 2535cd020abdfc6a60d041d62d8348e9c4ead7780bd4Marc Blank } 25369d4ac93efbba01afe668f9406feec69b3a2374ebMarc Blank 25370164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank // Make sure ExchangeService knows about this 25380164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank ExchangeService.kick("sync finished"); 25390164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } 25400164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank } catch (ProviderUnavailableException e) { 25410164f9da9a96fd8146c4a61e6de01734206baf4cMarc Blank Log.e(TAG, "EmailProvider unavailable; sync ended prematurely"); 25420b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo } 2543ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank } 2544ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank} 2545