Merge "Fix for Settings timezone names when boot time is wrong"
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 7b5bfb5..26e8303 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.XmlResourceParser;
+import android.icu.text.TimeZoneNames;
 import android.text.BidiFormatter;
 import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
@@ -26,20 +27,18 @@
 
 import com.android.settingslib.R;
 
-import libcore.icu.TimeZoneNames;
-
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
-import java.util.TreeSet;
 
 public class ZoneGetter {
     private static final String TAG = "ZoneGetter";
@@ -56,7 +55,8 @@
     public static String getTimeZoneOffsetAndName(TimeZone tz, Date now) {
         Locale locale = Locale.getDefault();
         String gmtString = getGmtOffsetString(locale, tz, now);
-        String zoneNameString = getZoneLongName(locale, tz, now);
+        TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
+        String zoneNameString = getZoneLongName(timeZoneNames, tz, now);
         if (zoneNameString == null) {
             return gmtString;
         }
@@ -68,6 +68,7 @@
     public static List<Map<String, Object>> getZonesList(Context context) {
         final Locale locale = Locale.getDefault();
         final Date now = new Date();
+        final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
 
         // The display name chosen for each zone entry depends on whether the zone is one associated
         // with the country of the user's chosen locale. For "local" zones we prefer the "long name"
@@ -85,25 +86,41 @@
         // selecting the wrong olson ids.
 
         // Get the list of olson ids to display to the user.
-        List<String> olsonIdsToDisplay = readTimezonesToDisplay(context);
+        List<String> olsonIdsToDisplayList = readTimezonesToDisplay(context);
+
+        // Store the information we are going to need more than once.
+        final int zoneCount = olsonIdsToDisplayList.size();
+        final String[] olsonIdsToDisplay = new String[zoneCount];
+        final TimeZone[] timeZones = new TimeZone[zoneCount];
+        final String[] gmtOffsetStrings = new String[zoneCount];
+        for (int i = 0; i < zoneCount; i++) {
+            String olsonId = olsonIdsToDisplayList.get(i);
+            olsonIdsToDisplay[i] = olsonId;
+            TimeZone tz = TimeZone.getTimeZone(olsonId);
+            timeZones[i] = tz;
+            gmtOffsetStrings[i] = getGmtOffsetString(locale, tz, now);
+        }
 
         // Create a lookup of local zone IDs.
-        Set<String> localZoneIds = new TreeSet<String>();
-        for (String olsonId : TimeZoneNames.forLocale(locale)) {
+        Set<String> localZoneIds = new HashSet<String>();
+        for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) {
             localZoneIds.add(olsonId);
         }
 
-        // Work out whether the long names for the local entries that we would show by default would
-        // be ambiguous.
-        Set<String> localZoneNames = new TreeSet<String>();
-        boolean localLongNamesAreAmbiguous = false;
-        for (String olsonId : olsonIdsToDisplay) {
+        // Work out whether the display names we would show by default would be ambiguous.
+        Set<String> localZoneNames = new HashSet<String>();
+        boolean useExemplarLocationForLocalNames = false;
+        for (int i = 0; i < zoneCount; i++) {
+            String olsonId = olsonIdsToDisplay[i];
             if (localZoneIds.contains(olsonId)) {
-                TimeZone tz = TimeZone.getTimeZone(olsonId);
-                String zoneLongName = getZoneLongName(locale, tz, now);
-                boolean longNameIsUnique = localZoneNames.add(zoneLongName);
-                if (!longNameIsUnique) {
-                    localLongNamesAreAmbiguous = true;
+                TimeZone tz = timeZones[i];
+                String displayName = getZoneLongName(timeZoneNames, tz, now);
+                if (displayName == null) {
+                    displayName = gmtOffsetStrings[i];
+                }
+                boolean nameIsUnique = localZoneNames.add(displayName);
+                if (!nameIsUnique) {
+                    useExemplarLocationForLocalNames = true;
                     break;
                 }
             }
@@ -111,15 +128,27 @@
 
         // Generate the list of zone entries to return.
         List<Map<String, Object>> zones = new ArrayList<Map<String, Object>>();
-        for (String olsonId : olsonIdsToDisplay) {
-            final TimeZone tz = TimeZone.getTimeZone(olsonId);
-            // Exemplar location display is the default. The only time we intend to display the long
-            // name is when the olsonId is local AND long names are not ambiguous.
-            boolean isLocalZoneId = localZoneIds.contains(olsonId);
-            boolean preferLongName = isLocalZoneId && !localLongNamesAreAmbiguous;
-            String displayName = getZoneDisplayName(locale, tz, now, preferLongName);
+        for (int i = 0; i < zoneCount; i++) {
+            String olsonId = olsonIdsToDisplay[i];
+            TimeZone tz = timeZones[i];
+            String gmtOffsetString = gmtOffsetStrings[i];
 
-            String gmtOffsetString = getGmtOffsetString(locale, tz, now);
+            boolean isLocalZoneId = localZoneIds.contains(olsonId);
+            boolean preferLongName = isLocalZoneId && !useExemplarLocationForLocalNames;
+            String displayName;
+            if (preferLongName) {
+                displayName = getZoneLongName(timeZoneNames, tz, now);
+            } else {
+                displayName = timeZoneNames.getExemplarLocationName(tz.getID());
+                if (displayName == null || displayName.isEmpty()) {
+                    // getZoneExemplarLocation can return null. Fall back to the long name.
+                    displayName = getZoneLongName(timeZoneNames, tz, now);
+                }
+            }
+            if (displayName == null  || displayName.isEmpty()) {
+                displayName = gmtOffsetString;
+            }
+
             int offsetMillis = tz.getOffset(now.getTime());
             Map<String, Object> displayEntry =
                     createDisplayEntry(tz, gmtOffsetString, displayName, offsetMillis);
@@ -138,30 +167,6 @@
         return map;
     }
 
-    /**
-     * Returns a name for the specific zone. If {@code preferLongName} is {@code true} then the
-     * long display name for the timezone will be used, otherwise the exemplar location will be
-     * preferred.
-     */
-    private static String getZoneDisplayName(Locale locale, TimeZone tz, Date now,
-            boolean preferLongName) {
-        String zoneNameString;
-        if (preferLongName) {
-            zoneNameString = getZoneLongName(locale, tz, now);
-        } else {
-            zoneNameString = getZoneExemplarLocation(locale, tz);
-            if (zoneNameString == null || zoneNameString.isEmpty()) {
-                // getZoneExemplarLocation can return null.
-                zoneNameString = getZoneLongName(locale, tz, now);
-            }
-        }
-        return zoneNameString;
-    }
-
-    private static String getZoneExemplarLocation(Locale locale, TimeZone tz) {
-        return TimeZoneNames.getExemplarLocation(locale.toString(), tz.getID());
-    }
-
     private static List<String> readTimezonesToDisplay(Context context) {
         List<String> olsonIds = new ArrayList<String>();
         try (XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones)) {
@@ -193,10 +198,15 @@
         return olsonIds;
     }
 
-    private static String getZoneLongName(Locale locale, TimeZone tz, Date now) {
-        boolean daylight = tz.inDaylightTime(now);
-        // This returns a name if it can, or will fall back to GMT+0X:00 format.
-        return tz.getDisplayName(daylight, TimeZone.LONG, locale);
+    /**
+     * Returns the long name for the timezone for the given locale at the time specified.
+     * Can return {@code null}.
+     */
+    private static String getZoneLongName(TimeZoneNames names, TimeZone tz, Date now) {
+        TimeZoneNames.NameType nameType =
+                tz.inDaylightTime(now) ? TimeZoneNames.NameType.LONG_DAYLIGHT
+                : TimeZoneNames.NameType.LONG_STANDARD;
+        return names.getDisplayName(tz.getID(), nameType, now.getTime());
     }
 
     private static String getGmtOffsetString(Locale locale, TimeZone tz, Date now) {