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