summaryrefslogtreecommitdiffstats
path: root/mobile/android/modules/HelperApps.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/modules/HelperApps.jsm')
-rw-r--r--mobile/android/modules/HelperApps.jsm229
1 files changed, 229 insertions, 0 deletions
diff --git a/mobile/android/modules/HelperApps.jsm b/mobile/android/modules/HelperApps.jsm
new file mode 100644
index 000000000..0ac478da0
--- /dev/null
+++ b/mobile/android/modules/HelperApps.jsm
@@ -0,0 +1,229 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* globals ContentAreaUtils */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
+ "resource://gre/modules/Prompt.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
+ "resource://gre/modules/Messaging.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
+ let ContentAreaUtils = {};
+ Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
+ return ContentAreaUtils;
+});
+
+this.EXPORTED_SYMBOLS = ["App","HelperApps"];
+
+function App(data) {
+ this.name = data.name;
+ this.isDefault = data.isDefault;
+ this.packageName = data.packageName;
+ this.activityName = data.activityName;
+ this.iconUri = "-moz-icon://" + data.packageName;
+}
+
+App.prototype = {
+ // callback will be null if a result is not requested
+ launch: function(uri, callback) {
+ HelperApps._launchApp(this, uri, callback);
+ return false;
+ }
+}
+
+var HelperApps = {
+ get defaultBrowsers() {
+ delete this.defaultBrowsers;
+ this.defaultBrowsers = this._getHandlers("http://www.example.com", {
+ filterBrowsers: false,
+ filterHtml: false
+ });
+ return this.defaultBrowsers;
+ },
+
+ // Finds handlers that have registered for text/html pages or urls ending in html. Some apps, like
+ // the Samsung Video player will only appear for these urls, while some Browsers (like Link Bubble)
+ // won't register here because of the text/html mime type.
+ get defaultHtmlHandlers() {
+ delete this.defaultHtmlHandlers;
+ return this.defaultHtmlHandlers = this._getHandlers("http://www.example.com/index.html", {
+ filterBrowsers: false,
+ filterHtml: false
+ });
+ },
+
+ _getHandlers: function(url, options) {
+ let values = {};
+
+ let handlers = this.getAppsForUri(Services.io.newURI(url, null, null), options);
+ handlers.forEach(function(app) {
+ values[app.name] = app;
+ }, this);
+
+ return values;
+ },
+
+ get protoSvc() {
+ delete this.protoSvc;
+ return this.protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService);
+ },
+
+ get urlHandlerService() {
+ delete this.urlHandlerService;
+ return this.urlHandlerService = Cc["@mozilla.org/uriloader/external-url-handler-service;1"].getService(Ci.nsIExternalURLHandlerService);
+ },
+
+ prompt: function showPicker(apps, promptOptions, callback) {
+ let p = new Prompt(promptOptions).addIconGrid({ items: apps });
+ p.show(callback);
+ },
+
+ getAppsForProtocol: function getAppsForProtocol(scheme) {
+ let protoHandlers = this.protoSvc.getProtocolHandlerInfoFromOS(scheme, {}).possibleApplicationHandlers;
+
+ let results = {};
+ for (let i = 0; i < protoHandlers.length; i++) {
+ try {
+ let protoApp = protoHandlers.queryElementAt(i, Ci.nsIHandlerApp);
+ results[protoApp.name] = new App({
+ name: protoApp.name,
+ description: protoApp.detailedDescription,
+ });
+ } catch(e) {}
+ }
+
+ return results;
+ },
+
+ getAppsForUri: function getAppsForUri(uri, flags = { }, callback) {
+ // Return early for well-known internal schemes
+ if (!uri || uri.schemeIs("about") || uri.schemeIs("chrome")) {
+ if (callback) {
+ callback([]);
+ }
+ return [];
+ }
+
+ flags.filterBrowsers = "filterBrowsers" in flags ? flags.filterBrowsers : true;
+ flags.filterHtml = "filterHtml" in flags ? flags.filterHtml : true;
+
+ // Query for apps that can/can't handle the mimetype
+ let msg = this._getMessage("Intent:GetHandlers", uri, flags);
+ let parseData = (d) => {
+ let apps = []
+ if (!d) {
+ return apps;
+ }
+
+ apps = this._parseApps(d.apps);
+
+ if (flags.filterBrowsers) {
+ apps = apps.filter((app) => {
+ return app.name && !this.defaultBrowsers[app.name];
+ });
+ }
+
+ // Some apps will register for html files (the Samsung Video player) but should be shown
+ // for non-HTML files (like videos). This filters them only if the page has an htm of html
+ // file extension.
+ if (flags.filterHtml) {
+ // Matches from the first '.' to the end of the string, '?', or '#'
+ let ext = /\.([^\?#]*)/.exec(uri.path);
+ if (ext && (ext[1] === "html" || ext[1] === "htm")) {
+ apps = apps.filter(function(app) {
+ return app.name && !this.defaultHtmlHandlers[app.name];
+ }, this);
+ }
+ }
+
+ return apps;
+ };
+
+ if (!callback) {
+ let data = this._sendMessageSync(msg);
+ return parseData(data);
+ } else {
+ Messaging.sendRequestForResult(msg).then(function(data) {
+ callback(parseData(data));
+ });
+ }
+ },
+
+ launchUri: function launchUri(uri) {
+ let msg = this._getMessage("Intent:Open", uri);
+ Messaging.sendRequest(msg);
+ },
+
+ _parseApps: function _parseApps(appInfo) {
+ // appInfo -> {apps: [app1Label, app1Default, app1PackageName, app1ActivityName, app2Label, app2Defaut, ...]}
+ // see GeckoAppShell.java getHandlersForIntent function for details
+ const numAttr = 4; // 4 elements per ResolveInfo: label, default, package name, activity name.
+
+ let apps = [];
+ for (let i = 0; i < appInfo.length; i += numAttr) {
+ apps.push(new App({"name" : appInfo[i],
+ "isDefault" : appInfo[i+1],
+ "packageName" : appInfo[i+2],
+ "activityName" : appInfo[i+3]}));
+ }
+
+ return apps;
+ },
+
+ _getMessage: function(type, uri, options = {}) {
+ let mimeType = options.mimeType;
+ if (uri && mimeType == undefined) {
+ mimeType = ContentAreaUtils.getMIMETypeForURI(uri) || "";
+ }
+
+ return {
+ type: type,
+ mime: mimeType,
+ action: options.action || "", // empty action string defaults to android.intent.action.VIEW
+ url: uri ? uri.spec : "",
+ packageName: options.packageName || "",
+ className: options.className || ""
+ };
+ },
+
+ _launchApp: function launchApp(app, uri, callback) {
+ if (callback) {
+ let msg = this._getMessage("Intent:OpenForResult", uri, {
+ packageName: app.packageName,
+ className: app.activityName
+ });
+
+ Messaging.sendRequestForResult(msg).then(callback);
+ } else {
+ let msg = this._getMessage("Intent:Open", uri, {
+ packageName: app.packageName,
+ className: app.activityName
+ });
+
+ Messaging.sendRequest(msg);
+ }
+ },
+
+ _sendMessageSync: function(msg) {
+ let res = null;
+ Messaging.sendRequestForResult(msg).then(function(data) {
+ res = data;
+ });
+
+ let thread = Services.tm.currentThread;
+ while (res == null) {
+ thread.processNextEvent(true);
+ }
+
+ return res;
+ },
+};