Home | History | Annotate | Download | only in chromeos
      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