Bug 741621 - Allow opening market links in the market webapp. r=mfinkle

This commit is contained in:
Wes Johnston 2012-08-20 14:35:44 -07:00
parent ff04ad6299
commit 67f380bd76
11 changed files with 280 additions and 11 deletions

View File

@ -93,6 +93,7 @@
<data android:mimeType="text/plain"/>
<data android:mimeType="application/xhtml+xml"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
@ -111,6 +112,25 @@
#include @OBJDIR@/WebAppManifestFragment.xml.in
<activity android:name=".MarketplaceApp"
android:label="Mozilla Marketplace"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize"
android:windowSoftInputMode="stateUnspecified|adjustResize"
android:icon="@drawable/marketplace"
android:launchMode="singleTask"
android:theme="@style/Gecko.NoActionBar"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="marketplace.mozilla.org"/>
<data android:scheme="https" android:host="marketplace.mozilla.org"/>
</intent-filter>
</activity>
<!-- Masquerade as the Resolver so that we can be opened from the Marketplace. -->
<activity-alias
android:name="com.android.internal.app.ResolverActivity"

View File

@ -171,7 +171,7 @@ abstract public class GeckoApp
private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
protected int mRestoreMode = GeckoAppShell.RESTORE_NONE;
private boolean mInitialized = false;
protected boolean mInitialized = false;
public enum LaunchState {Launching, WaitForDebugger,
Launched, GeckoRunning, GeckoExiting};
@ -1921,7 +1921,7 @@ abstract public class GeckoApp
* Handles getting a uri from and intent in a way that is backwards
* compatable with our previous implementations
*/
private String getURIFromIntent(Intent intent) {
protected String getURIFromIntent(Intent intent) {
String uri = intent.getDataString();
if (uri != null)
return uri;

View File

@ -817,10 +817,14 @@ public class GeckoAppShell
if (index == -1)
return null;
return getWebAppIntent(index, aURI);
}
public static Intent getWebAppIntent(int aIndex, String aURI) {
Intent intent = new Intent();
intent.setAction(GeckoApp.ACTION_WEBAPP_PREFIX + index);
intent.setAction(GeckoApp.ACTION_WEBAPP_PREFIX + aIndex);
intent.setData(Uri.parse(aURI));
intent.setClassName(GeckoApp.mAppContext, GeckoApp.mAppContext.getPackageName() + ".WebApps$WebApp" + index);
intent.setClassName(GeckoApp.mAppContext, GeckoApp.mAppContext.getPackageName() + ".WebApps$WebApp" + aIndex);
return intent;
}

View File

@ -168,6 +168,7 @@ endif
FENNEC_PP_JAVA_FILES = \
App.java \
MarketplaceApp.java \
WebApp.java \
WebApps.java \
GeckoActivity.java \
@ -998,6 +999,7 @@ MOZ_ANDROID_DRAWABLES += \
mobile/android/base/resources/drawable/tabs_tray_list_divider.xml \
mobile/android/base/resources/drawable/tabs_shadow.xml \
mobile/android/base/resources/drawable/shadow.png \
mobile/android/base/resources/drawable/marketplace.png \
$(NULL)
MOZ_ANDROID_DRAWABLES += $(shell if test -e $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn; then cat $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn | tr '\n' ' '; fi)

View File

@ -0,0 +1,114 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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/. */
#filter substitution
package @ANDROID_PACKAGE_NAME@;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.MenuItem;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.WebAppAllocator;
/*
* This is a stub activity, meant to just install the marketplace WebApp
* and then launch it
*/
public class MarketplaceApp extends WebApp {
protected int mWebAppIndex;
private static final String LOGTAG = "GeckoMarketplaceApp";
public static final String MARKETPLACE_HOST = "marketplace.mozilla.org";
public static final String MARKETPLACE_URI = "https://marketplace.mozilla.org";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Check that the uri being requested is a marketplace uri
String passedUri = null;
String uri = getURIFromIntent(getIntent());
if (uri != null && uri.length() > 0) {
passedUri = uri;
}
if (passedUri != null)
handleMarketplaceLink(passedUri);
}
// Use the default profile in order to ensure this is installed in the Fennec
// webapp registry. The marketplace WEBapp will have its own profile
@Override
protected String getDefaultProfileName() {
return "default";
}
@Override
protected void onNewIntent(Intent intent) {
Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - onNewIntent");
if (checkLaunchState(LaunchState.GeckoExiting)) {
// We're exiting and shouldn't try to do anything else just incase
// we're hung for some reason we'll force the process to exit
System.exit(0);
return;
}
// if we were previously OOM killed, we can end up here when launching
// from external shortcuts, so set this as the intent for initialization
if (!mInitialized) {
setIntent(intent);
return;
}
final String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
String uri = intent.getDataString();
if (Uri.parse(uri).getHost().equals(MARKETPLACE_HOST)) {
handleMarketplaceLink(uri);
return;
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
Log.i(LOGTAG,"onNewIntent: " + uri);
}
}
// All marketplace links from the system will come to the receiver first. It will determine if the
// marketplace app is installed. If the marketplace is not installed it will install it and send the link
// to the marketplace app
private void handleMarketplaceLink(final String url) {
// see if the marketplace app is registered, if its not, install it
int index = WebAppAllocator.getInstance(this).getIndexForApp(MARKETPLACE_URI);
if (index < 0) {
// If the app isn't installed, we send gecko a message to install it and then launch it with this url
JSONObject args = new JSONObject();
try {
args.put("url", url);
} catch (Exception e) {
Log.e(LOGTAG, "error building JSON arguments");
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("WebApps:InstallMarketplace", args.toString()));
} else {
// otherwise just launch the webapp with this url
Intent webappIntent = GeckoAppShell.getWebAppIntent(index, url);
if (webappIntent == null) {
Log.i(LOGTAG, "bounce launch");
return;
}
Log.i(LOGTAG, "Open " + url + " in marketplace app");
startActivity(webappIntent);
}
}
};

