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