Bug 1363167 - Remove GeckoInterface.getActivity; r=snorp
authorJim Chen <nchen@mozilla.com>
Mon, 15 May 2017 23:11:11 -0400
changeset 406667 d15e744f0008d62ce04fa776caebe429a88bf743
parent 406666 c399da282963b7c78338935b00edd14ddb639195
child 406668 b4549c97046b466e05826bffa53f35b175ec88ba
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1363167
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1363167 - Remove GeckoInterface.getActivity; r=snorp Use available Context or GeckoActivityMonitor to derive an Activity instance, instead of using GeckoInterface.getActivity(). MozReview-Commit-ID: GHLMtnQkr2l
mobile/android/base/java/org/mozilla/gecko/ActivityHandlerHelper.java
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/DevToolsAuthHelper.java
mobile/android/base/java/org/mozilla/gecko/FilePicker.java
mobile/android/base/java/org/mozilla/gecko/FilePickerResultHandler.java
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
mobile/android/base/java/org/mozilla/gecko/notifications/NotificationClient.java
mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java
mobile/android/base/java/org/mozilla/gecko/prompts/IntentChooserPrompt.java
mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityUtils.java
--- a/mobile/android/base/java/org/mozilla/gecko/ActivityHandlerHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ActivityHandlerHelper.java
@@ -16,20 +16,16 @@ import android.util.Log;
 public class ActivityHandlerHelper {
     private static final String LOGTAG = "GeckoActivityHandlerHelper";
     private static final ActivityResultHandlerMap mActivityResultHandlerMap = new ActivityResultHandlerMap();
 
     private static int makeRequestCode(ActivityResultHandler aHandler) {
         return mActivityResultHandlerMap.put(aHandler);
     }
 
-    public static void startIntent(Intent intent, ActivityResultHandler activityResultHandler) {
-        startIntentForActivity(GeckoAppShell.getGeckoInterface().getActivity(), intent, activityResultHandler);
-    }
-
     /**
      * Starts the Activity, catching & logging if the Activity fails to start.
      *
      * We catch to prevent callers from passing in invalid Intents and crashing the browser.
      *
      * @return true if the Activity is successfully started, false otherwise.
      */
     public static boolean startIntentAndCatch(final String logtag, final Context context, final Intent intent) {
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -3042,17 +3042,17 @@ public class BrowserApp extends GeckoApp
         // We want to adjust the window size when the keyboard appears to bring the
         // SearchEngineBar above the keyboard. However, adjusting the window size
         // when hiding the keyboard results in graphical glitches where the keyboard was
         // because nothing was being drawn underneath (bug 933422). This can be
         // prevented drawing content under the keyboard (i.e. in the Window).
         //
         // We do this here because there are glitches when unlocking a device with
         // BrowserSearch in the foreground if we use BrowserSearch.onStart/Stop.
-        getActivity().getWindow().setBackgroundDrawableResource(android.R.color.white);
+        getWindow().setBackgroundDrawableResource(android.R.color.white);
     }
 
     private void hideBrowserSearch() {
         if (!mBrowserSearch.getUserVisibleHint()) {
             return;
         }
 
         // To prevent overdraw, the HomePager is hidden when BrowserSearch is displayed:
--- a/mobile/android/base/java/org/mozilla/gecko/DevToolsAuthHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/DevToolsAuthHelper.java
@@ -16,32 +16,33 @@ import org.mozilla.gecko.util.InputOptio
 
 /**
  * Supports the DevTools WiFi debugging authentication flow by invoking a QR decoder.
  */
 public class DevToolsAuthHelper {
 
     private static final String LOGTAG = "GeckoDevToolsAuthHelper";
 
-    public static void scan(Context context, final EventCallback callback) {
+    public static void scan(final Activity context, final EventCallback callback) {
         final Intent intent = InputOptionsUtils.createQRCodeReaderIntent();
 
         intent.putExtra("PROMPT_MESSAGE", context.getString(R.string.devtools_auth_scan_header));
 
         // Check ahead of time if an activity exists for the intent.  This
         // avoids a case where we get both an ActivityNotFoundException *and*
         // an activity result when the activity is missing.
         PackageManager pm = context.getPackageManager();
         if (pm.resolveActivity(intent, 0) == null) {
             Log.w(LOGTAG, "PackageManager can't resolve the activity.");
             callback.sendError("PackageManager can't resolve the activity.");
             return;
         }
 
-        ActivityHandlerHelper.startIntent(intent, new ActivityResultHandler() {
+        ActivityHandlerHelper.startIntentForActivity(
+                context, intent, new ActivityResultHandler() {
             @Override
             public void onActivityResult(int resultCode, Intent intent) {
                 if (resultCode == Activity.RESULT_OK) {
                     String text = intent.getStringExtra("SCAN_RESULT");
                     callback.sendSuccess(text);
                 } else {
                     callback.sendError(resultCode);
                 }
--- a/mobile/android/base/java/org/mozilla/gecko/FilePicker.java
+++ b/mobile/android/base/java/org/mozilla/gecko/FilePicker.java
@@ -1,21 +1,23 @@
 /* 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.GeckoAppShell;
+import org.mozilla.gecko.permissions.PermissionBlock;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 
 import android.Manifest;
+import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.Parcelable;
@@ -38,17 +40,17 @@ public class FilePicker implements Bundl
     }
 
     public static void init(Context context) {
         if (sFilePicker == null) {
             sFilePicker = new FilePicker(context.getApplicationContext());
         }
     }
 
-    protected FilePicker(Context context) {
+    private FilePicker(Context context) {
         this.context = context;
         EventDispatcher.getInstance().registerUiThreadListener(this, "FilePicker:Show");
     }
 
     @Override // BundleEventListener
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
         if ("FilePicker:Show".equals(event)) {
@@ -60,36 +62,45 @@ public class FilePicker implements Bundl
             if ("mimeType".equals(mode)) {
                 mimeType = message.getString("mimeType", "");
             } else if ("extension".equals(mode)) {
                 mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.getString("extensions", ""));
             }
 
             final String[] requiredPermission = getPermissionsForMimeType(mimeType);
             final String finalMimeType = mimeType;
-            // Use activity context cause we want to prompt for runtime permission. (bug 1337692)
-            Permissions.from(GeckoAppShell.getGeckoInterface().getActivity())
-                    .withPermissions(requiredPermission)
-                    .andFallback(new Runnable() {
-                        @Override
-                        public void run() {
-                            callback.sendError(null);
-                        }
-                    })
-                    .run(new Runnable() {
-                        @Override
-                        public void run() {
-                            showFilePickerAsync(title, finalMimeType, new ResultHandler() {
-                                @Override
-                                public void gotFile(final String filename) {
-                                    callback.sendSuccess(filename);
-                                }
-                            }, tabId);
-                        }
-                    });
+
+            // Use activity context because we want to prompt for runtime permission.
+            final Activity currentActivity =
+                    GeckoActivityMonitor.getInstance().getCurrentActivity();
+            final PermissionBlock perm;
+            if (currentActivity != null) {
+                perm = Permissions.from(currentActivity);
+            } else {
+                perm = Permissions.from(context).doNotPrompt();
+            }
+
+            perm.withPermissions(requiredPermission)
+                .andFallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.sendError(null);
+                    }
+                })
+                .run(new Runnable() {
+                    @Override
+                    public void run() {
+                        showFilePickerAsync(title, finalMimeType, new ResultHandler() {
+                            @Override
+                            public void gotFile(final String filename) {
+                                callback.sendSuccess(filename);
+                            }
+                        }, tabId);
+                    }
+                });
         }
     }
 
     private static String[] getPermissionsForMimeType(final String mimeType) {
         if (mimeType.startsWith("audio/")) {
             return new String[] { Manifest.permission.RECORD_AUDIO };
         } else if (mimeType.startsWith("image/")) {
             return new String[] { Manifest.permission.CAMERA };
@@ -223,17 +234,19 @@ public class FilePicker implements Bundl
      * sends the file returned to the passed in handler. If a null handler is passed in, will still
      * pick and launch the file picker, but will throw away the result.
      */
     protected void showFilePickerAsync(final String title, final String mimeType,
                                        final ResultHandler handler, final int tabId) {
         final FilePickerResultHandler fileHandler =
                 new FilePickerResultHandler(handler, context, tabId);
         final Intent intent = getFilePickerIntent(title, mimeType, fileHandler);
+        final Activity currentActivity =
+                GeckoActivityMonitor.getInstance().getCurrentActivity();
 
-        if (intent == null) {
+        if (intent == null || currentActivity == null) {
             handler.gotFile("");
             return;
         }
 
-        ActivityHandlerHelper.startIntent(intent, fileHandler);
+        ActivityHandlerHelper.startIntentForActivity(currentActivity, intent, fileHandler);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/FilePickerResultHandler.java
+++ b/mobile/android/base/java/org/mozilla/gecko/FilePickerResultHandler.java
@@ -19,18 +19,16 @@ import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Process;
 import android.provider.MediaStore;
 import android.provider.OpenableColumns;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.CursorLoader;
 import android.support.v4.content.Loader;
 import android.text.TextUtils;
 import android.text.format.Time;
 import android.util.Log;
 
 class FilePickerResultHandler implements ActivityResultHandler {
@@ -53,16 +51,28 @@ class FilePickerResultHandler implements
     }
 
     void sendResult(String res) {
         if (handler != null) {
             handler.gotFile(res);
         }
     }
 
+    private <T> void initLoader(final LoaderCallbacks<T> callbacks) {
+        final Loader<T> loader = callbacks.onCreateLoader(/* id */ 0, /* args */ null);
+        loader.registerListener(/* id */ 0, new Loader.OnLoadCompleteListener<T>() {
+            @Override
+            public void onLoadComplete(final Loader<T> loader, final T data) {
+                callbacks.onLoadFinished(loader, data);
+                loader.unregisterListener(this);
+            }
+        });
+        loader.startLoading();
+    }
+
     @Override
     public void onActivityResult(int resultCode, Intent intent) {
         if (resultCode != Activity.RESULT_OK) {
             sendResult("");
             return;
         }
 
         // Camera results won't return an Intent. Use the file name we passed to the original intent.
@@ -85,38 +95,37 @@ class FilePickerResultHandler implements
 
         // Some file pickers may return a file uri
         if ("file".equals(uri.getScheme())) {
             String path = uri.getPath();
             sendResult(path == null ? "" : path);
             return;
         }
 
-        final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
-        final LoaderManager lm = fa.getSupportLoaderManager();
+        final Context context = GeckoAppShell.getApplicationContext();
 
         // Finally, Video pickers and some file pickers may return a content provider.
-        final ContentResolver cr = fa.getContentResolver();
+        final ContentResolver cr = context.getContentResolver();
         final Cursor cursor = cr.query(uri, new String[] { MediaStore.Video.Media.DATA }, null, null, null);
         if (cursor != null) {
             try {
                 // Try a query to make sure the expected columns exist
                 int index = cursor.getColumnIndex(MediaStore.Video.Media.DATA);
                 if (index >= 0) {
-                    lm.initLoader(intent.hashCode(), null, new VideoLoaderCallbacks(uri));
+                    initLoader(new VideoLoaderCallbacks(uri));
                     return;
                 }
             } catch (Exception ex) {
                 // We'll try a different loader below
             } finally {
                 cursor.close();
             }
         }
 
-        lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri, cacheDir, tabId));
+        initLoader(new FileLoaderCallbacks(uri, cacheDir, tabId));
     }
 
     public String generateImageName() {
         Time now = new Time();
         now.setToNow();
         mImageName = now.format("%Y-%m-%d %H.%M.%S") + ".jpg";
         return mImageName;
     }
@@ -124,18 +133,18 @@ class FilePickerResultHandler implements
     private class VideoLoaderCallbacks implements LoaderCallbacks<Cursor> {
         final private Uri uri;
         public VideoLoaderCallbacks(Uri uri) {
             this.uri = uri;
         }
 
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
-            return new CursorLoader(fa,
+            final Context context = GeckoAppShell.getApplicationContext();
+            return new CursorLoader(context,
                                     uri,
                                     new String[] { MediaStore.Video.Media.DATA },
                                     null,  // selection
                                     null,  // selectionArgs
                                     null); // sortOrder
         }
 
         @Override
@@ -152,19 +161,17 @@ class FilePickerResultHandler implements
 
                 sendResult(res);
             } else {
                 tryFileLoaderCallback();
             }
         }
 
         private void tryFileLoaderCallback() {
-            final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
-            final LoaderManager lm = fa.getSupportLoaderManager();
-            lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri, cacheDir, tabId));
+            initLoader(new FileLoaderCallbacks(uri, cacheDir, tabId));
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) { }
     }
 
     /**
      * This class's only dependency on FilePickerResultHandler is sendResult.
@@ -179,32 +186,32 @@ class FilePickerResultHandler implements
         public FileLoaderCallbacks(Uri uri, File cacheDir, int tabId) {
             this.uri = uri;
             this.cacheDir = cacheDir;
             this.tabId = tabId;
         }
 
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
-            return new CursorLoader(fa,
+            final Context context = GeckoAppShell.getApplicationContext();
+            return new CursorLoader(context,
                                     uri,
                                     new String[] { OpenableColumns.DISPLAY_NAME },
                                     null,  // selection
                                     null,  // selectionArgs
                                     null); // sortOrder
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
             if (cursor.moveToFirst()) {
                 String fileName = cursor.getString(0);
 
-                final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
-                final ContentResolver cr = fa.getContentResolver();
+                final Context context = GeckoAppShell.getApplicationContext();
+                final ContentResolver cr = context.getContentResolver();
 
                 // Generate an extension if we don't already have one
                 if (fileName == null || fileName.lastIndexOf('.') == -1) {
                     String mimeType = cr.getType(uri);
                     String fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType);
                     if (fileName == null) {
                         // tmp filenames must be at least 3 characters long. Add a prefix to make sure that happens
                         fileName = "tmp_" + Process.myPid() + fileExt;
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -415,21 +415,16 @@ public abstract class GeckoApp extends G
         return GeckoSharedPrefs.forApp(this);
     }
 
     public SharedPreferences getSharedPreferencesForProfile() {
         return GeckoSharedPrefs.forProfile(this);
     }
 
     @Override
-    public Activity getActivity() {
-        return this;
-    }
-
-    @Override
     public void addAppStateListener(GeckoAppShell.AppStateListener listener) {
         mAppStateListeners.add(listener);
     }
 
     @Override
     public void removeAppStateListener(GeckoAppShell.AppStateListener listener) {
         mAppStateListeners.remove(listener);
     }
--- a/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
@@ -103,19 +103,20 @@ public final class IntentHelper implemen
      */
     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 Context activityContext =
+                GeckoActivityMonitor.getInstance().getCurrentActivity();
+        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)) {
--- a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationClient.java
+++ b/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationClient.java
@@ -1,29 +1,31 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.notifications;
 
+import android.app.Activity;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.NotificationManagerCompat;
 import android.util.Log;
 
 import java.util.HashMap;
 
 import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.GeckoActivityMonitor;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoService;
 import org.mozilla.gecko.NotificationListener;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.gfx.BitmapUtils;
 
 /**
@@ -70,23 +72,23 @@ public final class NotificationClient im
         showNotification(name, cookie, title, text, host, imageUrl, data != null ? data : "");
     }
 
     private void showNotification(String name, String cookie, String title,
                                   String text, String host, String imageUrl,
                                   String persistentData) {
         // Put the strings into the intent as an URI
         // "alert:?name=<name>&cookie=<cookie>"
-        String packageName = AppConstants.ANDROID_PACKAGE_NAME;
+        String packageName = mContext.getPackageName();
         String className = AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS;
-        if (GeckoAppShell.getGeckoInterface() != null) {
-            final ComponentName comp = GeckoAppShell.getGeckoInterface()
-                                                    .getActivity().getComponentName();
-            packageName = comp.getPackageName();
-            className = comp.getClassName();
+
+        final Activity currentActivity =
+                GeckoActivityMonitor.getInstance().getCurrentActivity();
+        if (currentActivity != null) {
+            className = currentActivity.getClass().getName();
         }
         final Uri dataUri = (new Uri.Builder())
                 .scheme("moz-notification")
                 .authority(packageName)
                 .path(className)
                 .appendQueryParameter("name", name)
                 .appendQueryParameter("cookie", cookie)
                 .build();
--- a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java
@@ -8,25 +8,27 @@ package org.mozilla.gecko.notifications;
 import java.io.File;
 import java.io.UnsupportedEncodingException;
 import java.net.URLConnection;
 import java.net.URLDecoder;
 import java.util.List;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoActivityMonitor;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.net.Uri;
@@ -184,18 +186,24 @@ public final class NotificationHelper im
         notificationIntent.putExtra(ONGOING_ATTR, ongoing);
 
         final Uri dataUri = builder.build();
         notificationIntent.setData(dataUri);
         notificationIntent.putExtra(HELPER_NOTIFICATION, true);
         notificationIntent.putExtra(COOKIE_ATTR, message.getString(COOKIE_ATTR, ""));
 
         // All intents get routed through the notificationReceiver. That lets us bail if we don't want to start Gecko
-        final ComponentName name = new ComponentName(
-                mContext, GeckoAppShell.getGeckoInterface().getActivity().getClass());
+        final Activity currentActivity =
+                GeckoActivityMonitor.getInstance().getCurrentActivity();
+        final ComponentName name;
+        if (currentActivity != null) {
+            name = new ComponentName(mContext, currentActivity.getClass());
+        } else {
+            name = new ComponentName(mContext, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
+        }
         notificationIntent.putExtra(ORIGINAL_EXTRA_COMPONENT, name);
 
         return notificationIntent;
     }
 
     private PendingIntent buildNotificationPendingIntent(final GeckoBundle message,
                                                          final String type) {
         final Uri.Builder builder = getNotificationBuilder(message, type);
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/IntentChooserPrompt.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/IntentChooserPrompt.java
@@ -4,16 +4,17 @@
 
 package org.mozilla.gecko.prompts;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
+import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.widget.ListView;
 import android.util.Log;
 
@@ -129,17 +130,18 @@ public class IntentChooserPrompt {
         item.setIntent(new Intent(i));
 
         return item;
     }
 
     private ArrayList<PromptListItem> getItemsForIntent(Context context, Intent intent) {
         ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
         PackageManager pm = context.getPackageManager();
-        List<ResolveInfo> lri = pm.queryIntentActivityOptions(GeckoAppShell.getGeckoInterface().getActivity().getComponentName(), null, intent, 0);
+        List<ResolveInfo> lri = pm.queryIntentActivityOptions(
+                ((Activity) context).getComponentName(), null, intent, 0);
 
         // If we didn't find any activities, just return the empty list
         if (lri == null) {
             return items;
         }
 
         // Otherwise, convert the ResolveInfo. Note we don't currently check for duplicates here.
         for (ResolveInfo ri : lri) {
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java
@@ -20,20 +20,22 @@ import org.mozilla.gecko.TelemetryContra
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.util.ActivityResultHandler;
+import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.DrawableUtil;
 import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.InputOptionsUtils;
 import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.InputOptionsUtils;
+import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
 
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.ImageView;
@@ -181,18 +183,17 @@ public class ToolbarEditLayout extends T
 
     /**
      * Called when the parent gains focus (on app launch and resume)
      */
     public void onParentFocus() {
         if (showKeyboardOnFocus) {
             showKeyboardOnFocus = false;
 
-            Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
-            activity.runOnUiThread(new Runnable() {
+            ThreadUtils.postToUiThread(new Runnable() {
                 public void run() {
                     mEditText.requestFocus();
                     showSoftInput();
                 }
             });
         }
 
         // Checking if qr code is supported after resuming the app
@@ -284,17 +285,17 @@ public class ToolbarEditLayout extends T
         return GeckoSharedPrefs.forApp(context)
                 .getBoolean(GeckoPreferences.PREFS_VOICE_INPUT_ENABLED, true);
     }
 
     private void launchVoiceRecognizer() {
         Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "voice_input_launch");
         final Intent intent = InputOptionsUtils.createVoiceRecognizerIntent(getResources().getString(R.string.voicesearch_prompt));
 
-        Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
+        final Activity activity = ActivityUtils.getActivityFromContext(getContext());
         ActivityHandlerHelper.startIntentForActivity(activity, intent, new ActivityResultHandler() {
             @Override
             public void onActivityResult(int resultCode, Intent data) {
                 if (resultCode != Activity.RESULT_OK) {
                     return;
                 }
 
                 Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "voice_input_success");
@@ -321,17 +322,17 @@ public class ToolbarEditLayout extends T
         return GeckoSharedPrefs.forApp(context)
                 .getBoolean(GeckoPreferences.PREFS_QRCODE_ENABLED, true);
     }
 
     private void launchQRCodeReader() {
         Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "qrcode_input_launch");
         final Intent intent = InputOptionsUtils.createQRCodeReaderIntent();
 
-        Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
+        final Activity activity = ActivityUtils.getActivityFromContext(getContext());
         ActivityHandlerHelper.startIntentForActivity(activity, intent, new ActivityResultHandler() {
             @Override
             public void onActivityResult(int resultCode, Intent intent) {
                 if (resultCode == Activity.RESULT_OK) {
                     String text = intent.getStringExtra("SCAN_RESULT");
                     if (!StringUtils.isSearchQuery(text, false)) {
                         Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "qrcode_input_success");
                         mEditText.setText(text);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
@@ -23,22 +23,16 @@ public class BaseGeckoInterface implemen
     }
 
     @Override
     public EventDispatcher getAppEventDispatcher() {
         return eventDispatcher;
     }
 
     @Override
-    public Activity getActivity() {
-        // By default, GeckoView consumers do not have a distinguished current foreground Activity.
-        return null;
-    }
-
-    @Override
     public String getDefaultUAString() {
         return HardwareUtils.isTablet() ? BuildConfig.USER_AGENT_GECKOVIEW_TABLET :
                                           BuildConfig.USER_AGENT_GECKOVIEW_MOBILE;
     }
 
     // Bug 908779: Implement this
     @Override
     public void addPluginView(final View view) {}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -1656,17 +1656,16 @@ public class GeckoAppShell
     public interface AppStateListener {
         public void onPause();
         public void onResume();
         public void onOrientationChanged();
     }
 
     public interface GeckoInterface {
         public @NonNull EventDispatcher getAppEventDispatcher();
-        public Activity getActivity();
         public String getDefaultUAString();
 
         public void addPluginView(View view);
         public void removePluginView(final View view);
         public void enableOrientationListener();
         public void disableOrientationListener();
         public void addAppStateListener(AppStateListener listener);
         public void removeAppStateListener(AppStateListener listener);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityUtils.java
@@ -1,16 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
 import android.os.Build;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
 
 public class ActivityUtils {
     private ActivityUtils() {
@@ -61,11 +63,20 @@ public class ActivityUtils {
      * Finish this activity and launch the default home screen activity.
      */
     public static void goToHomeScreen(Activity activity) {
         Intent intent = new Intent(Intent.ACTION_MAIN);
 
         intent.addCategory(Intent.CATEGORY_HOME);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         activity.startActivity(intent);
+    }
 
+    public static Activity getActivityFromContext(Context context) {
+        while (context instanceof ContextWrapper) {
+            if (context instanceof Activity) {
+                return (Activity) context;
+            }
+            context = ((ContextWrapper) context).getBaseContext();
+        }
+        return null;
     }
 }