|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "base/message_loop.h" |
|
6 #include "jsapi.h" |
|
7 #include "mozilla/Attributes.h" |
|
8 #include "mozilla/ClearOnShutdown.h" |
|
9 #include "mozilla/Hal.h" |
|
10 #include "mozilla/Services.h" |
|
11 #include "mozilla/StaticPtr.h" |
|
12 #include "nsCOMPtr.h" |
|
13 #include "nsDebug.h" |
|
14 #include "nsIObserver.h" |
|
15 #include "nsIObserverService.h" |
|
16 #include "nsISettingsService.h" |
|
17 #include "nsJSUtils.h" |
|
18 #include "nsServiceManagerUtils.h" |
|
19 #include "nsString.h" |
|
20 #include "TimeZoneSettingObserver.h" |
|
21 #include "xpcpublic.h" |
|
22 #include "nsContentUtils.h" |
|
23 #include "nsCxPusher.h" |
|
24 #include "nsPrintfCString.h" |
|
25 |
|
26 #undef LOG |
|
27 #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Time Zone Setting" , ## args) |
|
28 #define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "Time Zone Setting" , ## args) |
|
29 |
|
30 #define TIME_TIMEZONE "time.timezone" |
|
31 #define MOZSETTINGS_CHANGED "mozsettings-changed" |
|
32 |
|
33 using namespace mozilla; |
|
34 |
|
35 namespace { |
|
36 |
|
37 class TimeZoneSettingObserver : public nsIObserver |
|
38 { |
|
39 public: |
|
40 NS_DECL_ISUPPORTS |
|
41 NS_DECL_NSIOBSERVER |
|
42 |
|
43 TimeZoneSettingObserver(); |
|
44 virtual ~TimeZoneSettingObserver(); |
|
45 static nsresult SetTimeZone(const JS::Value &aValue, JSContext *aContext); |
|
46 }; |
|
47 |
|
48 class TimeZoneSettingCb MOZ_FINAL : public nsISettingsServiceCallback |
|
49 { |
|
50 public: |
|
51 NS_DECL_ISUPPORTS |
|
52 |
|
53 TimeZoneSettingCb() {} |
|
54 |
|
55 NS_IMETHOD Handle(const nsAString &aName, JS::Handle<JS::Value> aResult) { |
|
56 |
|
57 JSContext *cx = nsContentUtils::GetCurrentJSContext(); |
|
58 NS_ENSURE_TRUE(cx, NS_OK); |
|
59 |
|
60 // If we don't have time.timezone value in the settings, we need |
|
61 // to initialize the settings based on the current system timezone |
|
62 // to make settings consistent with system. This usually happens |
|
63 // at the very first boot. After that, settings must have a value. |
|
64 if (aResult.isNull()) { |
|
65 // Get the current system time zone offset. Note that we need to |
|
66 // convert the value to a UTC representation in the format of |
|
67 // "UTC{+,-}hh:mm", so that the Gaia end can know how to interpret. |
|
68 // E.g., -480 is "UTC+08:00"; 630 is "UTC-10:30". |
|
69 int32_t timeZoneOffset = hal::GetTimezoneOffset(); |
|
70 nsPrintfCString curTimeZone("UTC%+03d:%02d", |
|
71 -timeZoneOffset / 60, |
|
72 abs(timeZoneOffset) % 60); |
|
73 |
|
74 // Convert it to a JS string. |
|
75 NS_ConvertUTF8toUTF16 utf16Str(curTimeZone); |
|
76 |
|
77 JS::Rooted<JSString*> jsStr(cx, JS_NewUCStringCopyN(cx, |
|
78 utf16Str.get(), |
|
79 utf16Str.Length())); |
|
80 if (!jsStr) { |
|
81 return NS_ERROR_OUT_OF_MEMORY; |
|
82 } |
|
83 |
|
84 // Set the settings based on the current system timezone. |
|
85 nsCOMPtr<nsISettingsServiceLock> lock; |
|
86 nsCOMPtr<nsISettingsService> settingsService = |
|
87 do_GetService("@mozilla.org/settingsService;1"); |
|
88 if (!settingsService) { |
|
89 ERR("Failed to get settingsLock service!"); |
|
90 return NS_OK; |
|
91 } |
|
92 settingsService->CreateLock(nullptr, getter_AddRefs(lock)); |
|
93 JS::Rooted<JS::Value> value(cx, JS::StringValue(jsStr)); |
|
94 lock->Set(TIME_TIMEZONE, value, nullptr, nullptr); |
|
95 return NS_OK; |
|
96 } |
|
97 |
|
98 // Set the system timezone based on the current settings. |
|
99 if (aResult.isString()) { |
|
100 return TimeZoneSettingObserver::SetTimeZone(aResult, cx); |
|
101 } |
|
102 |
|
103 return NS_OK; |
|
104 } |
|
105 |
|
106 NS_IMETHOD HandleError(const nsAString &aName) { |
|
107 ERR("TimeZoneSettingCb::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get()); |
|
108 return NS_OK; |
|
109 } |
|
110 }; |
|
111 |
|
112 NS_IMPL_ISUPPORTS(TimeZoneSettingCb, nsISettingsServiceCallback) |
|
113 |
|
114 TimeZoneSettingObserver::TimeZoneSettingObserver() |
|
115 { |
|
116 // Setup an observer to watch changes to the setting. |
|
117 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); |
|
118 if (!observerService) { |
|
119 ERR("GetObserverService failed"); |
|
120 return; |
|
121 } |
|
122 nsresult rv; |
|
123 rv = observerService->AddObserver(this, MOZSETTINGS_CHANGED, false); |
|
124 if (NS_FAILED(rv)) { |
|
125 ERR("AddObserver failed"); |
|
126 return; |
|
127 } |
|
128 |
|
129 // Read the 'time.timezone' setting in order to start with a known |
|
130 // value at boot time. The handle() will be called after reading. |
|
131 nsCOMPtr<nsISettingsServiceLock> lock; |
|
132 nsCOMPtr<nsISettingsService> settingsService = |
|
133 do_GetService("@mozilla.org/settingsService;1"); |
|
134 if (!settingsService) { |
|
135 ERR("Failed to get settingsLock service!"); |
|
136 return; |
|
137 } |
|
138 settingsService->CreateLock(nullptr, getter_AddRefs(lock)); |
|
139 nsCOMPtr<nsISettingsServiceCallback> callback = new TimeZoneSettingCb(); |
|
140 lock->Get(TIME_TIMEZONE, callback); |
|
141 } |
|
142 |
|
143 nsresult TimeZoneSettingObserver::SetTimeZone(const JS::Value &aValue, JSContext *aContext) |
|
144 { |
|
145 // Convert the JS value to a nsCString type. |
|
146 // The value should be a JS string like "America/Chicago" or "UTC-05:00". |
|
147 nsDependentJSString valueStr; |
|
148 if (!valueStr.init(aContext, aValue.toString())) { |
|
149 ERR("Failed to convert JS value to nsCString"); |
|
150 return NS_ERROR_FAILURE; |
|
151 } |
|
152 NS_ConvertUTF16toUTF8 newTimezone(valueStr); |
|
153 |
|
154 // Hal expects opposite sign from general notations, |
|
155 // so we need to flip it. |
|
156 if (newTimezone.Find(NS_LITERAL_CSTRING("UTC+")) == 0) { |
|
157 if (!newTimezone.SetCharAt('-', 3)) { |
|
158 return NS_ERROR_FAILURE; |
|
159 } |
|
160 } else if (newTimezone.Find(NS_LITERAL_CSTRING("UTC-")) == 0) { |
|
161 if (!newTimezone.SetCharAt('+', 3)) { |
|
162 return NS_ERROR_FAILURE; |
|
163 } |
|
164 } |
|
165 |
|
166 // Set the timezone only when the system timezone is not identical. |
|
167 nsCString curTimezone = hal::GetTimezone(); |
|
168 if (!curTimezone.Equals(newTimezone)) { |
|
169 hal::SetTimezone(newTimezone); |
|
170 } |
|
171 |
|
172 return NS_OK; |
|
173 } |
|
174 |
|
175 TimeZoneSettingObserver::~TimeZoneSettingObserver() |
|
176 { |
|
177 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); |
|
178 if (observerService) { |
|
179 observerService->RemoveObserver(this, MOZSETTINGS_CHANGED); |
|
180 } |
|
181 } |
|
182 |
|
183 NS_IMPL_ISUPPORTS(TimeZoneSettingObserver, nsIObserver) |
|
184 |
|
185 NS_IMETHODIMP |
|
186 TimeZoneSettingObserver::Observe(nsISupports *aSubject, |
|
187 const char *aTopic, |
|
188 const char16_t *aData) |
|
189 { |
|
190 if (strcmp(aTopic, MOZSETTINGS_CHANGED) != 0) { |
|
191 return NS_OK; |
|
192 } |
|
193 |
|
194 // Note that this function gets called for any and all settings changes, |
|
195 // so we need to carefully check if we have the one we're interested in. |
|
196 // |
|
197 // The string that we're interested in will be a JSON string that looks like: |
|
198 // {"key":"time.timezone","value":"America/Chicago"} or |
|
199 // {"key":"time.timezone","value":"UTC-05:00"} |
|
200 |
|
201 AutoSafeJSContext cx; |
|
202 |
|
203 // Parse the JSON value. |
|
204 nsDependentString dataStr(aData); |
|
205 JS::Rooted<JS::Value> val(cx); |
|
206 if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || |
|
207 !val.isObject()) { |
|
208 return NS_OK; |
|
209 } |
|
210 |
|
211 // Get the key, which should be the JS string "time.timezone". |
|
212 JS::Rooted<JSObject*> obj(cx, &val.toObject()); |
|
213 JS::Rooted<JS::Value> key(cx); |
|
214 if (!JS_GetProperty(cx, obj, "key", &key) || |
|
215 !key.isString()) { |
|
216 return NS_OK; |
|
217 } |
|
218 bool match; |
|
219 if (!JS_StringEqualsAscii(cx, key.toString(), TIME_TIMEZONE, &match) || |
|
220 !match) { |
|
221 return NS_OK; |
|
222 } |
|
223 |
|
224 // Get the value, which should be a JS string like "America/Chicago". |
|
225 JS::Rooted<JS::Value> value(cx); |
|
226 if (!JS_GetProperty(cx, obj, "value", &value) || |
|
227 !value.isString()) { |
|
228 return NS_OK; |
|
229 } |
|
230 |
|
231 // Set the system timezone. |
|
232 return SetTimeZone(value, cx); |
|
233 } |
|
234 |
|
235 } // anonymous namespace |
|
236 |
|
237 static mozilla::StaticRefPtr<TimeZoneSettingObserver> sTimeZoneSettingObserver; |
|
238 namespace mozilla { |
|
239 namespace system { |
|
240 void |
|
241 InitializeTimeZoneSettingObserver() |
|
242 { |
|
243 sTimeZoneSettingObserver = new TimeZoneSettingObserver(); |
|
244 ClearOnShutdown(&sTimeZoneSettingObserver); |
|
245 } |
|
246 |
|
247 } // namespace system |
|
248 } // namespace mozilla |