michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "base/message_loop.h" michael@0: #include "jsapi.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/ClearOnShutdown.h" michael@0: #include "mozilla/Hal.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDebug.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsISettingsService.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "TimeZoneSettingObserver.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsPrintfCString.h" michael@0: michael@0: #undef LOG michael@0: #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Time Zone Setting" , ## args) michael@0: #define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "Time Zone Setting" , ## args) michael@0: michael@0: #define TIME_TIMEZONE "time.timezone" michael@0: #define MOZSETTINGS_CHANGED "mozsettings-changed" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: namespace { michael@0: michael@0: class TimeZoneSettingObserver : public nsIObserver michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: TimeZoneSettingObserver(); michael@0: virtual ~TimeZoneSettingObserver(); michael@0: static nsresult SetTimeZone(const JS::Value &aValue, JSContext *aContext); michael@0: }; michael@0: michael@0: class TimeZoneSettingCb MOZ_FINAL : public nsISettingsServiceCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: TimeZoneSettingCb() {} michael@0: michael@0: NS_IMETHOD Handle(const nsAString &aName, JS::Handle aResult) { michael@0: michael@0: JSContext *cx = nsContentUtils::GetCurrentJSContext(); michael@0: NS_ENSURE_TRUE(cx, NS_OK); michael@0: michael@0: // If we don't have time.timezone value in the settings, we need michael@0: // to initialize the settings based on the current system timezone michael@0: // to make settings consistent with system. This usually happens michael@0: // at the very first boot. After that, settings must have a value. michael@0: if (aResult.isNull()) { michael@0: // Get the current system time zone offset. Note that we need to michael@0: // convert the value to a UTC representation in the format of michael@0: // "UTC{+,-}hh:mm", so that the Gaia end can know how to interpret. michael@0: // E.g., -480 is "UTC+08:00"; 630 is "UTC-10:30". michael@0: int32_t timeZoneOffset = hal::GetTimezoneOffset(); michael@0: nsPrintfCString curTimeZone("UTC%+03d:%02d", michael@0: -timeZoneOffset / 60, michael@0: abs(timeZoneOffset) % 60); michael@0: michael@0: // Convert it to a JS string. michael@0: NS_ConvertUTF8toUTF16 utf16Str(curTimeZone); michael@0: michael@0: JS::Rooted jsStr(cx, JS_NewUCStringCopyN(cx, michael@0: utf16Str.get(), michael@0: utf16Str.Length())); michael@0: if (!jsStr) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // Set the settings based on the current system timezone. michael@0: nsCOMPtr lock; michael@0: nsCOMPtr settingsService = michael@0: do_GetService("@mozilla.org/settingsService;1"); michael@0: if (!settingsService) { michael@0: ERR("Failed to get settingsLock service!"); michael@0: return NS_OK; michael@0: } michael@0: settingsService->CreateLock(nullptr, getter_AddRefs(lock)); michael@0: JS::Rooted value(cx, JS::StringValue(jsStr)); michael@0: lock->Set(TIME_TIMEZONE, value, nullptr, nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Set the system timezone based on the current settings. michael@0: if (aResult.isString()) { michael@0: return TimeZoneSettingObserver::SetTimeZone(aResult, cx); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD HandleError(const nsAString &aName) { michael@0: ERR("TimeZoneSettingCb::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get()); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(TimeZoneSettingCb, nsISettingsServiceCallback) michael@0: michael@0: TimeZoneSettingObserver::TimeZoneSettingObserver() michael@0: { michael@0: // Setup an observer to watch changes to the setting. michael@0: nsCOMPtr observerService = services::GetObserverService(); michael@0: if (!observerService) { michael@0: ERR("GetObserverService failed"); michael@0: return; michael@0: } michael@0: nsresult rv; michael@0: rv = observerService->AddObserver(this, MOZSETTINGS_CHANGED, false); michael@0: if (NS_FAILED(rv)) { michael@0: ERR("AddObserver failed"); michael@0: return; michael@0: } michael@0: michael@0: // Read the 'time.timezone' setting in order to start with a known michael@0: // value at boot time. The handle() will be called after reading. michael@0: nsCOMPtr lock; michael@0: nsCOMPtr settingsService = michael@0: do_GetService("@mozilla.org/settingsService;1"); michael@0: if (!settingsService) { michael@0: ERR("Failed to get settingsLock service!"); michael@0: return; michael@0: } michael@0: settingsService->CreateLock(nullptr, getter_AddRefs(lock)); michael@0: nsCOMPtr callback = new TimeZoneSettingCb(); michael@0: lock->Get(TIME_TIMEZONE, callback); michael@0: } michael@0: michael@0: nsresult TimeZoneSettingObserver::SetTimeZone(const JS::Value &aValue, JSContext *aContext) michael@0: { michael@0: // Convert the JS value to a nsCString type. michael@0: // The value should be a JS string like "America/Chicago" or "UTC-05:00". michael@0: nsDependentJSString valueStr; michael@0: if (!valueStr.init(aContext, aValue.toString())) { michael@0: ERR("Failed to convert JS value to nsCString"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: NS_ConvertUTF16toUTF8 newTimezone(valueStr); michael@0: michael@0: // Hal expects opposite sign from general notations, michael@0: // so we need to flip it. michael@0: if (newTimezone.Find(NS_LITERAL_CSTRING("UTC+")) == 0) { michael@0: if (!newTimezone.SetCharAt('-', 3)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } else if (newTimezone.Find(NS_LITERAL_CSTRING("UTC-")) == 0) { michael@0: if (!newTimezone.SetCharAt('+', 3)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // Set the timezone only when the system timezone is not identical. michael@0: nsCString curTimezone = hal::GetTimezone(); michael@0: if (!curTimezone.Equals(newTimezone)) { michael@0: hal::SetTimezone(newTimezone); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: TimeZoneSettingObserver::~TimeZoneSettingObserver() michael@0: { michael@0: nsCOMPtr observerService = services::GetObserverService(); michael@0: if (observerService) { michael@0: observerService->RemoveObserver(this, MOZSETTINGS_CHANGED); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(TimeZoneSettingObserver, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: TimeZoneSettingObserver::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (strcmp(aTopic, MOZSETTINGS_CHANGED) != 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Note that this function gets called for any and all settings changes, michael@0: // so we need to carefully check if we have the one we're interested in. michael@0: // michael@0: // The string that we're interested in will be a JSON string that looks like: michael@0: // {"key":"time.timezone","value":"America/Chicago"} or michael@0: // {"key":"time.timezone","value":"UTC-05:00"} michael@0: michael@0: AutoSafeJSContext cx; michael@0: michael@0: // Parse the JSON value. michael@0: nsDependentString dataStr(aData); michael@0: JS::Rooted val(cx); michael@0: if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || michael@0: !val.isObject()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Get the key, which should be the JS string "time.timezone". michael@0: JS::Rooted obj(cx, &val.toObject()); michael@0: JS::Rooted key(cx); michael@0: if (!JS_GetProperty(cx, obj, "key", &key) || michael@0: !key.isString()) { michael@0: return NS_OK; michael@0: } michael@0: bool match; michael@0: if (!JS_StringEqualsAscii(cx, key.toString(), TIME_TIMEZONE, &match) || michael@0: !match) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Get the value, which should be a JS string like "America/Chicago". michael@0: JS::Rooted value(cx); michael@0: if (!JS_GetProperty(cx, obj, "value", &value) || michael@0: !value.isString()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Set the system timezone. michael@0: return SetTimeZone(value, cx); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: static mozilla::StaticRefPtr sTimeZoneSettingObserver; michael@0: namespace mozilla { michael@0: namespace system { michael@0: void michael@0: InitializeTimeZoneSettingObserver() michael@0: { michael@0: sTimeZoneSettingObserver = new TimeZoneSettingObserver(); michael@0: ClearOnShutdown(&sTimeZoneSettingObserver); michael@0: } michael@0: michael@0: } // namespace system michael@0: } // namespace mozilla