Bug 1258450 - Automated (mostly) refactorings moving GeckoAppShell static methods into IntentHelper. r=snorp

MozReview-Commit-ID: JbfR3pVFxDK

--HG--
extra : rebase_source : db506aa89a8354e818e4b2c2415585710b3b7fa1
extra : source : 462e9e4314eff25e00e65e75359274ec09807eee
This commit is contained in:
Nick Alexander 2016-03-30 10:21:29 -07:00
parent 912d1a7b9e
commit 7296d618b7
6 changed files with 333 additions and 334 deletions

View File

@ -3508,7 +3508,7 @@ public class BrowserApp extends GeckoApp
// Context: Sharing via chrome list (no explicit session is active)
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "menu");
GeckoAppShell.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, tab.getDisplayTitle(), false);
IntentHelper.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, tab.getDisplayTitle(), false);
}
}
return true;

View File

@ -10,7 +10,6 @@ import android.widget.AdapterView;
import android.widget.Button;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.URLMetadataTable;
import org.mozilla.gecko.favicons.Favicons;
@ -61,7 +60,6 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
@ -94,7 +92,6 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.AbsoluteLayout;
@ -639,7 +636,7 @@ public abstract class GeckoApp
if (tab != null) {
title = tab.getDisplayTitle();
}
GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, title, false);
IntentHelper.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, title, false);
// Context: Sharing via chrome list (no explicit session is active)
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "text");

View File

