cherrypick: Try to use names from time zone strings if all else fails...

Fixes b/7232823 Use time zone name when all else fails...

Change-Id: Idc2633041641db43559d32185c6bd0e2a90f4765
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