summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java205
-rw-r--r--packages/SettingsLib/tests/AndroidManifest.xml1
-rw-r--r--packages/SettingsLib/tests/src/com/android/settingslib/utils/ZoneGetterTest.java68
3 files changed, 190 insertions, 84 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 26e8303d2bb8..857ca49eafc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -40,23 +40,47 @@ import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
+/**
+ * ZoneGetter is the utility class to get time zone and zone list, and both of them have display
+ * name in time zone. In this class, we will keep consistency about display names for all
+ * the methods.
+ *
+ * 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"
+ * (e.g. "Europe/London" -> "British Summer Time" for people in the UK). For "non-local"
+ * zones we prefer the exemplar location (e.g. "Europe/London" -> "London" for English
+ * speakers from outside the UK). This heuristic is based on the fact that people are
+ * typically familiar with their local timezones and exemplar locations don't always match
+ * modern-day expectations for people living in the country covered. Large countries like
+ * China that mostly use a single timezone (olson id: "Asia/Shanghai") may not live near
+ * "Shanghai" and prefer the long name over the exemplar location. The only time we don't
+ * follow this policy for local zones is when Android supplies multiple olson IDs to choose
+ * from and the use of a zone's long name leads to ambiguity. For example, at the time of
+ * writing Android lists 5 olson ids for Australia which collapse to 2 different zone names
+ * in winter but 4 different zone names in summer. The ambiguity leads to the users
+ * selecting the wrong olson ids.
+ *
+ */
public class ZoneGetter {
private static final String TAG = "ZoneGetter";
- private static final String XMLTAG_TIMEZONE = "timezone";
-
public static final String KEY_ID = "id"; // value: String
public static final String KEY_DISPLAYNAME = "name"; // value: String
public static final String KEY_GMT = "gmt"; // value: String
public static final String KEY_OFFSET = "offset"; // value: int (Integer)
- private ZoneGetter() {}
+ private static final String XMLTAG_TIMEZONE = "timezone";
- public static String getTimeZoneOffsetAndName(TimeZone tz, Date now) {
- Locale locale = Locale.getDefault();
- String gmtString = getGmtOffsetString(locale, tz, now);
- TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
- String zoneNameString = getZoneLongName(timeZoneNames, tz, now);
+ public static String getTimeZoneOffsetAndName(Context context, TimeZone tz, Date now) {
+ final Locale locale = Locale.getDefault();
+ final String gmtString = getGmtOffsetString(locale, tz, now);
+ final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
+ final ZoneGetterData data = new ZoneGetterData(context);
+
+ final boolean useExemplarLocationForLocalNames =
+ shouldUseExemplarLocationForLocalNames(data, timeZoneNames);
+ final String zoneNameString = getTimeZoneDisplayName(data, timeZoneNames,
+ useExemplarLocationForLocalNames, tz, tz.getID());
if (zoneNameString == null) {
return gmtString;
}
@@ -69,82 +93,20 @@ public class ZoneGetter {
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"
- // (e.g. "Europe/London" -> "British Summer Time" for people in the UK). For "non-local"
- // zones we prefer the exemplar location (e.g. "Europe/London" -> "London" for English
- // speakers from outside the UK). This heuristic is based on the fact that people are
- // typically familiar with their local timezones and exemplar locations don't always match
- // modern-day expectations for people living in the country covered. Large countries like
- // China that mostly use a single timezone (olson id: "Asia/Shanghai") may not live near
- // "Shanghai" and prefer the long name over the exemplar location. The only time we don't
- // follow this policy for local zones is when Android supplies multiple olson IDs to choose
- // from and the use of a zone's long name leads to ambiguity. For example, at the time of
- // writing Android lists 5 olson ids for Australia which collapse to 2 different zone names
- // in winter but 4 different zone names in summer. The ambiguity leads to the users
- // selecting the wrong olson ids.
-
- // Get the list of olson ids to display to the user.
- 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 HashSet<String>();
- for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) {
- localZoneIds.add(olsonId);
- }
+ final ZoneGetterData data = new ZoneGetterData(context);
// 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 = timeZones[i];
- String displayName = getZoneLongName(timeZoneNames, tz, now);
- if (displayName == null) {
- displayName = gmtOffsetStrings[i];
- }
- boolean nameIsUnique = localZoneNames.add(displayName);
- if (!nameIsUnique) {
- useExemplarLocationForLocalNames = true;
- break;
- }
- }
- }
+ final boolean useExemplarLocationForLocalNames =
+ shouldUseExemplarLocationForLocalNames(data, timeZoneNames);
// Generate the list of zone entries to return.
List<Map<String, Object>> zones = new ArrayList<Map<String, Object>>();
- for (int i = 0; i < zoneCount; i++) {
- String olsonId = olsonIdsToDisplay[i];
- TimeZone tz = timeZones[i];
- String gmtOffsetString = gmtOffsetStrings[i];
-
- 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);
- }
- }
+ for (int i = 0; i < data.zoneCount; i++) {
+ TimeZone tz = data.timeZones[i];
+ String gmtOffsetString = data.gmtOffsetStrings[i];
+
+ String displayName = getTimeZoneDisplayName(data, timeZoneNames,
+ useExemplarLocationForLocalNames, tz, data.olsonIdsToDisplay[i]);
if (displayName == null || displayName.isEmpty()) {
displayName = gmtOffsetString;
}
@@ -198,28 +160,103 @@ public class ZoneGetter {
return olsonIds;
}
+ private static boolean shouldUseExemplarLocationForLocalNames(ZoneGetterData data,
+ TimeZoneNames timeZoneNames) {
+ final Set<String> localZoneNames = new HashSet<String>();
+ final Date now = new Date();
+ for (int i = 0; i < data.zoneCount; i++) {
+ final String olsonId = data.olsonIdsToDisplay[i];
+ if (data.localZoneIds.contains(olsonId)) {
+ final TimeZone tz = data.timeZones[i];
+ String displayName = getZoneLongName(timeZoneNames, tz, now);
+ if (displayName == null) {
+ displayName = data.gmtOffsetStrings[i];
+ }
+ final boolean nameIsUnique = localZoneNames.add(displayName);
+ if (!nameIsUnique) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static String getTimeZoneDisplayName(ZoneGetterData data, TimeZoneNames timeZoneNames,
+ boolean useExemplarLocationForLocalNames, TimeZone tz, String olsonId) {
+ final Date now = new Date();
+ final boolean isLocalZoneId = data.localZoneIds.contains(olsonId);
+ final 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);
+ }
+ }
+
+ return displayName;
+ }
+
/**
* 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 =
+ final TimeZoneNames.NameType nameType =
tz.inDaylightTime(now) ? TimeZoneNames.NameType.LONG_DAYLIGHT
- : TimeZoneNames.NameType.LONG_STANDARD;
+ : TimeZoneNames.NameType.LONG_STANDARD;
return names.getDisplayName(tz.getID(), nameType, now.getTime());
}
private static String getGmtOffsetString(Locale locale, TimeZone tz, Date now) {
// Use SimpleDateFormat to format the GMT+00:00 string.
- SimpleDateFormat gmtFormatter = new SimpleDateFormat("ZZZZ");
+ final SimpleDateFormat gmtFormatter = new SimpleDateFormat("ZZZZ");
gmtFormatter.setTimeZone(tz);
String gmtString = gmtFormatter.format(now);
// Ensure that the "GMT+" stays with the "00:00" even if the digits are RTL.
- BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
boolean isRtl = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
gmtString = bidiFormatter.unicodeWrap(gmtString,
isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR);
return gmtString;
}
-}
+
+ private static final class ZoneGetterData {
+ public final String[] olsonIdsToDisplay;
+ public final String[] gmtOffsetStrings;
+ public final TimeZone[] timeZones;
+ public final Set<String> localZoneIds;
+ public final int zoneCount;
+
+ public ZoneGetterData(Context context) {
+ final Locale locale = Locale.getDefault();
+ final Date now = new Date();
+ final List<String> olsonIdsToDisplayList = readTimezonesToDisplay(context);
+
+ // Load all the data needed to display time zones
+ zoneCount = olsonIdsToDisplayList.size();
+ olsonIdsToDisplay = new String[zoneCount];
+ timeZones = new TimeZone[zoneCount];
+ gmtOffsetStrings = new String[zoneCount];
+ for (int i = 0; i < zoneCount; i++) {
+ final String olsonId = olsonIdsToDisplayList.get(i);
+ olsonIdsToDisplay[i] = olsonId;
+ final TimeZone tz = TimeZone.getTimeZone(olsonId);
+ timeZones[i] = tz;
+ gmtOffsetStrings[i] = getGmtOffsetString(locale, tz, now);
+ }
+
+ // Create a lookup of local zone IDs.
+ localZoneIds = new HashSet<String>();
+ for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) {
+ localZoneIds.add(olsonId);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/tests/AndroidManifest.xml b/packages/SettingsLib/tests/AndroidManifest.xml
index 18bbbed32011..9fd5a4108a43 100644
--- a/packages/SettingsLib/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
+ <uses-permission android:name="android.permission.SET_TIME_ZONE" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/utils/ZoneGetterTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/utils/ZoneGetterTest.java
new file mode 100644
index 000000000000..57e06ddb5c04
--- /dev/null
+++ b/packages/SettingsLib/tests/src/com/android/settingslib/utils/ZoneGetterTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.settingslib.utils;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.*;
+import com.android.settingslib.datetime.ZoneGetter;
+
+import static junit.framework.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ZoneGetterTest {
+ private static final String TIME_ZONE_LONDON_ID = "Europe/London";
+ private static final String TIME_ZONE_LA_ID = "America/Los_Angeles";
+ private Locale mLocaleEnUs;
+ private Calendar mCalendar;
+
+ @Before
+ public void setUp() {
+ mLocaleEnUs = new Locale("en", "us");
+ Locale.setDefault(mLocaleEnUs);
+ mCalendar = new GregorianCalendar(2016, 9, 1);
+ }
+
+ @Test
+ public void getTimeZoneOffsetAndName_setLondon_returnLondon() {
+ // Check it will ends with 'London', not 'British Summer Time' or sth else
+ testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "London");
+ }
+
+ @Test
+ public void getTimeZoneOffsetAndName_setLosAngeles_returnPacificDaylightTime() {
+ // Check it will ends with 'Pacific Daylight Time', not 'Los_Angeles'
+ testTimeZoneOffsetAndNameInner(TIME_ZONE_LA_ID, "Pacific Daylight Time");
+ }
+
+ private void testTimeZoneOffsetAndNameInner(String timeZoneId, String expectedName) {
+ final Context context = InstrumentationRegistry.getContext();
+ final TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
+
+ String timeZoneString = ZoneGetter.getTimeZoneOffsetAndName(context, timeZone,
+ mCalendar.getTime());
+
+ assertTrue(timeZoneString.endsWith(expectedName));
+ }
+
+}