@ -15,7 +15,6 @@ import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -35,7 +34,6 @@ import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PanZoomController;
import org.mozilla.gecko.overlays.ui.ShareDialog;
import org.mozilla.gecko.permissions.Permissions;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoRequest;
@ -46,10 +44,8 @@ import org.mozilla.gecko.util.NativeJSContainer;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ProxySelector;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.ExternalIntentDuringPrivateBrowsingPromptFragment;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlarmManager;
@ -89,9 +85,7 @@ import android.os.Environment;
import android.os.Looper;
import android.os.SystemClock;
import android.os.Vibrator;
import android.provider.Browser;
import android.provider.Settings;
import android.support.v4.app.FragmentActivity;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
@ -841,10 +835,10 @@ public class GeckoAppShell
@WrapForJNI(stubName = "GetHandlersForMimeTypeWrapper")
static String[] getHandlersForMimeType(String aMimeType, String aAction) {
Intent intent = getIntentForActionString(aAction);
Intent intent = IntentHelper.getIntentForActionString(aAction);
if (aMimeType != null && aMimeType.length() > 0)
intent.setType(aMimeType);
return getHandlersForIntent(intent);
return IntentHelper.getHandlersForIntent(intent);
}
@WrapForJNI(stubName = "GetHandlersForURLWrapper")
@ -852,10 +846,10 @@ public class GeckoAppShell
// aURL may contain the whole URL or just the protocol
Uri uri = aURL.indexOf(':') >= 0 ? Uri.parse(aURL) : new Uri.Builder().scheme(aURL).build();
Intent intent = getOpenURIIntent(getApplicationContext(), uri.toString(), "",
Intent intent = IntentHelper.getOpenURIIntent(getApplicationContext(), uri.toString(), "",
TextUtils.isEmpty(aAction) ? Intent.ACTION_VIEW : aAction, "");
return getHandlersForIntent(intent);
return IntentHelper.getHandlersForIntent(intent);
}
@WrapForJNI(stubName = "GetHWEncoderCapability")
@ -883,47 +877,6 @@ public class GeckoAppShell
return list;
}
static boolean hasHandlersForIntent(Intent intent) {
try {
return !queryIntentActivities(intent).isEmpty();
} catch (Exception ex) {
Log.e(LOGTAG, "Exception in GeckoAppShell.hasHandlersForIntent");
return false;
}
}
static String[] getHandlersForIntent(Intent intent) {
final PackageManager pm = getApplicationContext().getPackageManager();
try {
final List<ResolveInfo> list = queryIntentActivities(intent);
int numAttr = 4;
final String[] ret = new String[list.size() * numAttr];
for (int i = 0; i < list.size(); i++) {
ResolveInfo resolveInfo = list.get(i);
ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
if (resolveInfo.isDefault)
ret[i * numAttr + 1] = "default";
else
ret[i * numAttr + 1] = "";
ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
ret[i * numAttr + 3] = resolveInfo.activityInfo.name;
}
return ret;
} catch (Exception ex) {
Log.e(LOGTAG, "Exception in GeckoAppShell.getHandlersForIntent");
return new String[0];
}
}
static Intent getIntentForActionString(String aAction) {
// Default to the view action if no other action as been specified.
if (TextUtils.isEmpty(aAction)) {
return new Intent(Intent.ACTION_VIEW);
}
return new Intent(aAction);
}
@WrapForJNI(stubName = "GetExtensionFromMimeTypeWrapper")
static String getExtensionFromMimeType(String aMimeType) {
return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType);
@ -975,277 +928,9 @@ public class GeckoAppShell
String className,
String action,
String title) {
// Default to showing prompt in private browsing to be safe.
return openUriExternal(targetURI, mimeType, packageName, className, action, title, true);
}
/**
* Given the inputs to <code>getOpenURIIntent</code>, plus an optional
* package name and class name, create and fire an intent to open the
* provided URI. If a class name is specified but a package name is not,
* we will default to using the current fennec package.
*
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param packageName an optional app package name.
* @param className an optional intent class name.
* @param action an Android action specifier, such as
* <code>Intent.ACTION_SEND</code>.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @param showPromptInPrivateBrowsing whether or not the user should be prompted when opening
* this uri from private browsing. This should be true
* when the user doesn't explicitly choose to open an an
* external app (e.g. just clicked a link).
* @return true if the activity started successfully or the user was prompted to open the
* application; false otherwise.
*/
public static boolean openUriExternal(String targetURI,
String mimeType,
String packageName,
String className,
String action,
String title,
final boolean showPromptInPrivateBrowsing) {
final GeckoInterface gi = getGeckoInterface();
final Context activityContext = gi != null ? gi.getActivity() : null;
final Context context = activityContext != null ? activityContext : getApplicationContext();
final Intent intent = getOpenURIIntent(context, targetURI,
mimeType, action, title);
if (intent == null) {
return false;
}
if (!TextUtils.isEmpty(className)) {
if (!TextUtils.isEmpty(packageName)) {
intent.setClassName(packageName, className);
} else {
// Default to using the fennec app context.
intent.setClassName(context, className);
}
}
if (!showPromptInPrivateBrowsing || activityContext == null) {
if (activityContext == null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, intent);
} else {
// Ideally we retrieve the Activity from the calling args, rather than
// statically, but since this method is called from Gecko and I'm
// unfamiliar with that code, this is a simpler solution.
final FragmentActivity fragmentActivity = (FragmentActivity) activityContext;
return ExternalIntentDuringPrivateBrowsingPromptFragment.showDialogOrAndroidChooser(
context, fragmentActivity.getSupportFragmentManager(), intent);
}
}
/**
* Return a <code>Uri</code> instance which is equivalent to <code>u</code>,
* but with a guaranteed-lowercase scheme as if the API level 16 method
* <code>u.normalizeScheme</code> had been called.
*
* @param u the <code>Uri</code> to normalize.
* @return a <code>Uri</code>, which might be <code>u</code>.
*/
static Uri normalizeUriScheme(final Uri u) {
final String scheme = u.getScheme();
final String lower = scheme.toLowerCase(Locale.US);
if (lower.equals(scheme)) {
return u;
}
// Otherwise, return a new URI with a normalized scheme.
return u.buildUpon().scheme(lower).build();
}
/**
* Given a URI, a MIME type, and a title,
* produce a share intent which can be used to query all activities
* than can open the specified URI.
*
* @param context a <code>Context</code> instance.
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @return an <code>Intent</code>, or <code>null</code> if none could be
* produced.
*/
public static Intent getShareIntent(final Context context,
final String targetURI,
final String mimeType,
final String title) {
Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
shareIntent.putExtra(ShareDialog.INTENT_EXTRA_DEVICES_ONLY, true);
// Note that EXTRA_TITLE is intended to be used for share dialog
// titles. Common usage (e.g., Pocket) suggests that it's sometimes
// interpreted as an alternate to EXTRA_SUBJECT, so we include it.
shareIntent.putExtra(Intent.EXTRA_TITLE, title);
if (mimeType != null && mimeType.length() > 0) {
shareIntent.setType(mimeType);
}
return shareIntent;
}
/**
* Given a URI, a MIME type, an Android intent "action", and a title,
* produce an intent which can be used to start an activity to open
* the specified URI.
*
* @param context a <code>Context</code> instance.
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param action an Android action specifier, such as
* <code>Intent.ACTION_SEND</code>.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @return an <code>Intent</code>, or <code>null</code> if none could be
* produced.
*/
static Intent getOpenURIIntent(final Context context,
final String targetURI,
final String mimeType,
final String action,
final String title) {
// The resultant chooser can return non-exported activities in 4.1 and earlier.
// https://code.google.com/p/android/issues/detail?id=29535
final Intent intent = getOpenURIIntentInner(context, targetURI, mimeType, action, title);
if (intent != null) {
// Some applications use this field to return to the same browser after processing the
// Intent. While there is some danger (e.g. denial of service), other major browsers already
// use it and so it's the norm.
intent.putExtra(Browser.EXTRA_APPLICATION_ID, AppConstants.ANDROID_PACKAGE_NAME);
}
return intent;
}
private static Intent getOpenURIIntentInner(final Context context, final String targetURI,
final String mimeType, final String action, final String title) {
if (action.equalsIgnoreCase(Intent.ACTION_SEND)) {
Intent shareIntent = getShareIntent(context, targetURI, mimeType, title);
return Intent.createChooser(shareIntent,
context.getResources().getString(R.string.share_title));
}
Uri uri = normalizeUriScheme(targetURI.indexOf(':') >= 0 ? Uri.parse(targetURI) : new Uri.Builder().scheme(targetURI).build());
if (!TextUtils.isEmpty(mimeType)) {
Intent intent = getIntentForActionString(action);
intent.setDataAndType(uri, mimeType);
return intent;
}
if (!isUriSafeForScheme(uri)) {
return null;
}
final String scheme = uri.getScheme();
if ("intent".equals(scheme) || "android-app".equals(scheme)) {
final Intent intent;
try {
intent = Intent.parseUri(targetURI, 0);
} catch (final URISyntaxException e) {
Log.e(LOGTAG, "Unable to parse URI - " + e);
return null;
}
// Only open applications which can accept arbitrary data from a browser.
intent.addCategory(Intent.CATEGORY_BROWSABLE);
// Prevent site from explicitly opening our internal activities, which can leak data.
intent.setComponent(null);
nullIntentSelector(intent);
return intent;
}
// Compute our most likely intent, then check to see if there are any
// custom handlers that would apply.
// Start with the original URI. If we end up modifying it, we'll
// overwrite it.
final String extension = MimeTypeMap.getFileExtensionFromUrl(targetURI);
final Intent intent = getIntentForActionString(action);
intent.setData(uri);
if ("file".equals(scheme)) {
// Only set explicit mimeTypes on file://.
final String mimeType2 = getMimeTypeFromExtension(extension);
intent.setType(mimeType2);
return intent;
}
// Have a special handling for SMS based schemes, as the query parameters
// are not extracted from the URI automatically.
if (!"sms".equals(scheme) && !"smsto".equals(scheme) && !"mms".equals(scheme) && !"mmsto".equals(scheme)) {
return intent;
}
final String query = uri.getEncodedQuery();
if (TextUtils.isEmpty(query)) {
return intent;
}
// It is common to see sms*/mms* uris on the web without '//', it is W3C standard not to have the slashes,
// but android's Uri builder & Uri require the slashes and will interpret those without as malformed.
String currentUri = uri.toString();
String correctlyFormattedDataURIScheme = scheme + "://";
if (!currentUri.contains(correctlyFormattedDataURIScheme)) {
uri = Uri.parse(currentUri.replaceFirst(scheme + ":", correctlyFormattedDataURIScheme));
}
final String[] fields = query.split("&");
boolean shouldUpdateIntent = false;
String resultQuery = "";
for (String field : fields) {
if (field.startsWith("body=")) {
final String body = Uri.decode(field.substring(5));
intent.putExtra("sms_body", body);
shouldUpdateIntent = true;
} else if (field.startsWith("subject=")) {
final String subject = Uri.decode(field.substring(8));
intent.putExtra("subject", subject);
shouldUpdateIntent = true;
} else if (field.startsWith("cc=")) {
final String ccNumber = Uri.decode(field.substring(3));
String phoneNumber = uri.getAuthority();
if (phoneNumber != null) {
uri = uri.buildUpon().encodedAuthority(phoneNumber + ";" + ccNumber).build();
}
shouldUpdateIntent = true;
} else {
resultQuery = resultQuery.concat(resultQuery.length() > 0 ? "&" + field : field);
}
}
if (!shouldUpdateIntent) {
// No need to rewrite the URI, then.
return intent;
}
// Form a new URI without the extracted fields in the query part, and
// push that into the new Intent.
final String newQuery = resultQuery.length() > 0 ? "?" + resultQuery : "";
final Uri pruned = uri.buildUpon().encodedQuery(newQuery).build();
intent.setData(pruned);
return intent;
}
// We create a separate method to better encapsulate the @TargetApi use.
@TargetApi(15)
private static void nullIntentSelector(final Intent intent) {
if (!Versions.feature15Plus) {
return;
}
intent.setSelector(null);
return IntentHelper.openUriExternal(targetURI, mimeType, packageName, className, action, title, true);
}
/**

View File

@ -5,6 +5,7 @@
package org.mozilla.gecko;
import org.mozilla.gecko.overlays.ui.ShareDialog;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoEventListener;
@ -18,12 +19,18 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.provider.Browser;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import java.io.UnsupportedEncodingException;
import java.net.URI;
@ -83,6 +90,316 @@ public final class IntentHelper implements GeckoEventListener,
}
}
/**
* Given the inputs to <code>getOpenURIIntent</code>, plus an optional
* package name and class name, create and fire an intent to open the
* provided URI. If a class name is specified but a package name is not,
* we will default to using the current fennec package.
*
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param packageName an optional app package name.
* @param className an optional intent class name.
* @param action an Android action specifier, such as
* <code>Intent.ACTION_SEND</code>.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @param showPromptInPrivateBrowsing whether or not the user should be prompted when opening
* this uri from private browsing. This should be true
* when the user doesn't explicitly choose to open an an
* external app (e.g. just clicked a link).
* @return true if the activity started successfully or the user was prompted to open the
* application; false otherwise.
*/
public static boolean openUriExternal(String targetURI,
String mimeType,
String packageName,
String className,
String action,
String title,
final boolean showPromptInPrivateBrowsing) {
final GeckoAppShell.GeckoInterface gi = GeckoAppShell.getGeckoInterface();
final Context activityContext = gi != null ? gi.getActivity() : null;
final Context context = activityContext != null ? activityContext : GeckoAppShell.getApplicationContext();
final Intent intent = getOpenURIIntent(context, targetURI,
mimeType, action, title);
if (intent == null) {
return false;
}
if (!TextUtils.isEmpty(className)) {
if (!TextUtils.isEmpty(packageName)) {
intent.setClassName(packageName, className);
} else {
// Default to using the fennec app context.
intent.setClassName(context, className);
}
}
if (!showPromptInPrivateBrowsing || activityContext == null) {
if (activityContext == null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, intent);
} else {
// Ideally we retrieve the Activity from the calling args, rather than
// statically, but since this method is called from Gecko and I'm
// unfamiliar with that code, this is a simpler solution.
final FragmentActivity fragmentActivity = (FragmentActivity) activityContext;
return ExternalIntentDuringPrivateBrowsingPromptFragment.showDialogOrAndroidChooser(
context, fragmentActivity.getSupportFragmentManager(), intent);
}
}
public static boolean hasHandlersForIntent(Intent intent) {
try {
return !GeckoAppShell.queryIntentActivities(intent).isEmpty();
} catch (Exception ex) {
Log.e(LOGTAG, "Exception in hasHandlersForIntent");
return false;
}
}
public static String[] getHandlersForIntent(Intent intent) {
final PackageManager pm = GeckoAppShell.getApplicationContext().getPackageManager();
try {
final List<ResolveInfo> list = GeckoAppShell.queryIntentActivities(intent);
int numAttr = 4;
final String[] ret = new String[list.size() * numAttr];
for (int i = 0; i < list.size(); i++) {
ResolveInfo resolveInfo = list.get(i);
ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
if (resolveInfo.isDefault)
ret[i * numAttr + 1] = "default";
else
ret[i * numAttr + 1] = "";
ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
ret[i * numAttr + 3] = resolveInfo.activityInfo.name;
}
return ret;
} catch (Exception ex) {
Log.e(LOGTAG, "Exception in getHandlersForIntent");
return new String[0];
}
}
public static Intent getIntentForActionString(String aAction) {
// Default to the view action if no other action as been specified.
if (TextUtils.isEmpty(aAction)) {
return new Intent(Intent.ACTION_VIEW);
}
return new Intent(aAction);
}
/**
* Given a URI, a MIME type, and a title,
* produce a share intent which can be used to query all activities
* than can open the specified URI.
*
* @param context a <code>Context</code> instance.
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @return an <code>Intent</code>, or <code>null</code> if none could be
* produced.
*/
public static Intent getShareIntent(final Context context,
final String targetURI,
final String mimeType,
final String title) {
Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
shareIntent.putExtra(ShareDialog.INTENT_EXTRA_DEVICES_ONLY, true);
// Note that EXTRA_TITLE is intended to be used for share dialog
// titles. Common usage (e.g., Pocket) suggests that it's sometimes
// interpreted as an alternate to EXTRA_SUBJECT, so we include it.
shareIntent.putExtra(Intent.EXTRA_TITLE, title);
if (mimeType != null && mimeType.length() > 0) {
shareIntent.setType(mimeType);
}
return shareIntent;
}
/**
* Given a URI, a MIME type, an Android intent "action", and a title,
* produce an intent which can be used to start an activity to open
* the specified URI.
*
* @param context a <code>Context</code> instance.
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param action an Android action specifier, such as
* <code>Intent.ACTION_SEND</code>.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @return an <code>Intent</code>, or <code>null</code> if none could be
* produced.
*/
static Intent getOpenURIIntent(final Context context,
final String targetURI,
final String mimeType,
final String action,
final String title) {
// The resultant chooser can return non-exported activities in 4.1 and earlier.
// https://code.google.com/p/android/issues/detail?id=29535
final Intent intent = getOpenURIIntentInner(context, targetURI, mimeType, action, title);
if (intent != null) {
// Some applications use this field to return to the same browser after processing the
// Intent. While there is some danger (e.g. denial of service), other major browsers already
// use it and so it's the norm.
intent.putExtra(Browser.EXTRA_APPLICATION_ID, AppConstants.ANDROID_PACKAGE_NAME);
}
return intent;
}
private static Intent getOpenURIIntentInner(final Context context, final String targetURI,
final String mimeType, final String action, final String title) {
if (action.equalsIgnoreCase(Intent.ACTION_SEND)) {
Intent shareIntent = getShareIntent(context, targetURI, mimeType, title);
return Intent.createChooser(shareIntent,
context.getResources().getString(R.string.share_title));
}
Uri uri = normalizeUriScheme(targetURI.indexOf(':') >= 0 ? Uri.parse(targetURI) : new Uri.Builder().scheme(targetURI).build());
if (!TextUtils.isEmpty(mimeType)) {
Intent intent = getIntentForActionString(action);
intent.setDataAndType(uri, mimeType);
return intent;
}
if (!GeckoAppShell.isUriSafeForScheme(uri)) {
return null;
}
final String scheme = uri.getScheme();
if ("intent".equals(scheme) || "android-app".equals(scheme)) {
final Intent intent;
try {
intent = Intent.parseUri(targetURI, 0);
} catch (final URISyntaxException e) {
Log.e(LOGTAG, "Unable to parse URI - " + e);
return null;
}
// Only open applications which can accept arbitrary data from a browser.
intent.addCategory(Intent.CATEGORY_BROWSABLE);
// Prevent site from explicitly opening our internal activities, which can leak data.
intent.setComponent(null);
nullIntentSelector(intent);
return intent;
}
// Compute our most likely intent, then check to see if there are any
// custom handlers that would apply.
// Start with the original URI. If we end up modifying it, we'll
// overwrite it.
final String extension = MimeTypeMap.getFileExtensionFromUrl(targetURI);
final Intent intent = getIntentForActionString(action);
intent.setData(uri);
if ("file".equals(scheme)) {
// Only set explicit mimeTypes on file://.
final String mimeType2 = GeckoAppShell.getMimeTypeFromExtension(extension);
intent.setType(mimeType2);
return intent;
}
// Have a special handling for SMS based schemes, as the query parameters
// are not extracted from the URI automatically.
if (!"sms".equals(scheme) && !"smsto".equals(scheme) && !"mms".equals(scheme) && !"mmsto".equals(scheme)) {
return intent;
}
final String query = uri.getEncodedQuery();
if (TextUtils.isEmpty(query)) {
return intent;
}
// It is common to see sms*/mms* uris on the web without '//', it is W3C standard not to have the slashes,
// but android's Uri builder & Uri require the slashes and will interpret those without as malformed.
String currentUri = uri.toString();
String correctlyFormattedDataURIScheme = scheme + "://";
if (!currentUri.contains(correctlyFormattedDataURIScheme)) {
uri = Uri.parse(currentUri.replaceFirst(scheme + ":", correctlyFormattedDataURIScheme));
}
final String[] fields = query.split("&");
boolean shouldUpdateIntent = false;
String resultQuery = "";
for (String field : fields) {
if (field.startsWith("body=")) {
final String body = Uri.decode(field.substring(5));
intent.putExtra("sms_body", body);
shouldUpdateIntent = true;
} else if (field.startsWith("subject=")) {
final String subject = Uri.decode(field.substring(8));
intent.putExtra("subject", subject);
shouldUpdateIntent = true;
} else if (field.startsWith("cc=")) {
final String ccNumber = Uri.decode(field.substring(3));
String phoneNumber = uri.getAuthority();
if (phoneNumber != null) {
uri = uri.buildUpon().encodedAuthority(phoneNumber + ";" + ccNumber).build();
}
shouldUpdateIntent = true;
} else {
resultQuery = resultQuery.concat(resultQuery.length() > 0 ? "&" + field : field);
}
}
if (!shouldUpdateIntent) {
// No need to rewrite the URI, then.
return intent;
}
// Form a new URI without the extracted fields in the query part, and
// push that into the new Intent.
final String newQuery = resultQuery.length() > 0 ? "?" + resultQuery : "";
final Uri pruned = uri.buildUpon().encodedQuery(newQuery).build();
intent.setData(pruned);
return intent;
}
// We create a separate method to better encapsulate the @TargetApi use.
@TargetApi(15)
private static void nullIntentSelector(final Intent intent) {
if (!AppConstants.Versions.feature15Plus) {
return;
}
intent.setSelector(null);
}
/**
* Return a <code>Uri</code> instance which is equivalent to <code>u</code>,
* but with a guaranteed-lowercase scheme as if the API level 16 method
* <code>u.normalizeScheme</code> had been called.
*
* @param u the <code>Uri</code> to normalize.
* @return a <code>Uri</code>, which might be <code>u</code>.
*/
private static Uri normalizeUriScheme(final Uri u) {
final String scheme = u.getScheme();
final String lower = scheme.toLowerCase(Locale.US);
if (lower.equals(scheme)) {
return u;
}
// Otherwise, return a new URI with a normalized scheme.
return u.buildUpon().scheme(lower).build();
}
@Override
public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
if (event.equals("Intent:OpenNoHandler")) {
@ -108,12 +425,12 @@ public final class IntentHelper implements GeckoEventListener,
}
private void getHandlers(JSONObject message) throws JSONException {
final Intent intent = GeckoAppShell.getOpenURIIntent(activity,
final Intent intent = getOpenURIIntent(activity,
message.optString("url"),
message.optString("mime"),
message.optString("action"),
message.optString("title"));
final List<String> appList = Arrays.asList(GeckoAppShell.getHandlersForIntent(intent));
final List<String> appList = Arrays.asList(getHandlersForIntent(intent));
final JSONObject response = new JSONObject();
response.put("apps", new JSONArray(appList));
@ -121,7 +438,7 @@ public final class IntentHelper implements GeckoEventListener,
}
private void open(JSONObject message) throws JSONException {
GeckoAppShell.openUriExternal(message.optString("url"),
openUriExternal(message.optString("url"),
message.optString("mime"),
message.optString("packageName"),
message.optString("className"),
@ -130,7 +447,7 @@ public final class IntentHelper implements GeckoEventListener,
}
private void openForResult(final JSONObject message) throws JSONException {
Intent intent = GeckoAppShell.getOpenURIIntent(activity,
Intent intent = getOpenURIIntent(activity,
message.optString("url"),
message.optString("mime"),
message.optString("action"),

View File

@ -12,6 +12,7 @@ import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.IntentHelper;
import org.mozilla.gecko.R;
import org.mozilla.gecko.reader.ReaderModeUtils;
import org.mozilla.gecko.Restrictions;
@ -210,7 +211,7 @@ public abstract class HomeFragment extends Fragment {
Log.e(LOGTAG, "Can't share because URL is null");
return false;
} else {
GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
IntentHelper.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
Intent.ACTION_SEND, info.getDisplayTitle(), false);
// Context: Sharing via chrome homepage contextmenu list (home session should be active)

View File

@ -1,17 +1,16 @@
package org.mozilla.gecko.prompts;
import org.mozilla.gecko.IntentHelper;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.widget.GeckoActionProvider;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONException;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.util.Log;
import java.util.List;
import java.util.ArrayList;
@ -47,7 +46,7 @@ public class PromptListItem {
String type = obj.isNull("type") ? GeckoActionProvider.DEFAULT_MIME_TYPE :
obj.optString("type", GeckoActionProvider.DEFAULT_MIME_TYPE);
mIntent = GeckoAppShell.getShareIntent(context, uri, type, "");
mIntent = IntentHelper.getShareIntent(context, uri, type, "");
isParent = true;
} else {
mIntent = null;