Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
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";
6 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
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");
13 XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
14 let ContentAreaUtils = {};
15 Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
16 return ContentAreaUtils;
17 });
19 this.EXPORTED_SYMBOLS = ["App","HelperApps"];
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 }
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 }
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 },
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 },
58 _getHandlers: function(url, options) {
59 let values = {};
61 let handlers = this.getAppsForUri(Services.io.newURI(url, null, null), options);
62 handlers.forEach(function(app) {
63 values[app.name] = app;
64 }, this);
66 return values;
67 },
69 get protoSvc() {
70 delete this.protoSvc;
71 return this.protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService);
72 },
74 get urlHandlerService() {
75 delete this.urlHandlerService;
76 return this.urlHandlerService = Cc["@mozilla.org/uriloader/external-url-handler-service;1"].getService(Ci.nsIExternalURLHandlerService);
77 },
79 prompt: function showPicker(apps, promptOptions, callback) {
80 let p = new Prompt(promptOptions).addIconGrid({ items: apps });
81 p.show(callback);
82 },
84 getAppsForProtocol: function getAppsForProtocol(scheme) {
85 let protoHandlers = this.protoSvc.getProtocolHandlerInfoFromOS(scheme, {}).possibleApplicationHandlers;
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 }
98 return results;
99 },
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;
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 = []
110 if (!d)
111 return apps;
113 apps = this._parseApps(d.apps);
115 if (flags.filterBrowsers) {
116 apps = apps.filter((app) => {
117 return app.name && !this.defaultBrowsers[app.name];
118 });
119 }
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 }
134 return apps;
135 };
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 },
149 launchUri: function launchUri(uri) {
150 let msg = this._getMessage("Intent:Open", uri);
151 sendMessageToJava(msg);
152 },
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.
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 }
167 return apps;
168 },
170 _getMessage: function(type, uri, options = {}) {
171 let mimeType = options.mimeType;
172 if (uri && mimeType == undefined)
173 mimeType = ContentAreaUtils.getMIMETypeForURI(uri) || "";
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 },
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 });
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 });
201 sendMessageToJava(msg);
202 }
203 },
205 _sendMessageSync: function(msg) {
206 let res = null;
207 sendMessageToJava(msg, function(data) {
208 res = data;
209 });
211 let thread = Services.tm.currentThread;
212 while (res == null)
213 thread.processNextEvent(true);
215 return res;
216 },
217 };