View File

@ -22,13 +22,18 @@ public class WebAppAllocator {
if (sInstance == null) {
if (!(cx instanceof GeckoApp))
throw new RuntimeException("Context needs to be a GeckoApp");
sContext = (GeckoApp) cx;
sInstance = new WebAppAllocator(cx);
}
if (cx != sContext)
throw new RuntimeException("Tried to get WebAppAllocator instance for different context than it was created for");
// The marketplaceApp will run in this same process, but has a different context
// Rather than just failing, we want to create a new Allocator instead
if (cx != sContext) {
sInstance = null;
sContext = (GeckoApp) cx;
sInstance = new WebAppAllocator(cx);
}
return sInstance;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -48,7 +48,7 @@ var HelperApps = {
return found;
},
openUriInApp: function openUriInApp(uri) {
openUriInApp: function openUriInApp(uri) {
var possibleHandlers = this.getAppsForUri(uri);
if (possibleHandlers.length == 1) {
possibleHandlers[0].launchWithURI(uri);
@ -56,5 +56,50 @@ var HelperApps = {
let handlerInfoProto = this.urlHandlerService.getURLHandlerInfoFromOS(uri, {});
handlerInfoProto.preferredApplicationHandler.launchWithURI(uri);
}
},
showDoorhanger: function showDoorhanger(aUri, aCallback) {
let permValue = Services.perms.testPermission(aUri, "native-intent");
if (permValue != Services.perms.UNKNOWN_ACTION) {
if (permValue == Services.perms.ALLOW_ACTION) {
if (aCallback)
aCallback(aUri);
this.openUriInApp(aUri);
} else if (permValue == Services.perms.DENY_ACTION) {
// do nothing
}
return;
}
let apps = this.getAppsForUri(aUri);
let strings = Strings.browser;
let message = "";
if (apps.length == 1)
message = strings.formatStringFromName("helperapps.openWithApp2", [apps[0].name], 1);
else
message = strings.GetStringFromName("helperapps.openWithList2");
let buttons = [{
label: strings.GetStringFromName("helperapps.open"),
callback: function(aChecked) {
if (aChecked)
Services.perms.add(aUri, "native-intent", Ci.nsIPermissionManager.ALLOW_ACTION);
if (aCallback)
aCallback(aUri);
else
this.openUriInApp(aUri);
}
}, {
label: strings.GetStringFromName("helperapps.ignore"),
callback: function(aChecked) {
if (aChecked)
Services.perms.add(aUri, "native-intent", Ci.nsIPermissionManager.DENY_ACTION);
}
}];
let options = { checkbox: Strings.browser.GetStringFromName("helperapps.dontAskAgain") };
NativeWindow.doorhanger.show(message, "helperapps-open", buttons, BrowserApp.selectedTab.id, options);
}
};

View File

@ -38,6 +38,11 @@ var WebAppRT = {
if (isUpdate == "new") {
this.getDefaultPrefs().forEach(this.addPref);
// prevent offering to use helper apps for things that this app handles
// i.e. don't show the "Open in market?" popup when we're showing the market app
let uri = Services.io.newURI(url, null, null);
Services.perms.add(uri, "native-intent", Ci.nsIPermissionManager.DENY_ACTION);
// update the blocklist url to use a different app id
let blocklist = Services.prefs.getCharPref("extensions.blocklist.url");
blocklist = blocklist.replace(/%APP_ID%/g, "webapprt-mobile@mozilla.org");

View File

@ -247,7 +247,7 @@ var BrowserApp = {
let updated = this.isAppUpdated();
if (pinned) {
WebAppRT.init(updated);
WebAppRT.init(updated, url);
} else {
SearchEngines.init();
this.initContextMenu();
@ -1990,6 +1990,7 @@ nsBrowserAccess.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow]),
_getBrowser: function _getBrowser(aURI, aOpener, aWhere, aContext) {
console.log("GetBrowser");
let isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
if (isExternal && aURI && aURI.schemeIs("chrome"))
return null;
@ -2728,6 +2729,7 @@ Tab.prototype = {
}
case "DOMWillOpenModalDialog": {
console.log("DOMWillOpenModalDialog");
if (!aEvent.isTrusted)
return;
@ -2926,7 +2928,7 @@ Tab.prototype = {
let documentURI = contentWin.document.documentURIObject.spec;
let contentType = contentWin.document.contentType;
// If fixedURI matches browser.lastURI, we assume this isn't a real location
// change but rather a spurious addition like a wyciwyg URI prefix. See Bug 747883.
// Note that we have to ensure fixedURI is not the same as aLocationURI so we
@ -2941,6 +2943,18 @@ Tab.prototype = {
this.shouldShowPluginDoorhanger = true;
this.clickToPlayPluginsActivated = false;
// This is where we might check for helper apps.
// For now it is special cased to only check for the marketplace urls
if (WebappsUI.isMarketplace(aLocationURI)) {
// the marketplace app may not actually be installed, so instead we use a custom
// callback that will install and launch it for us if necessary
HelperApps.showDoorhanger(aLocationURI, function() {
WebappsUI.installAndLaunchMarketplace(aLocationURI.spec);
if (aRequest)
aRequest.cancel(Cr.NS_OK);
});
}
let message = {
gecko: {
type: "Content:LocationChange",
@ -5951,6 +5965,7 @@ var WebappsUI = {
Services.obs.addObserver(this, "webapps-sync-install", false);
Services.obs.addObserver(this, "webapps-sync-uninstall", false);
Services.obs.addObserver(this, "webapps-install-error", false);
Services.obs.addObserver(this, "WebApps:InstallMarketplace", false);
},
uninit: function unint() {
@ -5959,6 +5974,7 @@ var WebappsUI = {
Services.obs.removeObserver(this, "webapps-sync-install");
Services.obs.removeObserver(this, "webapps-sync-uninstall");
Services.obs.removeObserver(this, "webapps-install-error", false);
Services.obs.removeObserver(this, "WebApps:InstallMarketplace", false);
},
DEFAULT_PREFS_FILENAME: "default-prefs.js",
@ -6024,6 +6040,55 @@ var WebappsUI = {
}
});
break;
case "WebApps:InstallMarketplace":
this.installAndLaunchMarketplace(data.url);
break;
}
},
MARKETPLACE: {
MANIFEST: "https://marketplace.mozilla.org/manifest.webapp",
get URI() {
delete this.URI;
return this.URI = Services.io.newURI(this.MANIFEST, null, null);
}
},
isMarketplace: function isMarketplace(aUri) {
return aUri.host == this.MARKETPLACE.URI.host;
},
// installs the marketplace, if a url is passed in, will launch it when the install
// is complete
installAndLaunchMarketplace: function installAndLaunchMarketplace(aLaunchUrl) {
// TODO: Add a flag to hide other install prompt dialogs. This should be silent if possible
let request = navigator.mozApps.getInstalled();
request.onsuccess = function() {
let foundMarket = false;
for (let i = 0; i < request.result.length; i++) {
if (request.result[i].origin == this.MARKETPLACE.URI.prePath)
foundMarket = true;
}
let launchFun = (function() {
if (aLaunchUrl)
WebappsUI.openURL(aLaunchUrl || WebappsUI.MARKETPLACE.URI.prePath, WebappsUI.MARKETPLACE.URI.prePath);
}).bind(this);
if (foundMarket) {
launchFun();
} else {
let r = navigator.mozApps.install(WebappsUI.MARKETPLACE.MANIFEST);
r.onsuccess = function() {
launchFun();
};
r.onerror = function() {
console.log("error installing market " + this.error.name);
};
}
};
request.onerror = function() {
console.log("error getting installed " + this.error.name);
}
},
@ -6063,7 +6128,13 @@ var WebappsUI = {
doInstall: function doInstall(aData) {
let manifest = new DOMApplicationManifest(aData.app.manifest, aData.app.origin);
let name = manifest.name ? manifest.name : manifest.fullLaunchPath();
if (Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), name)) {
let showPrompt = true;
// skip showing the prompt if this is for the marketplace app
if (aData.app.origin == this.MARKETPLACE.URI.prePath)
showPrompt = false;
if (!showPrompt || Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), name)) {
// Add a homescreen shortcut -- we can't use createShortcut, since we need to pass
// a unique ID for Android webapp allocation
this.makeBase64Icon(this.getBiggestIcon(manifest.icons, Services.io.newURI(aData.app.origin, null, null)),

View File

@ -278,5 +278,8 @@ remoteIncomingPromptMessage=An incoming request to permit remote debugging conne
remoteIncomingPromptDisable=Disable
# Helper apps
helperapps.open=Open
helperapps.ignore=Ignore
helperapps.dontAskAgain=Don't ask again for this site
helperapps.openWithApp2=Open With %S App
helperapps.openWithList2=Open With an App