1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/modules/HelperApps.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,217 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 +"use strict"; 1.8 + 1.9 +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; 1.10 + 1.11 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.12 +Cu.import("resource://gre/modules/Services.jsm"); 1.13 +Cu.import("resource://gre/modules/Prompt.jsm"); 1.14 +Cu.import("resource://gre/modules/Messaging.jsm"); 1.15 + 1.16 +XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() { 1.17 + let ContentAreaUtils = {}; 1.18 + Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils); 1.19 + return ContentAreaUtils; 1.20 +}); 1.21 + 1.22 +this.EXPORTED_SYMBOLS = ["App","HelperApps"]; 1.23 + 1.24 +function App(data) { 1.25 + this.name = data.name; 1.26 + this.isDefault = data.isDefault; 1.27 + this.packageName = data.packageName; 1.28 + this.activityName = data.activityName; 1.29 + this.iconUri = "-moz-icon://" + data.packageName; 1.30 +} 1.31 + 1.32 +App.prototype = { 1.33 + // callback will be null if a result is not requested 1.34 + launch: function(uri, callback) { 1.35 + HelperApps._launchApp(this, uri, callback); 1.36 + return false; 1.37 + } 1.38 +} 1.39 + 1.40 +var HelperApps = { 1.41 + get defaultBrowsers() { 1.42 + delete this.defaultBrowsers; 1.43 + this.defaultBrowsers = this._getHandlers("http://www.example.com", { 1.44 + filterBrowsers: false, 1.45 + filterHtml: false 1.46 + }); 1.47 + return this.defaultBrowsers; 1.48 + }, 1.49 + 1.50 + // Finds handlers that have registered for text/html pages or urls ending in html. Some apps, like 1.51 + // the Samsung Video player will only appear for these urls, while some Browsers (like Link Bubble) 1.52 + // won't register here because of the text/html mime type. 1.53 + get defaultHtmlHandlers() { 1.54 + delete this.defaultHtmlHandlers; 1.55 + return this.defaultHtmlHandlers = this._getHandlers("http://www.example.com/index.html", { 1.56 + filterBrowsers: false, 1.57 + filterHtml: false 1.58 + }); 1.59 + }, 1.60 + 1.61 + _getHandlers: function(url, options) { 1.62 + let values = {}; 1.63 + 1.64 + let handlers = this.getAppsForUri(Services.io.newURI(url, null, null), options); 1.65 + handlers.forEach(function(app) { 1.66 + values[app.name] = app; 1.67 + }, this); 1.68 + 1.69 + return values; 1.70 + }, 1.71 + 1.72 + get protoSvc() { 1.73 + delete this.protoSvc; 1.74 + return this.protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService); 1.75 + }, 1.76 + 1.77 + get urlHandlerService() { 1.78 + delete this.urlHandlerService; 1.79 + return this.urlHandlerService = Cc["@mozilla.org/uriloader/external-url-handler-service;1"].getService(Ci.nsIExternalURLHandlerService); 1.80 + }, 1.81 + 1.82 + prompt: function showPicker(apps, promptOptions, callback) { 1.83 + let p = new Prompt(promptOptions).addIconGrid({ items: apps }); 1.84 + p.show(callback); 1.85 + }, 1.86 + 1.87 + getAppsForProtocol: function getAppsForProtocol(scheme) { 1.88 + let protoHandlers = this.protoSvc.getProtocolHandlerInfoFromOS(scheme, {}).possibleApplicationHandlers; 1.89 + 1.90 + let results = {}; 1.91 + for (let i = 0; i < protoHandlers.length; i++) { 1.92 + try { 1.93 + let protoApp = protoHandlers.queryElementAt(i, Ci.nsIHandlerApp); 1.94 + results[protoApp.name] = new App({ 1.95 + name: protoApp.name, 1.96 + description: protoApp.detailedDescription, 1.97 + }); 1.98 + } catch(e) {} 1.99 + } 1.100 + 1.101 + return results; 1.102 + }, 1.103 + 1.104 + getAppsForUri: function getAppsForUri(uri, flags = { }, callback) { 1.105 + flags.filterBrowsers = "filterBrowsers" in flags ? flags.filterBrowsers : true; 1.106 + flags.filterHtml = "filterHtml" in flags ? flags.filterHtml : true; 1.107 + 1.108 + // Query for apps that can/can't handle the mimetype 1.109 + let msg = this._getMessage("Intent:GetHandlers", uri, flags); 1.110 + let parseData = (d) => { 1.111 + let apps = [] 1.112 + 1.113 + if (!d) 1.114 + return apps; 1.115 + 1.116 + apps = this._parseApps(d.apps); 1.117 + 1.118 + if (flags.filterBrowsers) { 1.119 + apps = apps.filter((app) => { 1.120 + return app.name && !this.defaultBrowsers[app.name]; 1.121 + }); 1.122 + } 1.123 + 1.124 + // Some apps will register for html files (the Samsung Video player) but should be shown 1.125 + // for non-HTML files (like videos). This filters them only if the page has an htm of html 1.126 + // file extension. 1.127 + if (flags.filterHtml) { 1.128 + // Matches from the first '.' to the end of the string, '?', or '#' 1.129 + let ext = /\.([^\?#]*)/.exec(uri.path); 1.130 + if (ext && (ext[1] === "html" || ext[1] === "htm")) { 1.131 + apps = apps.filter(function(app) { 1.132 + return app.name && !this.defaultHtmlHandlers[app.name]; 1.133 + }, this); 1.134 + } 1.135 + } 1.136 + 1.137 + return apps; 1.138 + }; 1.139 + 1.140 + if (!callback) { 1.141 + let data = this._sendMessageSync(msg); 1.142 + if (!data) 1.143 + return []; 1.144 + return parseData(data); 1.145 + } else { 1.146 + sendMessageToJava(msg, function(data) { 1.147 + callback(parseData(data)); 1.148 + }); 1.149 + } 1.150 + }, 1.151 + 1.152 + launchUri: function launchUri(uri) { 1.153 + let msg = this._getMessage("Intent:Open", uri); 1.154 + sendMessageToJava(msg); 1.155 + }, 1.156 + 1.157 + _parseApps: function _parseApps(appInfo) { 1.158 + // appInfo -> {apps: [app1Label, app1Default, app1PackageName, app1ActivityName, app2Label, app2Defaut, ...]} 1.159 + // see GeckoAppShell.java getHandlersForIntent function for details 1.160 + const numAttr = 4; // 4 elements per ResolveInfo: label, default, package name, activity name. 1.161 + 1.162 + let apps = []; 1.163 + for (let i = 0; i < appInfo.length; i += numAttr) { 1.164 + apps.push(new App({"name" : appInfo[i], 1.165 + "isDefault" : appInfo[i+1], 1.166 + "packageName" : appInfo[i+2], 1.167 + "activityName" : appInfo[i+3]})); 1.168 + } 1.169 + 1.170 + return apps; 1.171 + }, 1.172 + 1.173 + _getMessage: function(type, uri, options = {}) { 1.174 + let mimeType = options.mimeType; 1.175 + if (uri && mimeType == undefined) 1.176 + mimeType = ContentAreaUtils.getMIMETypeForURI(uri) || ""; 1.177 + 1.178 + return { 1.179 + type: type, 1.180 + mime: mimeType, 1.181 + action: options.action || "", // empty action string defaults to android.intent.action.VIEW 1.182 + url: uri ? uri.spec : "", 1.183 + packageName: options.packageName || "", 1.184 + className: options.className || "" 1.185 + }; 1.186 + }, 1.187 + 1.188 + _launchApp: function launchApp(app, uri, callback) { 1.189 + if (callback) { 1.190 + let msg = this._getMessage("Intent:OpenForResult", uri, { 1.191 + packageName: app.packageName, 1.192 + className: app.activityName 1.193 + }); 1.194 + 1.195 + sendMessageToJava(msg, function(data) { 1.196 + callback(data); 1.197 + }); 1.198 + } else { 1.199 + let msg = this._getMessage("Intent:Open", uri, { 1.200 + packageName: app.packageName, 1.201 + className: app.activityName 1.202 + }); 1.203 + 1.204 + sendMessageToJava(msg); 1.205 + } 1.206 + }, 1.207 + 1.208 + _sendMessageSync: function(msg) { 1.209 + let res = null; 1.210 + sendMessageToJava(msg, function(data) { 1.211 + res = data; 1.212 + }); 1.213 + 1.214 + let thread = Services.tm.currentThread; 1.215 + while (res == null) 1.216 + thread.processNextEvent(true); 1.217 + 1.218 + return res; 1.219 + }, 1.220 +};