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 "AutoMounter.h"
6 #include "AutoMounterSetting.h"
7 
8 #include "base/message_loop.h"
9 #include "jsapi.h"
10 #include "mozilla/Services.h"
11 #include "nsCOMPtr.h"
12 #include "nsContentUtils.h"
13 #include "nsDebug.h"
14 #include "nsIObserverService.h"
15 #include "nsISettingsService.h"
16 #include "nsJSUtils.h"
17 #include "nsPrintfCString.h"
18 #include "nsServiceManagerUtils.h"
19 #include "nsString.h"
20 #include "nsThreadUtils.h"
21 #include "xpcpublic.h"
22 #include "mozilla/dom/ScriptSettings.h"
23 #include "mozilla/Attributes.h"
24 #include "mozilla/dom/BindingUtils.h"
25 #include "mozilla/dom/SettingChangeNotificationBinding.h"
26 
27 #undef LOG
28 #undef ERR
29 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AutoMounterSetting" , ## args)
30 #define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "AutoMounterSetting" , ## args)
31 
32 #define UMS_MODE                  "ums.mode"
33 #define UMS_STATUS                "ums.status"
34 #define UMS_VOLUME_ENABLED_PREFIX "ums.volume."
35 #define UMS_VOLUME_ENABLED_SUFFIX ".enabled"
36 #define MOZSETTINGS_CHANGED       "mozsettings-changed"
37 
38 using namespace mozilla::dom;
39 
40 namespace mozilla {
41 namespace system {
42 
43 class SettingsServiceCallback final : public nsISettingsServiceCallback
44 {
45 public:
46   NS_DECL_THREADSAFE_ISUPPORTS
47 
SettingsServiceCallback()48   SettingsServiceCallback() {}
49 
Handle(const nsAString & aName,JS::Handle<JS::Value> aResult)50   NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
51   {
52     if (aResult.isInt32()) {
53       int32_t mode = aResult.toInt32();
54       SetAutoMounterMode(mode);
55     }
56     return NS_OK;
57   }
58 
HandleError(const nsAString & aName)59   NS_IMETHOD HandleError(const nsAString& aName)
60   {
61     ERR("SettingsCallback::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get());
62     return NS_OK;
63   }
64 
65 protected:
~SettingsServiceCallback()66   ~SettingsServiceCallback() {}
67 };
68 
69 NS_IMPL_ISUPPORTS(SettingsServiceCallback, nsISettingsServiceCallback)
70 
71 class CheckVolumeSettingsCallback final : public nsISettingsServiceCallback
72 {
73 public:
74   NS_DECL_THREADSAFE_ISUPPORTS
75 
CheckVolumeSettingsCallback(const nsACString & aVolumeName)76   CheckVolumeSettingsCallback(const nsACString& aVolumeName)
77   : mVolumeName(aVolumeName) {}
78 
Handle(const nsAString & aName,JS::Handle<JS::Value> aResult)79   NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
80   {
81     if (aResult.isBoolean()) {
82       bool isSharingEnabled = aResult.toBoolean();
83       SetAutoMounterSharingMode(mVolumeName, isSharingEnabled);
84     }
85     return NS_OK;
86   }
87 
HandleError(const nsAString & aName)88   NS_IMETHOD HandleError(const nsAString& aName)
89   {
90     ERR("CheckVolumeSettingsCallback::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get());
91     return NS_OK;
92   }
93 
94 protected:
~CheckVolumeSettingsCallback()95   ~CheckVolumeSettingsCallback() {}
96 
97 private:
98   nsCString mVolumeName;
99 };
100 
NS_IMPL_ISUPPORTS(CheckVolumeSettingsCallback,nsISettingsServiceCallback)101 NS_IMPL_ISUPPORTS(CheckVolumeSettingsCallback, nsISettingsServiceCallback)
102 
103 AutoMounterSetting::AutoMounterSetting()
104   : mStatus(AUTOMOUNTER_STATUS_DISABLED)
105 {
106   MOZ_ASSERT(NS_IsMainThread());
107 
108   // Setup an observer to watch changes to the setting
109   nsCOMPtr<nsIObserverService> observerService =
110     mozilla::services::GetObserverService();
111   if (!observerService) {
112     ERR("GetObserverService failed");
113     return;
114   }
115   nsresult rv;
116   rv = observerService->AddObserver(this, MOZSETTINGS_CHANGED, false);
117   if (NS_FAILED(rv)) {
118     ERR("AddObserver failed");
119     return;
120   }
121 
122   // Force ums.mode to be 0 initially. We do this because settings are persisted.
123   // We don't want UMS to be enabled until such time as the phone is unlocked,
124   // and gaia/apps/system/js/storage.js takes care of detecting when the phone
125   // becomes unlocked and changes ums.mode appropriately.
126   nsCOMPtr<nsISettingsService> settingsService =
127     do_GetService("@mozilla.org/settingsService;1");
128   if (!settingsService) {
129     ERR("Failed to get settingsLock service!");
130     return;
131   }
132   nsCOMPtr<nsISettingsServiceLock> lock;
133   settingsService->CreateLock(nullptr, getter_AddRefs(lock));
134   nsCOMPtr<nsISettingsServiceCallback> callback = new SettingsServiceCallback();
135   JS::Rooted<JS::Value> value(RootingCx());
136   value.setInt32(AUTOMOUNTER_DISABLE);
137   lock->Set(UMS_MODE, value, callback, nullptr);
138   value.setInt32(mStatus);
139   lock->Set(UMS_STATUS, value, nullptr, nullptr);
140 }
141 
~AutoMounterSetting()142 AutoMounterSetting::~AutoMounterSetting()
143 {
144   nsCOMPtr<nsIObserverService> observerService =
145     mozilla::services::GetObserverService();
146   if (observerService) {
147     observerService->RemoveObserver(this, MOZSETTINGS_CHANGED);
148   }
149 }
150 
NS_IMPL_ISUPPORTS(AutoMounterSetting,nsIObserver) const151 NS_IMPL_ISUPPORTS(AutoMounterSetting, nsIObserver)
152 
153 const char *
154 AutoMounterSetting::StatusStr(int32_t aStatus)
155 {
156   switch (aStatus) {
157     case AUTOMOUNTER_STATUS_DISABLED:   return "Disabled";
158     case AUTOMOUNTER_STATUS_ENABLED:    return "Enabled";
159     case AUTOMOUNTER_STATUS_FILES_OPEN: return "FilesOpen";
160   }
161   return "??? Unknown ???";
162 }
163 
164 class CheckVolumeSettingsRunnable : public Runnable
165 {
166 public:
CheckVolumeSettingsRunnable(const nsACString & aVolumeName)167   CheckVolumeSettingsRunnable(const nsACString& aVolumeName)
168     : mVolumeName(aVolumeName) {}
169 
Run()170   NS_IMETHOD Run() override
171   {
172     MOZ_ASSERT(NS_IsMainThread());
173     nsCOMPtr<nsISettingsService> settingsService =
174       do_GetService("@mozilla.org/settingsService;1");
175     NS_ENSURE_TRUE(settingsService, NS_ERROR_FAILURE);
176     nsCOMPtr<nsISettingsServiceLock> lock;
177     settingsService->CreateLock(nullptr, getter_AddRefs(lock));
178     nsCOMPtr<nsISettingsServiceCallback> callback =
179       new CheckVolumeSettingsCallback(mVolumeName);
180     nsPrintfCString setting(UMS_VOLUME_ENABLED_PREFIX "%s" UMS_VOLUME_ENABLED_SUFFIX,
181                             mVolumeName.get());
182     lock->Get(setting.get(), callback);
183     return NS_OK;
184   }
185 
186 private:
187   nsCString mVolumeName;
188 };
189 
190 //static
191 void
CheckVolumeSettings(const nsACString & aVolumeName)192 AutoMounterSetting::CheckVolumeSettings(const nsACString& aVolumeName)
193 {
194   NS_DispatchToMainThread(new CheckVolumeSettingsRunnable(aVolumeName));
195 }
196 
197 class SetStatusRunnable : public Runnable
198 {
199 public:
SetStatusRunnable(int32_t aStatus)200   SetStatusRunnable(int32_t aStatus) : mStatus(aStatus) {}
201 
Run()202   NS_IMETHOD Run() override
203   {
204     MOZ_ASSERT(NS_IsMainThread());
205     nsCOMPtr<nsISettingsService> settingsService =
206       do_GetService("@mozilla.org/settingsService;1");
207     NS_ENSURE_TRUE(settingsService, NS_ERROR_FAILURE);
208     nsCOMPtr<nsISettingsServiceLock> lock;
209     settingsService->CreateLock(nullptr, getter_AddRefs(lock));
210     // lock may be null if this gets called during shutdown.
211     if (lock) {
212       JS::Rooted<JS::Value> value(RootingCx(),
213 				  JS::Int32Value(mStatus));
214       lock->Set(UMS_STATUS, value, nullptr, nullptr);
215     }
216     return NS_OK;
217   }
218 
219 private:
220   int32_t mStatus;
221 };
222 
223 //static
224 void
SetStatus(int32_t aStatus)225 AutoMounterSetting::SetStatus(int32_t aStatus)
226 {
227   if (aStatus != mStatus) {
228     LOG("Changing status from '%s' to '%s'",
229         StatusStr(mStatus), StatusStr(aStatus));
230     mStatus = aStatus;
231     NS_DispatchToMainThread(new SetStatusRunnable(aStatus));
232   }
233 }
234 
235 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)236 AutoMounterSetting::Observe(nsISupports* aSubject,
237                             const char* aTopic,
238                             const char16_t* aData)
239 {
240   if (strcmp(aTopic, MOZSETTINGS_CHANGED) != 0) {
241     return NS_OK;
242   }
243 
244   // Note that this function gets called for any and all settings changes,
245   // so we need to carefully check if we have the one we're interested in.
246   //
247   // The string that we're interested in will be a JSON string that looks like:
248   //  {"key":"ums.autoMount","value":true}
249 
250   RootedDictionary<SettingChangeNotification> setting(RootingCx());
251   if (!WrappedJSToDictionary(aSubject, setting)) {
252     return NS_OK;
253   }
254 
255   // Check for ums.mode changes
256   if (setting.mKey.EqualsASCII(UMS_MODE)) {
257     if (!setting.mValue.isInt32()) {
258       return NS_OK;
259     }
260     int32_t mode = setting.mValue.toInt32();
261     SetAutoMounterMode(mode);
262     return NS_OK;
263   }
264 
265   // Check for ums.volume.NAME.enabled
266   if (StringBeginsWith(setting.mKey, NS_LITERAL_STRING(UMS_VOLUME_ENABLED_PREFIX)) &&
267       StringEndsWith(setting.mKey, NS_LITERAL_STRING(UMS_VOLUME_ENABLED_SUFFIX))) {
268     if (!setting.mValue.isBoolean()) {
269       return NS_OK;
270     }
271     const size_t prefixLen = sizeof(UMS_VOLUME_ENABLED_PREFIX) - 1;
272     const size_t suffixLen = sizeof(UMS_VOLUME_ENABLED_SUFFIX) - 1;
273     nsDependentSubstring volumeName =
274       Substring(setting.mKey, prefixLen, setting.mKey.Length() - prefixLen - suffixLen);
275     bool isSharingEnabled = setting.mValue.toBoolean();
276     SetAutoMounterSharingMode(NS_LossyConvertUTF16toASCII(volumeName), isSharingEnabled);
277     return NS_OK;
278   }
279 
280   return NS_OK;
281 }
282 
283 } // namespace system
284 } // namespace mozilla
285