Bug 1168980 - Search app store when there is no app to handle intent:// URI. r=margaret

This commit is contained in:
Michael Comella 2015-06-10 17:26:27 -07:00
parent 197bbfbbbf
commit 882413144c
5 changed files with 81 additions and 30 deletions

View File

@ -16,8 +16,12 @@ import org.json.JSONObject;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
@ -27,8 +31,14 @@ public final class IntentHelper implements GeckoEventListener {
"Intent:GetHandlers",
"Intent:Open",
"Intent:OpenForResult",
"Intent:OpenNoHandler",
"WebActivity:Open"
};
// via http://developer.android.com/distribute/tools/promote/linking.html
private static String MARKET_INTENT_URI_PACKAGE_PREFIX = "market://details?id=";
private static String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
private static IntentHelper instance;
private final Activity activity;
@ -64,6 +74,8 @@ public final class IntentHelper implements GeckoEventListener {
open(message);
} else if (event.equals("Intent:OpenForResult")) {
openForResult(message);
} else if (event.equals("Intent:OpenNoHandler")) {
openNoHandler(message);
} else if (event.equals("WebActivity:Open")) {
openWebActivity(message);
}
@ -105,6 +117,66 @@ public final class IntentHelper implements GeckoEventListener {
ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));
}
/**
* Opens a URI without any valid handlers on device. In the best case, a package is specified
* and we can bring the user directly to the application page in an app market. If a package is
* not specified and there is a fallback url in the intent extras, we open that url. If neither
* is present, we alert the user that we were unable to open the link.
*/
private void openNoHandler(final JSONObject msg) {
final String uri = msg.optString("uri");
if (TextUtils.isEmpty(uri)) {
displayToastCannotOpenLink();
Log.w(LOGTAG, "Received empty URL. Ignoring...");
return;
}
final Intent intent;
try {
// TODO (bug 1173626): This will not handle android-app uris on non 5.1 devices.
intent = Intent.parseUri(uri, 0);
} catch (final URISyntaxException e) {
displayToastCannotOpenLink();
// Don't log the exception to prevent leaking URIs.
Log.w(LOGTAG, "Unable to parse Intent URI");
return;
}
// For this flow, we follow Chrome's lead:
// https://developer.chrome.com/multidevice/android/intents
//
// Note on alternative flows: we could get the intent package from a component, however, for
// security reasons, components are ignored when opening URIs (bug 1168998) so we should
// ignore it here too.
//
// Our old flow used to prompt the user to search for their app in the market by scheme and
// while this could help the user find a new app, there is not always a correlation in
// scheme to application name and we could end up steering the user wrong (potentially to
// malicious software). Better to leave that one alone.
if (intent.getPackage() != null) {
final String marketUri = MARKET_INTENT_URI_PACKAGE_PREFIX + intent.getPackage();
final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
marketIntent.addCategory(Intent.CATEGORY_BROWSABLE);
marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(marketIntent);
} else if (intent.hasExtra(EXTRA_BROWSER_FALLBACK_URL)) {
final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
Tabs.getInstance().loadUrl(fallbackUrl);
} else {
displayToastCannotOpenLink();
// Don't log the URI to prevent leaking it.
Log.w(LOGTAG, "Unable to handle URI");
}
}
private void displayToastCannotOpenLink() {
final String errText = activity.getResources().getString(R.string.intent_uri_cannot_open);
Toast.makeText(activity, errText, Toast.LENGTH_LONG).show();
}
private void openWebActivity(JSONObject message) throws JSONException {
final Intent intent = WebActivityMapper.getIntentForWebActivity(message.getJSONObject("activity"));
ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));

View File

@ -659,3 +659,5 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!-- LOCALIZATION NOTE (find_matchcase): This is meant to appear as an icon that changes color
if match-case is activated. i.e. No more than two letters, one uppercase, one lowercase. -->
<!ENTITY find_matchcase "Aa">
<!ENTITY intent_uri_cannot_open "Cannot open link">

View File

@ -538,4 +538,6 @@
<string name="percent">&percent;</string>
<string name="remote_tabs_last_synced">&remote_tabs_last_synced;</string>
<string name="intent_uri_cannot_open">&intent_uri_cannot_open;</string>
</resources>

View File

@ -25,14 +25,6 @@ ContentDispatchChooser.prototype =
return this._protoSvc;
},
_getChromeWin: function getChromeWin() {
try {
return Services.wm.getMostRecentWindow("navigator:browser");
} catch (e) {
throw Cr.NS_ERROR_FAILURE;
}
},
ask: function ask(aHandler, aWindowContext, aURI, aReason) {
let window = null;
try {
@ -49,26 +41,12 @@ ContentDispatchChooser.prototype =
if (aHandler.possibleApplicationHandlers.length > 1) {
aHandler.launchWithURI(aURI, aWindowContext);
} else {
let win = this._getChromeWin();
if (win && win.NativeWindow) {
let bundle = Services.strings.createBundle("chrome://browser/locale/handling.properties");
let failedText = bundle.GetStringFromName("protocol.failed");
let searchText = bundle.GetStringFromName("protocol.toast.search");
let msg = {
type: "Intent:OpenNoHandler",
uri: aURI.spec,
};
win.NativeWindow.toast.show(failedText, "long", {
button: {
label: searchText,
callback: function() {
let message = {
type: "Intent:Open",
url: "market://search?q=" + aURI.scheme,
};
Messaging.sendRequest(message);
}
}
});
}
Messaging.sendRequest(msg);
}
},
};

View File

@ -3,6 +3,3 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
download.blocked=Unable to download file
protocol.failed=Couldn't find an app to open this link
# A very short string shown in the button toast when no application can open the url
protocol.toast.search=Search