1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/webui/options/chromeos/system_settings_provider.h" 6 7 #include <string> 8 9 #include "base/i18n/rtl.h" 10 #include "base/memory/scoped_ptr.h" 11 #include "base/stl_util-inl.h" 12 #include "base/string_util.h" 13 #include "base/stringprintf.h" 14 #include "base/synchronization/lock.h" 15 #include "base/time.h" 16 #include "base/utf_string_conversions.h" 17 #include "base/values.h" 18 #include "chrome/browser/chromeos/cros/cros_library.h" 19 #include "chrome/browser/chromeos/cros_settings.h" 20 #include "chrome/browser/chromeos/cros_settings_names.h" 21 #include "chrome/browser/chromeos/login/user_manager.h" 22 #include "grit/generated_resources.h" 23 #include "ui/base/l10n/l10n_util.h" 24 #include "unicode/calendar.h" 25 #include "unicode/timezone.h" 26 #include "unicode/ures.h" 27 28 namespace { 29 30 // TODO(jungshik): Using Enumerate method in ICU gives 600+ timezones. 31 // Even after filtering out duplicate entries with a strict identity check, 32 // we still have 400+ zones. Relaxing the criteria for the timezone 33 // identity is likely to cut down the number to < 100. Until we 34 // come up with a better list, we hard-code the following list as used by 35 // Android. 36 static const char* kTimeZones[] = { 37 "Pacific/Majuro", 38 "Pacific/Midway", 39 "Pacific/Honolulu", 40 "America/Anchorage", 41 "America/Los_Angeles", 42 "America/Tijuana", 43 "America/Denver", 44 "America/Phoenix", 45 "America/Chihuahua", 46 "America/Chicago", 47 "America/Mexico_City", 48 "America/Costa_Rica", 49 "America/Regina", 50 "America/New_York", 51 "America/Bogota", 52 "America/Caracas", 53 "America/Barbados", 54 "America/Manaus", 55 "America/Santiago", 56 "America/St_Johns", 57 "America/Sao_Paulo", 58 "America/Araguaina", 59 "America/Argentina/Buenos_Aires", 60 "America/Godthab", 61 "America/Montevideo", 62 "Atlantic/South_Georgia", 63 "Atlantic/Azores", 64 "Atlantic/Cape_Verde", 65 "Africa/Casablanca", 66 "Europe/London", 67 "Europe/Amsterdam", 68 "Europe/Belgrade", 69 "Europe/Brussels", 70 "Europe/Sarajevo", 71 "Africa/Windhoek", 72 "Africa/Brazzaville", 73 "Asia/Amman", 74 "Europe/Athens", 75 "Asia/Beirut", 76 "Africa/Cairo", 77 "Europe/Helsinki", 78 "Asia/Jerusalem", 79 "Europe/Minsk", 80 "Africa/Harare", 81 "Asia/Baghdad", 82 "Europe/Moscow", 83 "Asia/Kuwait", 84 "Africa/Nairobi", 85 "Asia/Tehran", 86 "Asia/Baku", 87 "Asia/Tbilisi", 88 "Asia/Yerevan", 89 "Asia/Dubai", 90 "Asia/Kabul", 91 "Asia/Karachi", 92 "Asia/Oral", 93 "Asia/Yekaterinburg", 94 "Asia/Calcutta", 95 "Asia/Colombo", 96 "Asia/Katmandu", 97 "Asia/Almaty", 98 "Asia/Rangoon", 99 "Asia/Krasnoyarsk", 100 "Asia/Bangkok", 101 "Asia/Shanghai", 102 "Asia/Hong_Kong", 103 "Asia/Irkutsk", 104 "Asia/Kuala_Lumpur", 105 "Australia/Perth", 106 "Asia/Taipei", 107 "Asia/Seoul", 108 "Asia/Tokyo", 109 "Asia/Yakutsk", 110 "Australia/Adelaide", 111 "Australia/Darwin", 112 "Australia/Brisbane", 113 "Australia/Hobart", 114 "Australia/Sydney", 115 "Asia/Vladivostok", 116 "Pacific/Guam", 117 "Asia/Magadan", 118 "Pacific/Auckland", 119 "Pacific/Fiji", 120 "Pacific/Tongatapu", 121 }; 122 123 static base::Lock timezone_bundle_lock; 124 125 struct UResClose { 126 inline void operator() (UResourceBundle* b) const { 127 ures_close(b); 128 } 129 }; 130 131 string16 GetExemplarCity(const icu::TimeZone& zone) { 132 // TODO(jungshik): After upgrading to ICU 4.6, use U_ICUDATA_ZONE 133 static const char* zone_bundle_name = NULL; 134 135 // These will be leaked at the end. 136 static UResourceBundle *zone_bundle = NULL; 137 static UResourceBundle *zone_strings = NULL; 138 139 UErrorCode status = U_ZERO_ERROR; 140 { 141 base::AutoLock lock(timezone_bundle_lock); 142 if (zone_bundle == NULL) 143 zone_bundle = ures_open(zone_bundle_name, uloc_getDefault(), &status); 144 145 if (zone_strings == NULL) 146 zone_strings = ures_getByKey(zone_bundle, "zone_strings", NULL, &status); 147 } 148 149 icu::UnicodeString zone_id; 150 zone.getID(zone_id); 151 std::string zone_id_str; 152 zone_id.toUTF8String(zone_id_str); 153 154 // resource keys for timezones use ':' in place of '/'. 155 ReplaceSubstringsAfterOffset(&zone_id_str, 0, "/", ":"); 156 scoped_ptr_malloc<UResourceBundle, UResClose> zone_item( 157 ures_getByKey(zone_strings, zone_id_str.c_str(), NULL, &status)); 158 icu::UnicodeString city; 159 if (U_FAILURE(status)) 160 goto fallback; 161 city = icu::ures_getUnicodeStringByKey(zone_item.get(), "ec", &status); 162 if (U_SUCCESS(status)) 163 return string16(city.getBuffer(), city.length()); 164 165 fallback: 166 ReplaceSubstringsAfterOffset(&zone_id_str, 0, ":", "/"); 167 // Take the last component of a timezone id (e.g. 'Baz' in 'Foo/Bar/Baz'). 168 // Depending on timezones, keeping all but the 1st component 169 // (e.g. Bar/Baz) may be better, but our current list does not have 170 // any timezone for which that's the case. 171 std::string::size_type slash_pos = zone_id_str.rfind('/'); 172 if (slash_pos != std::string::npos && slash_pos < zone_id_str.size()) 173 zone_id_str.erase(0, slash_pos + 1); 174 // zone id has '_' in place of ' '. 175 ReplaceSubstringsAfterOffset(&zone_id_str, 0, "_", " "); 176 return ASCIIToUTF16(zone_id_str); 177 } 178 179 } // namespace anonymous 180 181 namespace chromeos { 182 183 SystemSettingsProvider::SystemSettingsProvider() { 184 for (size_t i = 0; i < arraysize(kTimeZones); i++) { 185 timezones_.push_back(icu::TimeZone::createTimeZone( 186 icu::UnicodeString(kTimeZones[i], -1, US_INV))); 187 } 188 SystemAccess::GetInstance()->AddObserver(this); 189 190 } 191 192 SystemSettingsProvider::~SystemSettingsProvider() { 193 SystemAccess::GetInstance()->RemoveObserver(this); 194 STLDeleteElements(&timezones_); 195 } 196 197 void SystemSettingsProvider::DoSet(const std::string& path, Value* in_value) { 198 // Only the owner can change the time zone. 199 if (!UserManager::Get()->current_user_is_owner()) 200 return; 201 202 if (path == kSystemTimezone) { 203 string16 value; 204 if (!in_value || !in_value->IsType(Value::TYPE_STRING) || 205 !in_value->GetAsString(&value)) 206 return; 207 const icu::TimeZone* timezone = GetTimezone(value); 208 if (!timezone) 209 return; 210 SystemAccess::GetInstance()->SetTimezone(*timezone); 211 } 212 } 213 214 bool SystemSettingsProvider::Get(const std::string& path, 215 Value** out_value) const { 216 if (path == kSystemTimezone) { 217 *out_value = Value::CreateStringValue(GetKnownTimezoneID( 218 SystemAccess::GetInstance()->GetTimezone())); 219 return true; 220 } 221 return false; 222 } 223 224 bool SystemSettingsProvider::HandlesSetting(const std::string& path) { 225 return ::StartsWithASCII(path, std::string("cros.system."), true); 226 } 227 228 void SystemSettingsProvider::TimezoneChanged(const icu::TimeZone& timezone) { 229 // Fires system setting change notification. 230 CrosSettings::Get()->FireObservers(kSystemTimezone); 231 } 232 233 ListValue* SystemSettingsProvider::GetTimezoneList() { 234 ListValue* timezoneList = new ListValue(); 235 for (std::vector<icu::TimeZone*>::iterator iter = timezones_.begin(); 236 iter != timezones_.end(); ++iter) { 237 const icu::TimeZone* timezone = *iter; 238 ListValue* option = new ListValue(); 239 option->Append(Value::CreateStringValue(GetTimezoneID(*timezone))); 240 option->Append(Value::CreateStringValue(GetTimezoneName(*timezone))); 241 timezoneList->Append(option); 242 } 243 return timezoneList; 244 } 245 246 string16 SystemSettingsProvider::GetTimezoneName( 247 const icu::TimeZone& timezone) { 248 // Instead of using the raw_offset, use the offset in effect now. 249 // For instance, US Pacific Time, the offset shown will be -7 in summer 250 // while it'll be -8 in winter. 251 int raw_offset, dst_offset; 252 UDate now = icu::Calendar::getNow(); 253 UErrorCode status = U_ZERO_ERROR; 254 timezone.getOffset(now, false, raw_offset, dst_offset, status); 255 DCHECK(U_SUCCESS(status)); 256 int offset = raw_offset + dst_offset; 257 // offset is in msec. 258 int minute_offset = std::abs(offset) / 60000; 259 int hour_offset = minute_offset / 60; 260 int min_remainder = minute_offset % 60; 261 // Some timezones have a non-integral hour offset. So, we need to 262 // use hh:mm form. 263 std::string offset_str = base::StringPrintf(offset >= 0 ? 264 "UTC+%d:%02d" : "UTC-%d:%02d", hour_offset, min_remainder); 265 266 // TODO(jungshik): When coming up with a better list of timezones, we also 267 // have to come up with better 'display' names. One possibility is to list 268 // multiple cities (e.g. "Los Angeles, Vancouver .." in the order of 269 // the population of a country the city belongs to.). 270 // We can also think of using LONG_GENERIC or LOCATION once we upgrade 271 // to ICU 4.6. 272 // In the meantime, we use "LONG" name with "Exemplar City" to distinguish 273 // multiple timezones with the same "LONG" name but with different 274 // rules (e.g. US Mountain Time in Denver vs Phoenix). 275 icu::UnicodeString name; 276 timezone.getDisplayName(dst_offset != 0, icu::TimeZone::LONG, name); 277 string16 result(l10n_util::GetStringFUTF16( 278 IDS_OPTIONS_SETTINGS_TIMEZONE_DISPLAY_TEMPLATE, ASCIIToUTF16(offset_str), 279 string16(name.getBuffer(), name.length()), GetExemplarCity(timezone))); 280 base::i18n::AdjustStringForLocaleDirection(&result); 281 return result; 282 } 283 284 string16 SystemSettingsProvider::GetTimezoneID( 285 const icu::TimeZone& timezone) { 286 icu::UnicodeString id; 287 timezone.getID(id); 288 return string16(id.getBuffer(), id.length()); 289 } 290 291 const icu::TimeZone* SystemSettingsProvider::GetTimezone( 292 const string16& timezone_id) { 293 for (std::vector<icu::TimeZone*>::iterator iter = timezones_.begin(); 294 iter != timezones_.end(); ++iter) { 295 const icu::TimeZone* timezone = *iter; 296 if (GetTimezoneID(*timezone) == timezone_id) { 297 return timezone; 298 } 299 } 300 return NULL; 301 } 302 303 string16 SystemSettingsProvider::GetKnownTimezoneID( 304 const icu::TimeZone& timezone) const { 305 for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin(); 306 iter != timezones_.end(); ++iter) { 307 const icu::TimeZone* known_timezone = *iter; 308 if (known_timezone->hasSameRules(timezone)) 309 return GetTimezoneID(*known_timezone); 310 } 311 312 // Not able to find a matching timezone in our list. 313 return string16(); 314 } 315 316 } // namespace chromeos 317