Merge "Rename USER_QUERY -> LIVE_QUERY" into jb-mr1-dev
diff --git a/exchange2/AndroidManifest.xml b/exchange2/AndroidManifest.xml
index 7537b73..2f2bad2 100644
--- a/exchange2/AndroidManifest.xml
+++ b/exchange2/AndroidManifest.xml
@@ -147,6 +147,7 @@
android:authorities="com.android.exchange.directory.provider"
android:readPermission="android.permission.READ_CONTACTS"
android:multiprocess="false"
+ android:exported="true"
>
<meta-data
android:name="android.content.ContactDirectory"
diff --git a/exchange2/src/com/android/exchange/EasSyncService.java b/exchange2/src/com/android/exchange/EasSyncService.java
index 657bc84..d0360bc 100644
--- a/exchange2/src/com/android/exchange/EasSyncService.java
+++ b/exchange2/src/com/android/exchange/EasSyncService.java
@@ -1747,6 +1747,8 @@
}
if (emptyStream) {
+ // Make sure we get rid of updates/deletes
+ target.cleanup();
// If this happens, exit cleanly, and change the interval from push to ping
// if necessary
userLog("Empty sync response; finishing");
diff --git a/exchange2/src/com/android/exchange/adapter/CalendarSyncAdapter.java b/exchange2/src/com/android/exchange/adapter/CalendarSyncAdapter.java
index 6525610..45e8372 100644
--- a/exchange2/src/com/android/exchange/adapter/CalendarSyncAdapter.java
+++ b/exchange2/src/com/android/exchange/adapter/CalendarSyncAdapter.java
@@ -1721,6 +1721,10 @@
s.data(Tags.CALENDAR_BUSY_STATUS, Integer.toString(busyStatus));
// Meeting status, 0 = appointment, 1 = meeting, 3 = attendee
+ // In JB, organizer won't be an attendee
+ if (organizerEmail == null && entityValues.containsKey(Events.ORGANIZER)) {
+ organizerEmail = entityValues.getAsString(Events.ORGANIZER);
+ }
if (mEmailAddress.equalsIgnoreCase(organizerEmail)) {
s.data(Tags.CALENDAR_MEETING_STATUS, hasAttendees ? "1" : "0");
} else {
diff --git a/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java b/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java
index 87c96cb..c15c253 100644
--- a/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java
+++ b/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java
@@ -25,6 +25,7 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
+import android.os.TransactionTooLargeException;
import android.provider.CalendarContract.Events;
import android.text.Html;
import android.text.SpannedString;
@@ -1112,9 +1113,23 @@
@Override
public void commit() {
+ commitImpl(0);
+ }
+
+ public void commitImpl(int tryCount) {
// Use a batch operation to handle the changes
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+ // Maximum size of message text per fetch
+ int numFetched = fetchedEmails.size();
+ int maxPerFetch = 0;
+ if (numFetched > 0 && tryCount > 0) {
+ // Educated guess that 450000 chars (900k) is ok; 600k is a killer
+ // Remember that when fetching, we're not getting any other data
+ // We'll keep trying, reducing the maximum each time
+ // Realistically, this will rarely exceed 1, and probably never 2
+ maxPerFetch = 450000 / numFetched / tryCount;
+ }
for (Message msg: fetchedEmails) {
// Find the original message's id (by serverId and mailbox)
Cursor c = getServerIdCursor(msg.mServerId, EmailContent.ID_PROJECTION);
@@ -1139,6 +1154,10 @@
if (id != null) {
userLog("Fetched body successfully for ", id);
mBindArgument[0] = id;
+ if ((maxPerFetch > 0) && (msg.mText.length() > maxPerFetch)) {
+ userLog("Truncating message to " + maxPerFetch);
+ msg.mText = msg.mText.substring(0, maxPerFetch) + "...";
+ }
ops.add(ContentProviderOperation.newUpdate(Body.CONTENT_URI)
.withSelection(Body.MESSAGE_KEY + "=?", mBindArgument)
.withValue(Body.TEXT_CONTENT, msg.mText)
@@ -1193,7 +1212,10 @@
try {
mContentResolver.applyBatch(EmailContent.AUTHORITY, ops);
userLog(mMailbox.mDisplayName, " SyncKey saved as: ", mMailbox.mSyncKey);
- } catch (RemoteException e) {
+ } catch (TransactionTooLargeException e) {
+ Log.w(TAG, "Transaction failed on fetched message; retrying...");
+ commitImpl(++tryCount);
+ } catch (RemoteException e) {
// There is nothing to be done here; fail by returning null
} catch (OperationApplicationException e) {
// There is nothing to be done here; fail by returning null
diff --git a/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java b/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java
index 2c213a4..411c0f9 100644
--- a/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java
+++ b/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java
@@ -188,8 +188,13 @@
}
}
} else if (tag == Tags.FOLDER_SYNC_KEY) {
- mAccount.mSyncKey = getValue();
- userLog("New Account SyncKey: ", mAccount.mSyncKey);
+ String newKey = getValue();
+ if (!resetFolders) {
+ mAccount.mSyncKey = newKey;
+ userLog("New syncKey: ", newKey);
+ } else {
+ userLog("Ignoring new syncKey: ", newKey);
+ }
} else if (tag == Tags.FOLDER_CHANGES) {
if (mStatusOnly) return res;
changesParser(mOperations, mInitialSync);
diff --git a/exchange2/src/com/android/exchange/utility/CalendarUtilities.java b/exchange2/src/com/android/exchange/utility/CalendarUtilities.java
index a0ac47d..2872e80 100644
--- a/exchange2/src/com/android/exchange/utility/CalendarUtilities.java
+++ b/exchange2/src/com/android/exchange/utility/CalendarUtilities.java
@@ -107,17 +107,19 @@
// The following constants relate to Microsoft's TIME_ZONE_INFORMATION structure
// For documentation, see http://msdn.microsoft.com/en-us/library/ms725481(VS.85).aspx
+ static final int MSFT_TIME_ZONE_STRING_SIZE = 32;
+
static final int MSFT_TIME_ZONE_BIAS_OFFSET = 0;
static final int MSFT_TIME_ZONE_STANDARD_NAME_OFFSET =
MSFT_TIME_ZONE_BIAS_OFFSET + MSFT_LONG_SIZE;
static final int MSFT_TIME_ZONE_STANDARD_DATE_OFFSET =
- MSFT_TIME_ZONE_STANDARD_NAME_OFFSET + (MSFT_WCHAR_SIZE*32);
+ MSFT_TIME_ZONE_STANDARD_NAME_OFFSET + (MSFT_WCHAR_SIZE*MSFT_TIME_ZONE_STRING_SIZE);
static final int MSFT_TIME_ZONE_STANDARD_BIAS_OFFSET =
MSFT_TIME_ZONE_STANDARD_DATE_OFFSET + MSFT_SYSTEMTIME_SIZE;
static final int MSFT_TIME_ZONE_DAYLIGHT_NAME_OFFSET =
MSFT_TIME_ZONE_STANDARD_BIAS_OFFSET + MSFT_LONG_SIZE;
static final int MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET =
- MSFT_TIME_ZONE_DAYLIGHT_NAME_OFFSET + (MSFT_WCHAR_SIZE*32);
+ MSFT_TIME_ZONE_DAYLIGHT_NAME_OFFSET + (MSFT_WCHAR_SIZE*MSFT_TIME_ZONE_STRING_SIZE);
static final int MSFT_TIME_ZONE_DAYLIGHT_BIAS_OFFSET =
MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET + MSFT_SYSTEMTIME_SIZE;
static final int MSFT_TIME_ZONE_SIZE =
@@ -216,6 +218,19 @@
bytes[offset] = (byte) ((value >> 8) & 0xFF);
}
+ static String getString(byte[] bytes, int offset, int size) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < size; i++) {
+ int ch = bytes[offset + i];
+ if (ch == 0) {
+ break;
+ } else {
+ sb.append((char)ch);
+ }
+ }
+ return sb.toString();
+ }
+
// Internal structure for storing a time zone date from a SYSTEMTIME structure
// This date represents either the start or the end time for DST
static class TimeZoneDate {
@@ -861,19 +876,34 @@
if (dstSavings != timeZone.getDSTSavings()) continue;
return timeZone;
}
- // In this case, there is no daylight savings time, so the only interesting data
- // is the offset, and we know that all of the zoneId's match; we'll take the first
boolean lenient = false;
+ boolean name = false;
if ((dstStart.hour != dstEnd.hour) && (precision == STANDARD_DST_PRECISION)) {
timeZone = tziStringToTimeZoneImpl(timeZoneString, LENIENT_DST_PRECISION);
lenient = true;
} else {
- timeZone = TimeZone.getTimeZone(zoneIds[0]);
+ // We can't find a time zone match, so our last attempt is to see if there's
+ // a valid time zone name in the TZI; if not we'll just take the first TZ with
+ // a matching offset (which is likely wrong, but ... what else is there to do)
+ String tzName = getString(timeZoneBytes, MSFT_TIME_ZONE_STANDARD_NAME_OFFSET,
+ MSFT_TIME_ZONE_STRING_SIZE);
+ if (!tzName.isEmpty()) {
+ TimeZone tz = TimeZone.getTimeZone(tzName);
+ if (tz != null) {
+ timeZone = tz;
+ name = true;
+ } else {
+ timeZone = TimeZone.getTimeZone(zoneIds[0]);
+ }
+ } else {
+ timeZone = TimeZone.getTimeZone(zoneIds[0]);
+ }
}
if (Eas.USER_LOG) {
ExchangeService.log(TAG,
"No TimeZone with correct DST settings; using " +
- (lenient ? "lenient" : "first") + ": " + timeZone.getID());
+ (name ? "name" : (lenient ? "lenient" : "first")) + ": " +
+ timeZone.getID());
}
return timeZone;
}
diff --git a/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java b/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java
index 0ecdb8f..77820a5 100644
--- a/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java
+++ b/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java
@@ -110,6 +110,13 @@
"AAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAAAxP///w==";
+ // This string specifies "Europe/London" in the name, but otherwise is somewhat bogus
+ // in that it has unknown time zone dates with a 0 bias (GMT). (From a Zimbra server user)
+ private static final String EUROPE_LONDON_TIME_BY_NAME =
+ "AAAAAEV1cm9wZS9Mb25kb24AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAoAAQAFAAIAAAAAAAAAAAAAAEV1cm9wZS9Mb25kb24AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAQAFAAEAAAAAAAAAxP///w==";
+
private static final String ORGANIZER = "organizer@server.com";
private static final String ATTENDEE = "attendee@server.com";
@@ -140,6 +147,9 @@
tz = CalendarUtilities.tziStringToTimeZone(AUSTRALIA_ACT_TIME);
assertEquals("Australia/ACT", tz.getID());
+ tz = CalendarUtilities.tziStringToTimeZone(EUROPE_LONDON_TIME_BY_NAME);
+ assertEquals("Europe/London", tz.getID());
+
// Test peculiar MS sent EST data with and without lenient precision; send standard
// precision + 1 (i.e. 1ms) to make sure the code doesn't automatically flip to lenient
// when the tz isn't found