|
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 "use strict"; |
|
5 |
|
6 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; |
|
7 |
|
8 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
9 Cu.import("resource://gre/modules/Services.jsm"); |
|
10 Cu.import("resource://gre/modules/Prompt.jsm"); |
|
11 Cu.import("resource://gre/modules/Messaging.jsm"); |
|
12 |
|
13 XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() { |
|
14 let ContentAreaUtils = {}; |
|
15 Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils); |
|
16 return ContentAreaUtils; |
|
17 }); |
|
18 |
|
19 this.EXPORTED_SYMBOLS = ["App","HelperApps"]; |
|
20 |
|
21 function App(data) { |
|
22 this.name = data.name; |
|
23 this.isDefault = data.isDefault; |
|
24 this.packageName = data.packageName; |
|
25 this.activityName = data.activityName; |
|
26 this.iconUri = "-moz-icon://" + data.packageName; |
|
27 } |
|
28 |
|
29 App.prototype = { |
|
30 // callback will be null if a result is not requested |
|
31 launch: function(uri, callback) { |
|
32 HelperApps._launchApp(this, uri, callback); |
|
33 return false; |
|
34 } |
|
35 } |
|
36 |
|
37 var HelperApps = { |
|
38 get defaultBrowsers() { |
|
39 delete this.defaultBrowsers; |
|
40 this.defaultBrowsers = this._getHandlers("http://www.example.com", { |
|
41 filterBrowsers: false, |
|
42 filterHtml: false |
|
43 }); |
|
44 return this.defaultBrowsers; |
|
45 }, |
|
46 |
|
47 // Finds handlers that have registered for text/html pages or urls ending in html. Some apps, like |
|
48 // the Samsung Video player will only appear for these urls, while some Browsers (like Link Bubble) |
|
49 // won't register here because of the text/html mime type. |
|
50 get defaultHtmlHandlers() { |
|
51 delete this.defaultHtmlHandlers; |
|
52 return this.defaultHtmlHandlers = this._getHandlers("http://www.example.com/index.html", { |
|
53 filterBrowsers: false, |
|
54 filterHtml: false |
|
55 }); |
|
56 }, |
|
57 |
|
58 _getHandlers: function(url, options) { |
|
59 let values = {}; |
|
60 |
|
61 let handlers = this.getAppsForUri(Services.io.newURI(url, null, null), options); |
|
62 handlers.forEach(function(app) { |
|
63 values[app.name] = app; |
|
64 }, this); |
|
65 |
|
66 return values; |
|
67 }, |
|
68 |
|
69 get protoSvc() { |
|
70 delete this.protoSvc; |
|
71 return this.protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService); |
|
72 }, |
|
73 |
|
74 get urlHandlerService() { |
|
75 delete this.urlHandlerService; |
|
76 return this.urlHandlerService = Cc["@mozilla.org/uriloader/external-url-handler-service;1"].getService(Ci.nsIExternalURLHandlerService); |
|
77 }, |
|
78 |
|
79 prompt: function showPicker(apps, promptOptions, callback) { |
|
80 let p = new Prompt(promptOptions).addIconGrid({ items: apps }); |
|
81 p.show(callback); |
|
82 }, |
|
83 |
|
84 getAppsForProtocol: function getAppsForProtocol(scheme) { |
|
85 let protoHandlers = this.protoSvc.getProtocolHandlerInfoFromOS(scheme, {}).possibleApplicationHandlers; |
|
86 |
|
87 let results = {}; |
|
88 for (let i = 0; i < protoHandlers.length; i++) { |
|
89 try { |
|
90 let protoApp = protoHandlers.queryElementAt(i, Ci.nsIHandlerApp); |
|
91 results[protoApp.name] = new App({ |
|
92 name: protoApp.name, |
|
93 description: protoApp.detailedDescription, |
|
94 }); |
|
95 } catch(e) {} |
|
96 } |
|
97 |
|
98 return results; |
|
99 }, |
|
100 |
|
101 getAppsForUri: function getAppsForUri(uri, flags = { }, callback) { |
|
102 flags.filterBrowsers = "filterBrowsers" in flags ? flags.filterBrowsers : true; |
|
103 flags.filterHtml = "filterHtml" in flags ? flags.filterHtml : true; |
|
104 |
|
105 // Query for apps that can/can't handle the mimetype |
|
106 let msg = this._getMessage("Intent:GetHandlers", uri, flags); |
|
107 let parseData = (d) => { |
|
108 let apps = [] |
|
109 |
|
110 if (!d) |
|
111 return apps; |
|
112 |
|
113 apps = this._parseApps(d.apps); |
|
114 |
|
115 if (flags.filterBrowsers) { |
|
116 apps = apps.filter((app) => { |
|
117 return app.name && !this.defaultBrowsers[app.name]; |
|
118 }); |
|
119 } |
|
120 |
|
121 // Some apps will register for html files (the Samsung Video player) but should be shown |
|
122 // for non-HTML files (like videos). This filters them only if the page has an htm of html |
|
123 // file extension. |
|
124 if (flags.filterHtml) { |
|
125 // Matches from the first '.' to the end of the string, '?', or '#' |
|
126 let ext = /\.([^\?#]*)/.exec(uri.path); |
|
127 if (ext && (ext[1] === "html" || ext[1] === "htm")) { |
|
128 apps = apps.filter(function(app) { |
|
129 return app.name && !this.defaultHtmlHandlers[app.name]; |
|
130 }, this); |
|
131 } |
|
132 } |
|
133 |
|
134 return apps; |
|
135 }; |
|
136 |
|
137 if (!callback) { |
|
138 let data = this._sendMessageSync(msg); |
|
139 if (!data) |
|
140 return []; |
|
141 return parseData(data); |
|
142 } else { |
|
143 sendMessageToJava(msg, function(data) { |
|
144 callback(parseData(data)); |
|
145 }); |
|
146 } |
|
147 }, |
|
148 |
|
149 launchUri: function launchUri(uri) { |
|
150 let msg = this._getMessage("Intent:Open", uri); |
|
151 sendMessageToJava(msg); |
|
152 }, |
|
153 |
|
154 _parseApps: function _parseApps(appInfo) { |
|
155 // appInfo -> {apps: [app1Label, app1Default, app1PackageName, app1ActivityName, app2Label, app2Defaut, ...]} |
|
156 // see GeckoAppShell.java getHandlersForIntent function for details |
|
157 const numAttr = 4; // 4 elements per ResolveInfo: label, default, package name, activity name. |
|
158 |
|
159 let apps = []; |
|
160 for (let i = 0; i < appInfo.length; i += numAttr) { |
|
161 apps.push(new App({"name" : appInfo[i], |
|
162 "isDefault" : appInfo[i+1], |
|
163 "packageName" : appInfo[i+2], |
|
164 "activityName" : appInfo[i+3]})); |
|
165 } |
|
166 |
|
167 return apps; |
|
168 }, |
|
169 |
|
170 _getMessage: function(type, uri, options = {}) { |
|
171 let mimeType = options.mimeType; |
|
172 if (uri && mimeType == undefined) |
|
173 mimeType = ContentAreaUtils.getMIMETypeForURI(uri) || ""; |
|
174 |
|
175 return { |
|
176 type: type, |
|
177 mime: mimeType, |
|
178 action: options.action || "", // empty action string defaults to android.intent.action.VIEW |
|
179 url: uri ? uri.spec : "", |
|
180 packageName: options.packageName || "", |
|
181 className: options.className || "" |
|
182 }; |
|
183 }, |
|
184 |
|
185 _launchApp: function launchApp(app, uri, callback) { |
|
186 if (callback) { |
|
187 let msg = this._getMessage("Intent:OpenForResult", uri, { |
|
188 packageName: app.packageName, |
|
189 className: app.activityName |
|
190 }); |
|
191 |
|
192 sendMessageToJava(msg, function(data) { |
|
193 callback(data); |
|
194 }); |
|
195 } else { |
|
196 let msg = this._getMessage("Intent:Open", uri, { |
|
197 packageName: app.packageName, |
|
198 className: app.activityName |
|
199 }); |
|
200 |
|
201 sendMessageToJava(msg); |
|
202 } |
|
203 }, |
|
204 |
|
205 _sendMessageSync: function(msg) { |
|
206 let res = null; |
|
207 sendMessageToJava(msg, function(data) { |
|
208 res = data; |
|
209 }); |
|
210 |
|
211 let thread = Services.tm.currentThread; |
|
212 while (res == null) |
|
213 thread.processNextEvent(true); |
|
214 |
|
215 return res; |
|
216 }, |
|
217 }; |