|
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 "use strict"; |
|
6 |
|
7 const Cu = Components.utils; |
|
8 const Cc = Components.classes; |
|
9 const Ci = Components.interfaces; |
|
10 |
|
11 this.EXPORTED_SYMBOLS = ["OperatorAppsRegistry"]; |
|
12 |
|
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
14 Cu.import("resource://gre/modules/FileUtils.jsm"); |
|
15 Cu.import("resource://gre/modules/Webapps.jsm"); |
|
16 Cu.import("resource://gre/modules/Services.jsm"); |
|
17 Cu.import("resource://gre/modules/osfile.jsm"); |
|
18 Cu.import("resource://gre/modules/AppsUtils.jsm"); |
|
19 Cu.import("resource://gre/modules/Task.jsm"); |
|
20 |
|
21 let Path = OS.Path; |
|
22 |
|
23 #ifdef MOZ_B2G_RIL |
|
24 XPCOMUtils.defineLazyServiceGetter(this, "iccProvider", |
|
25 "@mozilla.org/ril/content-helper;1", |
|
26 "nsIIccProvider"); |
|
27 #endif |
|
28 |
|
29 function debug(aMsg) { |
|
30 //dump("-*-*- OperatorApps.jsm : " + aMsg + "\n"); |
|
31 } |
|
32 |
|
33 // Single Variant source dir will be set in PREF_SINGLE_VARIANT_DIR |
|
34 // preference. |
|
35 // if PREF_SINGLE_VARIANT_DIR does not exist or has not value, it will use (as |
|
36 // single variant source) the value of |
|
37 // DIRECTORY_NAME + "/" + SINGLE_VARIANT_SOURCE_DIR value instead. |
|
38 // SINGLE_VARIANT_CONF_FILE will be stored on Single Variant Source. |
|
39 // Apps will be stored on an app per directory basis, hanging from |
|
40 // SINGLE_VARIANT_SOURCE_DIR |
|
41 const DIRECTORY_NAME = "webappsDir"; |
|
42 const SINGLE_VARIANT_SOURCE_DIR = "svoperapps"; |
|
43 const SINGLE_VARIANT_CONF_FILE = "singlevariantconf.json"; |
|
44 const PREF_FIRST_RUN_WITH_SIM = "dom.webapps.firstRunWithSIM"; |
|
45 const PREF_SINGLE_VARIANT_DIR = "dom.mozApps.single_variant_sourcedir"; |
|
46 const METADATA = "metadata.json"; |
|
47 const UPDATEMANIFEST = "update.webapp"; |
|
48 const MANIFEST = "manifest.webapp"; |
|
49 const APPLICATION_ZIP = "application.zip"; |
|
50 |
|
51 function isFirstRunWithSIM() { |
|
52 try { |
|
53 if (Services.prefs.prefHasUserValue(PREF_FIRST_RUN_WITH_SIM)) { |
|
54 return Services.prefs.getBoolPref(PREF_FIRST_RUN_WITH_SIM); |
|
55 } |
|
56 } catch(e) { |
|
57 debug ("Error getting pref. " + e); |
|
58 } |
|
59 return true; |
|
60 } |
|
61 |
|
62 #ifdef MOZ_B2G_RIL |
|
63 let iccListener = { |
|
64 notifyStkCommand: function() {}, |
|
65 |
|
66 notifyStkSessionEnd: function() {}, |
|
67 |
|
68 notifyCardStateChanged: function() {}, |
|
69 |
|
70 notifyIccInfoChanged: function() { |
|
71 // TODO: Bug 927709 - OperatorApps for multi-sim |
|
72 // In Multi-sim, there is more than one client in iccProvider. Each |
|
73 // client represents a icc service. To maintain the backward compatibility |
|
74 // with single sim, we always use client 0 for now. Adding support for |
|
75 // multiple sim will be addressed in bug 927709, if needed. |
|
76 let clientId = 0; |
|
77 let iccInfo = iccProvider.getIccInfo(clientId); |
|
78 if (iccInfo && iccInfo.mcc && iccInfo.mnc) { |
|
79 let mcc = iccInfo.mcc; |
|
80 let mnc = iccInfo.mnc; |
|
81 debug("******* iccListener cardIccInfo MCC-MNC: " + mcc + "-" + mnc); |
|
82 iccProvider.unregisterIccMsg(clientId, this); |
|
83 OperatorAppsRegistry._installOperatorApps(mcc, mnc); |
|
84 |
|
85 debug("Broadcast message first-run-with-sim"); |
|
86 let messenger = Cc["@mozilla.org/system-message-internal;1"] |
|
87 .getService(Ci.nsISystemMessagesInternal); |
|
88 messenger.broadcastMessage("first-run-with-sim", { mcc: mcc, |
|
89 mnc: mnc }); |
|
90 } |
|
91 } |
|
92 }; |
|
93 #endif |
|
94 |
|
95 this.OperatorAppsRegistry = { |
|
96 |
|
97 _baseDirectory: null, |
|
98 |
|
99 init: function() { |
|
100 debug("init"); |
|
101 #ifdef MOZ_B2G_RIL |
|
102 if (isFirstRunWithSIM()) { |
|
103 debug("First Run with SIM"); |
|
104 Task.spawn(function() { |
|
105 try { |
|
106 yield this._initializeSourceDir(); |
|
107 // TODO: Bug 927709 - OperatorApps for multi-sim |
|
108 // In Multi-sim, there is more than one client in iccProvider. Each |
|
109 // client represents a icc service. To maintain the backward |
|
110 // compatibility with single sim, we always use client 0 for now. |
|
111 // Adding support for multiple sim will be addressed in bug 927709, if |
|
112 // needed. |
|
113 let clientId = 0; |
|
114 let iccInfo = iccProvider.getIccInfo(clientId); |
|
115 let mcc = 0; |
|
116 let mnc = 0; |
|
117 if (iccInfo && iccInfo.mcc) { |
|
118 mcc = iccInfo.mcc; |
|
119 } |
|
120 if (iccInfo && iccInfo.mnc) { |
|
121 mnc = iccInfo.mnc; |
|
122 } |
|
123 if (mcc && mnc) { |
|
124 this._installOperatorApps(mcc, mnc); |
|
125 } else { |
|
126 iccProvider.registerIccMsg(clientId, iccListener); |
|
127 } |
|
128 } catch (e) { |
|
129 debug("Error Initializing OperatorApps. " + e); |
|
130 } |
|
131 }.bind(this)); |
|
132 } else { |
|
133 debug("No First Run with SIM"); |
|
134 } |
|
135 #endif |
|
136 }, |
|
137 |
|
138 _copyDirectory: function(aOrg, aDst) { |
|
139 debug("copying " + aOrg + " to " + aDst); |
|
140 return aDst && Task.spawn(function() { |
|
141 try { |
|
142 let orgDir = Cc["@mozilla.org/file/local;1"] |
|
143 .createInstance(Ci.nsIFile); |
|
144 orgDir.initWithPath(aOrg); |
|
145 if (!orgDir.exists() || !orgDir.isDirectory()) { |
|
146 debug(aOrg + " does not exist or is not a directory"); |
|
147 return; |
|
148 } |
|
149 |
|
150 let dstDir = Cc["@mozilla.org/file/local;1"] |
|
151 .createInstance(Ci.nsIFile); |
|
152 dstDir.initWithPath(aDst); |
|
153 if (!dstDir.exists()) { |
|
154 dstDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); |
|
155 } |
|
156 |
|
157 let entries = orgDir.directoryEntries; |
|
158 while (entries.hasMoreElements()) { |
|
159 let entry = entries.getNext().QueryInterface(Ci.nsIFile); |
|
160 |
|
161 if (!entry.isDirectory()) { |
|
162 // Remove the file, because copyTo doesn't overwrite files. |
|
163 let dstFile = dstDir.clone(); |
|
164 dstFile.append(entry.leafName); |
|
165 if(dstFile.exists()) { |
|
166 dstFile.remove(false); |
|
167 } |
|
168 |
|
169 entry.copyTo(dstDir, entry.leafName); |
|
170 } else { |
|
171 yield this._copyDirectory(entry.path, |
|
172 Path.join(aDst, entry.leafName)); |
|
173 } |
|
174 } |
|
175 } catch (e) { |
|
176 debug("Error copying " + aOrg + " to " + aDst + ". " + e); |
|
177 } |
|
178 }.bind(this)); |
|
179 }, |
|
180 |
|
181 _initializeSourceDir: function() { |
|
182 return Task.spawn(function() { |
|
183 let svFinalDirName; |
|
184 try { |
|
185 svFinalDirName = Services.prefs.getCharPref(PREF_SINGLE_VARIANT_DIR); |
|
186 } catch(e) { |
|
187 debug ("Error getting pref. " + e); |
|
188 this.appsDir = FileUtils.getFile(DIRECTORY_NAME, |
|
189 [SINGLE_VARIANT_SOURCE_DIR]).path; |
|
190 return; |
|
191 } |
|
192 // If SINGLE_VARIANT_CONF_FILE is in PREF_SINGLE_VARIANT_DIR return |
|
193 // PREF_SINGLE_VARIANT_DIR as sourceDir, else go to |
|
194 // DIRECTORY_NAME + SINGLE_VARIANT_SOURCE_DIR and move all apps (and |
|
195 // configuration file) to PREF_SINGLE_VARIANT_DIR and return |
|
196 // PREF_SINGLE_VARIANT_DIR as sourceDir. |
|
197 let svFinalDir = Cc["@mozilla.org/file/local;1"] |
|
198 .createInstance(Ci.nsIFile); |
|
199 svFinalDir.initWithPath(svFinalDirName); |
|
200 if (!svFinalDir.exists()) { |
|
201 svFinalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); |
|
202 } |
|
203 |
|
204 let svIndex = svFinalDir.clone(); |
|
205 svIndex.append(SINGLE_VARIANT_CONF_FILE); |
|
206 if (!svIndex.exists()) { |
|
207 let svSourceDir = FileUtils.getFile(DIRECTORY_NAME, |
|
208 [SINGLE_VARIANT_SOURCE_DIR]); |
|
209 |
|
210 yield this._copyDirectory(svSourceDir.path, svFinalDirName); |
|
211 |
|
212 debug("removing directory:" + svSourceDir.path); |
|
213 try { |
|
214 svSourceDir.remove(true); |
|
215 } catch(ex) { } |
|
216 } |
|
217 |
|
218 this.appsDir = svFinalDirName; |
|
219 }.bind(this)); |
|
220 }, |
|
221 |
|
222 set appsDir(aDir) { |
|
223 debug("appsDir SET: " + aDir); |
|
224 if (aDir) { |
|
225 this._baseDirectory = Cc["@mozilla.org/file/local;1"] |
|
226 .createInstance(Ci.nsILocalFile); |
|
227 this._baseDirectory.initWithPath(aDir); |
|
228 } else { |
|
229 this._baseDirectory = null; |
|
230 } |
|
231 }, |
|
232 |
|
233 get appsDir() { |
|
234 return this._baseDirectory; |
|
235 }, |
|
236 |
|
237 eraseVariantAppsNotInList: function(aIdsApp) { |
|
238 if (!aIdsApp || !Array.isArray(aIdsApp)) { |
|
239 aIdsApp = [ ]; |
|
240 } |
|
241 |
|
242 let svDir; |
|
243 try { |
|
244 svDir = this.appsDir.clone(); |
|
245 } catch (e) { |
|
246 debug("eraseVariantAppsNotInList --> Error getting Dir "+ |
|
247 svDir.path + ". " + e); |
|
248 return; |
|
249 } |
|
250 |
|
251 if (!svDir || !svDir.exists()) { |
|
252 return; |
|
253 } |
|
254 |
|
255 let entries = svDir.directoryEntries; |
|
256 while (entries.hasMoreElements()) { |
|
257 let entry = entries.getNext().QueryInterface(Ci.nsIFile); |
|
258 if (entry.isDirectory() && aIdsApp.indexOf(entry.leafName) < 0) { |
|
259 try{ |
|
260 entry.remove(true); |
|
261 } catch(e) { |
|
262 debug("Error removing [" + entry.path + "]." + e); |
|
263 } |
|
264 } |
|
265 } |
|
266 }, |
|
267 |
|
268 _launchInstall: function(isPackage, aId, aMetadata, aManifest) { |
|
269 if (!aManifest) { |
|
270 debug("Error: The application " + aId + " does not have a manifest"); |
|
271 return; |
|
272 } |
|
273 |
|
274 let appData = { |
|
275 app: { |
|
276 installOrigin: aMetadata.installOrigin, |
|
277 origin: aMetadata.origin, |
|
278 manifestURL: aMetadata.manifestURL, |
|
279 manifestHash: AppsUtils.computeHash(JSON.stringify(aManifest)) |
|
280 }, |
|
281 appId: undefined, |
|
282 isBrowser: false, |
|
283 isPackage: isPackage, |
|
284 forceSuccessAck: true |
|
285 }; |
|
286 |
|
287 if (isPackage) { |
|
288 debug("aId:" + aId + ". Installing as packaged app."); |
|
289 let installPack = this.appsDir.clone(); |
|
290 installPack.append(aId); |
|
291 installPack.append(APPLICATION_ZIP); |
|
292 |
|
293 if (!installPack.exists()) { |
|
294 debug("SV " + installPack.path + " file do not exists for app " + aId); |
|
295 return; |
|
296 } |
|
297 |
|
298 appData.app.localInstallPath = installPack.path; |
|
299 appData.app.updateManifest = aManifest; |
|
300 DOMApplicationRegistry.confirmInstall(appData); |
|
301 } else { |
|
302 debug("aId:" + aId + ". Installing as hosted app."); |
|
303 appData.app.manifest = aManifest; |
|
304 DOMApplicationRegistry.confirmInstall(appData); |
|
305 } |
|
306 }, |
|
307 |
|
308 _installOperatorApps: function(aMcc, aMnc) { |
|
309 Task.spawn(function() { |
|
310 debug("Install operator apps ---> mcc:"+ aMcc + ", mnc:" + aMnc); |
|
311 if (!isFirstRunWithSIM()) { |
|
312 debug("Operator apps already installed."); |
|
313 return; |
|
314 } |
|
315 |
|
316 let aIdsApp = yield this._getSingleVariantApps(aMcc, aMnc); |
|
317 debug("installOperatorApps --> aIdsApp:" + JSON.stringify(aIdsApp)); |
|
318 for (let i = 0; i < aIdsApp.length; i++) { |
|
319 let aId = aIdsApp[i]; |
|
320 let aMetadata = yield AppsUtils.loadJSONAsync( |
|
321 Path.join(this.appsDir.path, aId, METADATA)); |
|
322 if (!aMetadata) { |
|
323 debug("Error reading metadata file"); |
|
324 return; |
|
325 } |
|
326 |
|
327 debug("metadata:" + JSON.stringify(aMetadata)); |
|
328 let isPackage = true; |
|
329 let manifest; |
|
330 let manifests = [UPDATEMANIFEST, MANIFEST]; |
|
331 for (let j = 0; j < manifests.length; j++) { |
|
332 manifest = yield AppsUtils.loadJSONAsync( |
|
333 Path.join(this.appsDir.path, aId, manifests[j])); |
|
334 |
|
335 if (!manifest) { |
|
336 isPackage = false; |
|
337 } else { |
|
338 break; |
|
339 } |
|
340 } |
|
341 if (manifest) { |
|
342 this._launchInstall(isPackage, aId, aMetadata, manifest); |
|
343 } else { |
|
344 debug ("Error. Neither " + UPDATEMANIFEST + " file nor " + MANIFEST + |
|
345 " file for " + aId + " app."); |
|
346 } |
|
347 } |
|
348 this.eraseVariantAppsNotInList(aIdsApp); |
|
349 Services.prefs.setBoolPref(PREF_FIRST_RUN_WITH_SIM, false); |
|
350 Services.prefs.savePrefFile(null); |
|
351 }.bind(this)).then(null, function(aError) { |
|
352 debug("Error: " + aError); |
|
353 }); |
|
354 }, |
|
355 |
|
356 _getSingleVariantApps: function(aMcc, aMnc) { |
|
357 |
|
358 function normalizeCode(aCode) { |
|
359 let ncode = "" + aCode; |
|
360 while (ncode.length < 3) { |
|
361 ncode = "0" + ncode; |
|
362 } |
|
363 return ncode; |
|
364 } |
|
365 |
|
366 return Task.spawn(function*() { |
|
367 let key = normalizeCode(aMcc) + "-" + normalizeCode(aMnc); |
|
368 let file = Path.join(this.appsDir.path, SINGLE_VARIANT_CONF_FILE); |
|
369 let aData = yield AppsUtils.loadJSONAsync(file); |
|
370 |
|
371 if (!aData || !(key in aData)) { |
|
372 return []; |
|
373 } |
|
374 |
|
375 return aData[key]; |
|
376 }.bind(this)); |
|
377 } |
|
378 }; |
|
379 |
|
380 OperatorAppsRegistry.init(); |