Bug 1090385 - More robust handling of external intents. r=snorp, a=sledru
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -48,16 +48,17 @@ import org.mozilla.gecko.health.SessionI
import org.mozilla.gecko.home.BrowserSearch;
import org.mozilla.gecko.home.HomeBanner;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.HomePanelsManager;
import org.mozilla.gecko.home.SearchEngine;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuItem;
+import org.mozilla.gecko.mozglue.ContextUtils;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.preferences.ClearOnShutdownPref;
import org.mozilla.gecko.prompts.Prompt;
import org.mozilla.gecko.prompts.PromptListItem;
import org.mozilla.gecko.sync.setup.SyncAccounts;
import org.mozilla.gecko.tabspanel.TabsPanel;
import org.mozilla.gecko.toolbar.AutocompleteHandler;
import org.mozilla.gecko.toolbar.BrowserToolbar;
@@ -460,17 +461,17 @@ public class BrowserApp extends GeckoApp
}
@Override
public void onCreate(Bundle savedInstanceState) {
mAboutHomeStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_ABOUTHOME");
final Intent intent = getIntent();
- String args = StringUtils.getStringExtra(intent, "args");
+ String args = ContextUtils.getStringExtra(intent, "args");
if (args != null && args.contains(GUEST_BROWSING_ARG)) {
mProfile = GeckoProfile.createGuestProfile(this);
} else {
GeckoProfile.maybeCleanupGuestProfile(this);
}
// This has to be prepared prior to calling GeckoApp.onCreate, because
// widget code and BrowserToolbar need it, and they're created by the
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -35,16 +35,18 @@ import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PluginLayer;
import org.mozilla.gecko.health.HealthRecorder;
import org.mozilla.gecko.health.SessionInformation;
import org.mozilla.gecko.health.StubbedHealthRecorder;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuInflater;
import org.mozilla.gecko.menu.MenuPanel;
+import org.mozilla.gecko.mozglue.ContextUtils;
+import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.preferences.ClearOnShutdownPref;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.prompts.PromptService;
import org.mozilla.gecko.updater.UpdateService;
import org.mozilla.gecko.updater.UpdateServiceHelper;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventCallback;
@@ -1082,18 +1084,18 @@ public abstract class GeckoApp
window.getDecorView().setSystemUiVisibility(fullscreen ? 1 : 0);
}
});
}
/**
* Check and start the Java profiler if MOZ_PROFILER_STARTUP env var is specified.
**/
- protected static void earlyStartJavaSampler(Intent intent) {
- String env = StringUtils.getStringExtra(intent, "env0");
+ protected static void earlyStartJavaSampler(SafeIntent intent) {
+ String env = intent.getStringExtra("env0");
for (int i = 1; env != null; i++) {
if (env.startsWith("MOZ_PROFILER_STARTUP=")) {
if (!env.endsWith("=")) {
GeckoJavaSampler.start(10, 1000);
Log.d(LOGTAG, "Profiling Java on startup");
}
break;
}
@@ -1103,31 +1105,30 @@ public abstract class GeckoApp
/**
* Called when the activity is first created.
*
* Here we initialize all of our profile settings, Firefox Health Report,
* and other one-shot constructions.
**/
@Override
- public void onCreate(Bundle savedInstanceState)
- {
+ public void onCreate(Bundle savedInstanceState) {
GeckoAppShell.registerGlobalExceptionHandler();
// Enable Android Strict Mode for developers' local builds (the "default" channel).
if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
enableStrictMode();
}
// The clock starts...now. Better hurry!
mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
- final Intent intent = getIntent();
- final String args = StringUtils.getStringExtra(intent, "args");
+ final SafeIntent intent = new SafeIntent(getIntent());
+ final String args = intent.getStringExtra("args");
earlyStartJavaSampler(intent);
// GeckoLoader wants to dig some environment variables out of the
// incoming intent, so pass it in here. GeckoLoader will do its
// business later and dispose of the reference.
GeckoLoader.setLastIntent(intent);
@@ -1212,17 +1213,17 @@ public abstract class GeckoApp
if (GeckoThread.isCreated()) {
// This happens when the GeckoApp activity is destroyed by Android
// without killing the entire application (see Bug 769269).
mIsRestoringActivity = true;
Telemetry.HistogramAdd("FENNEC_RESTORING_ACTIVITY", 1);
}
- Bundle stateBundle = getIntent().getBundleExtra(EXTRA_STATE_BUNDLE);
+ Bundle stateBundle = ContextUtils.getBundleExtra(getIntent(), EXTRA_STATE_BUNDLE);
if (stateBundle != null) {
// Use the state bundle if it was given as an intent extra. This is
// only intended to be used internally via Robocop, so a boolean
// is read from a private shared pref to prevent other apps from
// injecting states.
final SharedPreferences prefs = getSharedPreferences();
if (prefs.getBoolean(PREFS_ALLOW_STATE_BUNDLE, false)) {
Log.i(LOGTAG, "Restoring state from intent bundle");
@@ -1406,28 +1407,31 @@ public abstract class GeckoApp
int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
Tabs.getInstance().loadUrl(url, flags);
}
}
private void initialize() {
mInitialized = true;
- Intent intent = getIntent();
- String action = intent.getAction();
-
- String passedUri = null;
+ final SafeIntent intent = new SafeIntent(getIntent());
+ final String action = intent.getAction();
+
final String uri = getURIFromIntent(intent);
+
+ final String passedUri;
if (!TextUtils.isEmpty(uri)) {
passedUri = uri;
+ } else {
+ passedUri = null;
}
final boolean isExternalURL = passedUri != null &&
!AboutPages.isAboutHome(passedUri);
- StartupAction startupAction;
+ final StartupAction startupAction;
if (isExternalURL) {
startupAction = StartupAction.URL;
} else {
startupAction = StartupAction.NORMAL;
}
// Start migrating as early as possible, can do this in
// parallel with Gecko load.
@@ -1503,17 +1507,17 @@ public abstract class GeckoApp
}
}, 1000 * 5 /* 5 seconds */);
}
// Check if launched from data reporting notification.
if (ACTION_LAUNCH_SETTINGS.equals(action)) {
Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
// Copy extras.
- settingsIntent.putExtras(intent);
+ settingsIntent.putExtras(intent.getUnsafe());
startActivity(settingsIntent);
}
//app state callbacks
mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
//register for events
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
@@ -1724,17 +1728,17 @@ public abstract class GeckoApp
return shouldRestore;
}
private String getSessionRestorePreference() {
return getSharedPreferences().getString(GeckoPreferences.PREFS_RESTORE_SESSION, "quit");
}
private boolean getRestartFromIntent() {
- return getIntent().getBooleanExtra("didRestart", false);
+ return ContextUtils.getBooleanExtra(getIntent(), "didRestart", false);
}
/**
* Enable Android StrictMode checks (for supported OS versions).
* http://developer.android.com/reference/android/os/StrictMode.html
*/
private void enableStrictMode() {
Log.d(LOGTAG, "Enabling Android StrictMode");
@@ -1824,44 +1828,46 @@ public abstract class GeckoApp
Log.e(LOGTAG, "Exception prefetching URL", e);
} finally {
if (connection != null)
connection.disconnect();
}
}
}
- private void processAlertCallback(Intent intent) {
+ private void processAlertCallback(SafeIntent intent) {
String alertName = "";
String alertCookie = "";
Uri data = intent.getData();
if (data != null) {
alertName = data.getQueryParameter("name");
if (alertName == null)
alertName = "";
alertCookie = data.getQueryParameter("cookie");
if (alertCookie == null)
alertCookie = "";
}
handleNotification(ACTION_ALERT_CALLBACK, alertName, alertCookie);
}
@Override
- protected void onNewIntent(Intent intent) {
+ protected void onNewIntent(Intent externalIntent) {
+ final SafeIntent intent = new SafeIntent(externalIntent);
+
if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting)) {
// We're exiting and shouldn't try to do anything else. In the case
// where we are hung while exiting, we should force the process to exit.
GeckoAppShell.systemExit();
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);
+ setIntent(externalIntent);
return;
}
final String action = intent.getAction();
if (ACTION_LOAD.equals(action)) {
String uri = intent.getDataString();
Tabs.getInstance().loadUrl(uri);
@@ -1885,26 +1891,26 @@ public abstract class GeckoApp
} else if (ACTION_ALERT_CALLBACK.equals(action)) {
processAlertCallback(intent);
} else if (NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
NotificationHelper.getInstance(getApplicationContext()).handleNotificationIntent(intent);
} else if (ACTION_LAUNCH_SETTINGS.equals(action)) {
// Check if launched from data reporting notification.
Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
// Copy extras.
- settingsIntent.putExtras(intent);
+ settingsIntent.putExtras(intent.getUnsafe());
startActivity(settingsIntent);
}
}
/*
* Handles getting a URI from an intent in a way that is backwards-
* compatible with our previous implementations.
*/
- protected String getURIFromIntent(Intent intent) {
+ protected String getURIFromIntent(SafeIntent intent) {
final String action = intent.getAction();
if (ACTION_ALERT_CALLBACK.equals(action) || NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
return null;
}
String uri = intent.getDataString();
if (uri != null) {
return uri;
--- a/mobile/android/base/NotificationHelper.java
+++ b/mobile/android/base/NotificationHelper.java
@@ -1,36 +1,33 @@
/* -*- 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/. */
package org.mozilla.gecko;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.StringUtils;
+import java.util.HashMap;
+import java.util.Iterator;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
+import org.mozilla.gecko.util.GeckoEventListener;
-import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.IntentFilter;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
-import java.util.Iterator;
-import java.util.HashMap;
-
public final class NotificationHelper implements GeckoEventListener {
public static final String HELPER_BROADCAST_ACTION = AppConstants.ANDROID_PACKAGE_NAME + ".helperBroadcastAction";
public static final String NOTIFICATION_ID = "NotificationHelper_ID";
private static final String LOGTAG = "GeckoNotificationHelper";
private static final String HELPER_NOTIFICATION = "helperNotif";
// Attributes mandatory to be used while sending a notification from js.
@@ -105,17 +102,17 @@ public final class NotificationHelper im
hideNotification(message);
}
}
public boolean isHelperIntent(Intent i) {
return i.getBooleanExtra(HELPER_NOTIFICATION, false);
}
- public void handleNotificationIntent(Intent i) {
+ public void handleNotificationIntent(SafeIntent i) {
final Uri data = i.getData();
if (data == null) {
Log.e(LOGTAG, "handleNotificationEvent: empty data");
return;
}
final String id = data.getQueryParameter(ID_ATTR);
final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
if (id == null || notificationType == null) {
@@ -132,17 +129,17 @@ public final class NotificationHelper im
return;
}
}
JSONObject args = new JSONObject();
// The handler and cookie parameters are optional.
final String handler = data.getQueryParameter(HANDLER_ATTR);
- final String cookie = StringUtils.getStringExtra(i, COOKIE_ATTR);
+ final String cookie = i.getStringExtra(COOKIE_ATTR);
try {
args.put(ID_ATTR, id);
args.put(EVENT_TYPE_ATTR, notificationType);
args.put(HANDLER_ATTR, handler);
args.put(COOKIE_ATTR, cookie);
if (BUTTON_EVENT.equals(notificationType)) {
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -22,16 +22,17 @@ if CONFIG['MOZ_NATIVE_DEVICES']:
resjar.generated_sources += ['android/support/v7/appcompat/R.java']
resjar.generated_sources += ['android/support/v7/mediarouter/R.java']
resjar.javac_flags += ['-Xlint:all']
mgjar = add_java_jar('gecko-mozglue')
mgjar.sources += [
'mozglue/ByteBufferInputStream.java',
+ 'mozglue/ContextUtils.java',
'mozglue/DirectBufferAllocator.java',
'mozglue/generatorannotations/GeneratorOptions.java',
'mozglue/generatorannotations/OptionalGeneratedParameter.java',
'mozglue/generatorannotations/WrapElementForJNI.java',
'mozglue/generatorannotations/WrapEntireClassForJNI.java',
'mozglue/JNITarget.java',
'mozglue/NativeReference.java',
'mozglue/NativeZip.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/mozglue/ContextUtils.java
@@ -0,0 +1,102 @@
+/* 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/. */
+
+package org.mozilla.gecko.mozglue;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+public class ContextUtils {
+ private static final String LOGTAG = "GeckoContextUtils";
+
+ public static Bundle getBundleExtra(final Intent intent, final String name) {
+ return new SafeIntent(intent).getBundleExtra(name);
+ }
+
+ public static String getStringExtra(final Intent intent, final String name) {
+ return new SafeIntent(intent).getStringExtra(name);
+ }
+
+ public static boolean getBooleanExtra(Intent intent, String name, boolean defaultValue) {
+ return new SafeIntent(intent).getBooleanExtra(name, defaultValue);
+ }
+
+ public static class SafeIntent {
+ private final Intent intent;
+
+ public SafeIntent(final Intent intent) {
+ this.intent = intent;
+ }
+
+ public boolean getBooleanExtra(final String name, final boolean defaultValue) {
+ try {
+ return intent.getBooleanExtra(name, defaultValue);
+ } catch (OutOfMemoryError e) {
+ Log.w(LOGTAG, "Couldn't get intent extras: OOM. Malformed?");
+ return defaultValue;
+ } catch (RuntimeException e) {
+ Log.w(LOGTAG, "Couldn't get intent extras.", e);
+ return defaultValue;
+ }
+ }
+
+ public String getStringExtra(final String name) {
+ try {
+ return intent.getStringExtra(name);
+ } catch (OutOfMemoryError e) {
+ Log.w(LOGTAG, "Couldn't get intent extras: OOM. Malformed?");
+ return null;
+ } catch (RuntimeException e) {
+ Log.w(LOGTAG, "Couldn't get intent extras.", e);
+ return null;
+ }
+ }
+
+ public Bundle getBundleExtra(final String name) {
+ try {
+ return intent.getBundleExtra(name);
+ } catch (OutOfMemoryError e) {
+ Log.w(LOGTAG, "Couldn't get intent extras: OOM. Malformed?");
+ return null;
+ } catch (RuntimeException e) {
+ Log.w(LOGTAG, "Couldn't get intent extras.", e);
+ return null;
+ }
+ }
+
+ public String getAction() {
+ return intent.getAction();
+ }
+
+ public String getDataString() {
+ try {
+ return intent.getDataString();
+ } catch (OutOfMemoryError e) {
+ Log.w(LOGTAG, "Couldn't get intent data string: OOM. Malformed?");
+ return null;
+ } catch (RuntimeException e) {
+ Log.w(LOGTAG, "Couldn't get intent data string.", e);
+ return null;
+ }
+ }
+
+ public Uri getData() {
+ try {
+ return intent.getData();
+ } catch (OutOfMemoryError e) {
+ Log.w(LOGTAG, "Couldn't get intent data: OOM. Malformed?");
+ return null;
+ } catch (RuntimeException e) {
+ Log.w(LOGTAG, "Couldn't get intent data.", e);
+ return null;
+ }
+ }
+
+ public Intent getUnsafe() {
+ return intent;
+ }
+ }
+}
--- a/mobile/android/base/mozglue/GeckoLoader.java.in
+++ b/mobile/android/base/mozglue/GeckoLoader.java.in
@@ -17,24 +17,26 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
+import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
+
public final class GeckoLoader {
private static final String LOGTAG = "GeckoLoader";
// These match AppConstants, but we're built earlier.
private static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
private static final String MOZ_APP_ABI = "@MOZ_APP_ABI@";
- private static volatile Intent sIntent;
+ private static volatile SafeIntent sIntent;
private static File sCacheFile;
private static File sGREDir;
private static final Object sLibLoadingLock = new Object();
// Must hold sLibLoadingLock while accessing the following boolean variables.
private static boolean sSQLiteLibsLoaded;
private static boolean sNSSLibsLoaded;
private static boolean sMozGlueLoaded;
@@ -118,24 +120,24 @@ public final class GeckoLoader {
// check if the old tmp dir is there
File oldDir = new File(tmpDir.getParentFile(), "app_tmp");
if (oldDir.exists()) {
delTree(oldDir);
}
return tmpDir;
}
- public static void setLastIntent(Intent intent) {
+ public static void setLastIntent(SafeIntent intent) {
sIntent = intent;
}
public static void setupGeckoEnvironment(Context context, String[] pluginDirs, String profilePath) {
// if we have an intent (we're being launched by an activity)
// read in any environmental variables from it here
- final Intent intent = sIntent;
+ final SafeIntent intent = sIntent;
if (intent != null) {
String env = intent.getStringExtra("env0");
Log.d(LOGTAG, "Gecko environment env0: " + env);
for (int c = 1; env != null; c++) {
putenv(env);
env = intent.getStringExtra("env" + c);
Log.d(LOGTAG, "env" + c + ": " + env);
}
--- a/mobile/android/base/util/StringUtils.java
+++ b/mobile/android/base/util/StringUtils.java
@@ -1,19 +1,17 @@
/* -*- 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/. */
package org.mozilla.gecko.util;
-import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
-import android.util.Log;
public class StringUtils {
private static final String LOGTAG = "GeckoStringUtils";
private static final String FILTER_URL_PREFIX = "filter://";
private static final String USER_ENTERED_URL_PREFIX = "user-entered:";
/*
@@ -184,21 +182,9 @@ public class StringUtils {
return uri.getSchemeSpecificPart();
}
return url;
}
public static String encodeUserEnteredUrl(String url) {
return Uri.fromParts("user-entered", url, null).toString();
}
-
- public static String getStringExtra(Intent intent, String name) {
- try {
- return intent.getStringExtra(name);
- } catch (android.os.BadParcelableException ex) {
- Log.w(LOGTAG, "Couldn't get string extra: malformed intent.");
- return null;
- } catch (RuntimeException re) {
- Log.w(LOGTAG, "Couldn't get string extra.", re);
- return null;
- }
- }
}
--- a/mobile/android/base/webapp/WebappImpl.java
+++ b/mobile/android/base/webapp/WebappImpl.java
@@ -13,16 +13,17 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.webapp.InstallHelper.InstallCallback;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -151,17 +152,17 @@ public class WebappImpl extends GeckoApp
} else {
launchWebapp(origin);
}
setTitle(mAppName);
}
@Override
- protected String getURIFromIntent(Intent intent) {
+ protected String getURIFromIntent(SafeIntent intent) {
String uri = super.getURIFromIntent(intent);
if (uri != null) {
return uri;
}
// This is where we construct the URL from the Intent from the
// the synthesized APK.
// TODO Translate AndroidIntents into WebActivities here.