Bug 1090385 - More robust handling of external intents. r=snorp, a=sledru
authorRichard Newman <rnewman@mozilla.com>
Tue, 28 Oct 2014 14:28:31 -0700
changeset 218158 2c6590150a85
parent 218155 0de6e2b5507a
child 218159 f8bdafd5fac5
push id559
push userrnewman@mozilla.com
push date2014-10-30 19:35 +0000
treeherdermozilla-release@2c6590150a85 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, sledru
bugs1090385
milestone33.1
Bug 1090385 - More robust handling of external intents. r=snorp, a=sledru
mobile/android/base/BrowserApp.java
mobile/android/base/GeckoApp.java
mobile/android/base/NotificationHelper.java
mobile/android/base/moz.build
mobile/android/base/mozglue/ContextUtils.java
mobile/android/base/mozglue/GeckoLoader.java.in
mobile/android/base/util/StringUtils.java
mobile/android/base/webapp/WebappImpl.java
--- 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.