WIP draft
authorNevin Chen <cnevinchen@gmail.com>
Wed, 19 Apr 2017 18:20:50 +0800
changeset 565004 958a39bddbdddab2afac596e84f792a907a75dfa
parent 565003 80cfe2241a508adf12f6b93f67a9af917b37e91f
child 624886 3436fff426f8921e84ffd65fde824039df9dfd27
push id54752
push userbmo:cnevinchen@gmail.com
push dateWed, 19 Apr 2017 10:21:23 +0000
milestone55.0a1
WIP MozReview-Commit-ID: HQVayaoBhN4
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/deeplink/DeepLinkAction.java
mobile/android/base/java/org/mozilla/gecko/deeplink/DeepLinkUtil.java
mobile/android/base/moz.build
mobile/android/base/strings.xml.in
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -21,16 +21,20 @@ import org.mozilla.gecko.AppConstants.Ve
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.Tabs.TabEvents;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.cleanup.FileCleanupController;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.SuggestedSites;
+import org.mozilla.gecko.deeplink.DeepLinkAction;
+import org.mozilla.gecko.deeplink.DeepLinkListener;
+import org.mozilla.gecko.deeplink.DeepLinkStore;
+import org.mozilla.gecko.deeplink.DeepLinkUtil;
 import org.mozilla.gecko.delegates.BrowserAppDelegate;
 import org.mozilla.gecko.delegates.OfflineTabStatusDelegate;
 import org.mozilla.gecko.delegates.ScreenshotDelegate;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.distribution.DistributionStoreCallback;
 import org.mozilla.gecko.dlc.DownloadContentService;
 import org.mozilla.gecko.icons.IconsHelper;
 import org.mozilla.gecko.icons.decoders.IconDirectoryEntry;
@@ -155,18 +159,20 @@ import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.animation.Interpolator;
 import android.widget.Button;
 import android.widget.ListView;
 import android.widget.RelativeLayout;
 import android.widget.ViewFlipper;
+
 import org.mozilla.gecko.switchboard.AsyncConfigLoader;
 import org.mozilla.gecko.switchboard.SwitchBoard;
+
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.net.URLEncoder;
@@ -175,27 +181,28 @@ import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Vector;
 import java.util.regex.Pattern;
 
 public class BrowserApp extends GeckoApp
-                        implements TabsPanel.TabsLayoutChangeListener,
-                                   PropertyAnimator.PropertyAnimationListener,
-                                   View.OnKeyListener,
-                                   LayerView.DynamicToolbarListener,
-                                   BrowserSearch.OnSearchListener,
-                                   BrowserSearch.OnEditSuggestionListener,
-                                   OnUrlOpenListener,
-                                   OnUrlOpenInBackgroundListener,
-                                   AnchoredPopup.OnVisibilityChangeListener,
-                                   ActionModeCompat.Presenter,
-                                   LayoutInflater.Factory {
+        implements TabsPanel.TabsLayoutChangeListener,
+        PropertyAnimator.PropertyAnimationListener,
+        View.OnKeyListener,
+        LayerView.DynamicToolbarListener,
+        BrowserSearch.OnSearchListener,
+        BrowserSearch.OnEditSuggestionListener,
+        OnUrlOpenListener,
+        OnUrlOpenInBackgroundListener,
+        AnchoredPopup.OnVisibilityChangeListener,
+        ActionModeCompat.Presenter,
+        LayoutInflater.Factory,
+        DeepLinkListener {
     private static final String LOGTAG = "GeckoBrowserApp";
 
     private static final int TABS_ANIMATION_DURATION = 450;
 
     // Intent String extras used to specify custom Switchboard configurations.
     private static final String INTENT_KEY_SWITCHBOARD_SERVER = "switchboard-server";
 
     // TODO: Replace with kinto endpoint.
@@ -246,16 +253,44 @@ public class BrowserApp extends GeckoApp
     private ActionModeCompat mActionMode;
     private TabHistoryController tabHistoryController;
     private ZoomedView mZoomedView;
 
     private static final int GECKO_TOOLS_MENU = -1;
     private static final int ADDON_MENU_OFFSET = 1000;
     public static final String TAB_HISTORY_FRAGMENT_TAG = "tabHistoryFragment";
 
+    @Override
+    public void execute(DeepLinkAction action) {
+        if (action.getType() == DeepLinkAction.TYPE.DEFAULT_BROWSER) {
+            boolean isDefaultBrowser = getSharedPreferences().getBoolean(GeckoPreferences.PREFS_DEFAULT_BROWSER, false);
+            if (isDefaultBrowser(Intent.ACTION_VIEW)) {
+                return;
+            }
+            GeckoSharedPrefs.forApp(this).edit().putBoolean(GeckoPreferences.PREFS_DEFAULT_BROWSER, true);
+
+            if (AppConstants.Versions.feature24Plus) {
+                // We are special casing the link to set the default browser here: On old Android versions we
+                // link to a SUMO page but on new Android versions we can link to the default app settings where
+                // the user can actually set a default browser (Bug 1312686).
+                Intent changeDefaultApps = new Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS");
+                getContext().startActivity(changeDefaultApps);
+            } else {
+                Tabs.getInstance().loadUrlInTab(getString(R.string.pref_default_browser_link));
+
+            }
+        }
+
+    }
+
+    @Override
+    public boolean isFinishing() {
+        return false;
+    }
+
     private static class MenuItemInfo {
         public int id;
         public String label;
         public boolean checkable;
         public boolean checked;
         public boolean enabled = true;
         public boolean visible = true;
         public int parent;
@@ -526,24 +561,25 @@ public class BrowserApp extends GeckoApp
                     return true;
 
                 case KeyEvent.KEYCODE_W:
                     Tabs.getInstance().closeTab(tab);
                     return true;
 
                 case KeyEvent.KEYCODE_F:
                     mFindInPageBar.show();
-                return true;
+                    return true;
             }
         }
 
         return false;
     }
 
     private Runnable mCheckLongPress;
+
     {
         // Only initialise the runnable if we are >= N.
         // See onKeyDown() for more details of the back-button long-press workaround
         if (!Versions.preN) {
             mCheckLongPress = new Runnable() {
                 public void run() {
                     handleBackLongPress();
                 }
@@ -580,16 +616,20 @@ public class BrowserApp extends GeckoApp
         if (AndroidGamepadManager.handleKeyEvent(event)) {
             return true;
         }
         return super.onKeyUp(keyCode, event);
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
+
+
+        handleDeepLink();
+
         final Context appContext = getApplicationContext();
 
         GeckoLoader.loadMozGlue(appContext);
         if (!HardwareUtils.isSupportedSystem() || !GeckoLoader.neonCompatible()) {
             // This build does not support the Android version of the device; Exit early.
             super.onCreate(savedInstanceState);
             return;
         }
@@ -727,48 +767,48 @@ public class BrowserApp extends GeckoApp
         setBrowserToolbarListeners();
 
         mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page);
         mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
 
         mDoorhangerOverlay = findViewById(R.id.doorhanger_overlay);
 
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
-            "Search:Keyword",
-            null);
+                "Search:Keyword",
+                null);
 
         EventDispatcher.getInstance().registerUiThreadListener(this,
-            "Menu:Open",
-            "Menu:Update",
-            "Menu:Add",
-            "Menu:Remove",
-            "LightweightTheme:Update",
-            "Tab:Added",
-            "Video:Play",
-            "CharEncoding:Data",
-            "CharEncoding:State",
-            "Settings:Show",
-            "Updater:Launch",
-            "Sanitize:OpenTabs",
-            null);
+                "Menu:Open",
+                "Menu:Update",
+                "Menu:Add",
+                "Menu:Remove",
+                "LightweightTheme:Update",
+                "Tab:Added",
+                "Video:Play",
+                "CharEncoding:Data",
+                "CharEncoding:State",
+                "Settings:Show",
+                "Updater:Launch",
+                "Sanitize:OpenTabs",
+                null);
 
         EventDispatcher.getInstance().registerBackgroundThreadListener(this,
-            "Experiments:GetActive",
-            "Experiments:SetOverride",
-            "Experiments:ClearOverride",
-            "Favicon:Request",
-            "Feedback:MaybeLater",
-            "Sanitize:ClearHistory",
-            "Sanitize:ClearSyncedTabs",
-            "Telemetry:Gather",
-            "Download:AndroidDownloadManager",
-            "Website:AppInstalled",
-            "Website:AppInstallFailed",
-            "Website:Metadata",
-            null);
+                "Experiments:GetActive",
+                "Experiments:SetOverride",
+                "Experiments:ClearOverride",
+                "Favicon:Request",
+                "Feedback:MaybeLater",
+                "Sanitize:ClearHistory",
+                "Sanitize:ClearSyncedTabs",
+                "Telemetry:Gather",
+                "Download:AndroidDownloadManager",
+                "Website:AppInstalled",
+                "Website:AppInstallFailed",
+                "Website:Metadata",
+                null);
 
         getAppEventDispatcher().registerUiThreadListener(this, "Prompt:ShowTop");
 
         final GeckoProfile profile = getProfile();
 
         // We want to upload the telemetry core ping as soon after startup as possible. It relies on the
         // Distribution being initialized. If you move this initialization, ensure it plays well with telemetry.
         final Distribution distribution = Distribution.init(getApplicationContext());
@@ -818,38 +858,50 @@ public class BrowserApp extends GeckoApp
         // Set the maximum bits-per-pixel the favicon system cares about.
         IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
 
         // The update service is enabled for RELEASE_OR_BETA, which includes the release and beta channels.
         // However, no updates are served.  Therefore, we don't trust the update service directly, and
         // try to avoid prompting unnecessarily. See Bug 1232798.
         if (!AppConstants.RELEASE_OR_BETA && UpdateServiceHelper.isUpdaterEnabled(this)) {
             Permissions.from(this)
-                       .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
-                       .doNotPrompt()
-                       .andFallback(new Runnable() {
-                           @Override
-                           public void run() {
-                               showUpdaterPermissionSnackbar();
-                           }
-                       })
-                      .run();
+                    .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                    .doNotPrompt()
+                    .andFallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            showUpdaterPermissionSnackbar();
+                        }
+                    })
+                    .run();
         }
 
         for (final BrowserAppDelegate delegate : delegates) {
             delegate.onCreate(this, savedInstanceState);
         }
 
         // We want to get an understanding of how our user base is spread (bug 1221646).
         final String installerPackageName = getPackageManager().getInstallerPackageName(getPackageName());
         Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, TelemetryContract.Method.SYSTEM, "installer_" + installerPackageName);
     }
 
+    private void handleDeepLink() {
+        DeepLinkStore.register(DeepLinkAction.TYPE.DEFAULT_BROWSER, this);
+        Intent intent = getIntent();
+        if (intent != null && intent.getData() != null) {
+            String deepLink = DeepLinkUtil.getDeepLink(intent.getData().toString());
+            if (deepLink != null && deepLink.equalsIgnoreCase(DeepLinkAction.TYPE.DEFAULT_BROWSER.name())) {
+                DeepLinkStore.dispatch(new DeepLinkAction(DeepLinkAction.TYPE.DEFAULT_BROWSER, deepLink));
+            }
+        }
+    }
+
     /**
      * Initializes the default Switchboard URLs the first time.
+     *
      * @param intent
      */
     private static void initSwitchboard(final Context context, final SafeIntent intent, final boolean isInAutomation) {
         if (isInAutomation) {
             Log.d(LOGTAG, "Switchboard disabled - in automation");
             return;
         } else if (!AppConstants.MOZ_SWITCHBOARD) {
             Log.d(LOGTAG, "Switchboard compile-time disabled");
@@ -886,19 +938,19 @@ public class BrowserApp extends GeckoApp
     private void conditionallyNotifyEOL() {
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
         try {
             final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
             if (!prefs.contains(EOL_NOTIFIED)) {
 
                 // Launch main App to load SUMO url on EOL notification.
                 final String link = getString(R.string.eol_notification_url,
-                                              AppConstants.MOZ_APP_VERSION,
-                                              AppConstants.OS_TARGET,
-                                              Locales.getLanguageTag(Locale.getDefault()));
+                        AppConstants.MOZ_APP_VERSION,
+                        AppConstants.OS_TARGET,
+                        Locales.getLanguageTag(Locale.getDefault()));
 
                 final Intent intent = new Intent(Intent.ACTION_VIEW);
                 intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
                 intent.setData(Uri.parse(link));
                 final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
 
                 final Notification notification = new NotificationCompat.Builder(this)
                         .setContentTitle(getString(R.string.eol_notification_title))
@@ -908,19 +960,19 @@ public class BrowserApp extends GeckoApp
                         .setContentIntent(pendingIntent)
                         .build();
 
                 final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                 final int notificationID = EOL_NOTIFIED.hashCode();
                 notificationManager.notify(notificationID, notification);
 
                 GeckoSharedPrefs.forProfile(this)
-                                .edit()
-                                .putBoolean(EOL_NOTIFIED, true)
-                                .apply();
+                        .edit()
+                        .putBoolean(EOL_NOTIFIED, true)
+                        .apply();
             }
         } finally {
             StrictMode.setThreadPolicy(savedPolicy);
         }
     }
 
     /**
      * Code to actually show the first run pager, separated
@@ -941,17 +993,17 @@ public class BrowserApp extends GeckoApp
         }
     }
 
     /**
      * Check and show the firstrun pane if the browser has never been launched and
      * is not opening an external link from another application.
      *
      * @param context Context of application; used to show firstrun pane if appropriate
-     * @param intent Intent that launched this activity
+     * @param intent  Intent that launched this activity
      */
     private void checkFirstrun(Context context, SafeIntent intent) {
         if (getProfile().inGuestMode()) {
             // We do not want to show any first run tour for guest profiles.
             return;
         }
 
         if (intent.getBooleanExtra(EXTRA_SKIP_STARTPANE, false)) {
@@ -960,17 +1012,17 @@ public class BrowserApp extends GeckoApp
             return;
         }
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
 
         try {
             final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
 
             if (prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED_OLD, true) &&
-                prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, true)) {
+                    prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, true)) {
                 if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
                     // Check to see if a distribution has turned off the first run pager.
                     final Distribution distribution = Distribution.getInstance(BrowserApp.this);
                     if (!distribution.shouldWaitForSystemDistribution()) {
                         checkFirstrunInternal();
                     } else {
                         distribution.addOnDistributionReadyCallback(new Distribution.ReadyCallback() {
                             @Override
@@ -1422,17 +1474,17 @@ public class BrowserApp extends GeckoApp
             ThreadUtils.postToBackgroundThread(new Runnable() {
                 @Override
                 public void run() {
                     GeckoAppShell.createShortcut(title, url);
                 }
             });
 
             Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU,
-                getResources().getResourceEntryName(itemId));
+                    getResources().getResourceEntryName(itemId));
             return true;
         }
 
         if (itemId == R.id.set_as_homepage) {
             final Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab == null) {
                 return true;
             }
@@ -1442,17 +1494,17 @@ public class BrowserApp extends GeckoApp
                 return true;
             }
             final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
             final SharedPreferences.Editor editor = prefs.edit();
             editor.putString(GeckoPreferences.PREFS_HOMEPAGE, url);
             editor.apply();
 
             Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU,
-                getResources().getResourceEntryName(itemId));
+                    getResources().getResourceEntryName(itemId));
             return true;
         }
 
         return false;
     }
 
     @Override
     public void setAccessibilityEnabled(boolean enabled) {
@@ -1503,48 +1555,48 @@ public class BrowserApp extends GeckoApp
 
         if (mZoomedView != null) {
             mZoomedView.destroy();
         }
 
         mSearchEngineManager.unregisterListeners();
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
-            "Search:Keyword",
-            null);
+                "Search:Keyword",
+                null);
 
         EventDispatcher.getInstance().unregisterUiThreadListener(this,
-            "Menu:Open",
-            "Menu:Update",
-            "Menu:Add",
-            "Menu:Remove",
-            "LightweightTheme:Update",
-            "Tab:Added",
-            "Video:Play",
-            "CharEncoding:Data",
-            "CharEncoding:State",
-            "Settings:Show",
-            "Updater:Launch",
-            "Sanitize:OpenTabs",
-            null);
+                "Menu:Open",
+                "Menu:Update",
+                "Menu:Add",
+                "Menu:Remove",
+                "LightweightTheme:Update",
+                "Tab:Added",
+                "Video:Play",
+                "CharEncoding:Data",
+                "CharEncoding:State",
+                "Settings:Show",
+                "Updater:Launch",
+                "Sanitize:OpenTabs",
+                null);
 
         EventDispatcher.getInstance().unregisterBackgroundThreadListener(this,
-            "Experiments:GetActive",
-            "Experiments:SetOverride",
-            "Experiments:ClearOverride",
-            "Favicon:Request",
-            "Feedback:MaybeLater",
-            "Sanitize:ClearHistory",
-            "Sanitize:ClearSyncedTabs",
-            "Telemetry:Gather",
-            "Download:AndroidDownloadManager",
-            "Website:AppInstalled",
-            "Website:AppInstallFailed",
-            "Website:Metadata",
-            null);
+                "Experiments:GetActive",
+                "Experiments:SetOverride",
+                "Experiments:ClearOverride",
+                "Favicon:Request",
+                "Feedback:MaybeLater",
+                "Sanitize:ClearHistory",
+                "Sanitize:ClearSyncedTabs",
+                "Telemetry:Gather",
+                "Download:AndroidDownloadManager",
+                "Website:AppInstalled",
+                "Website:AppInstallFailed",
+                "Website:Metadata",
+                null);
 
         getAppEventDispatcher().unregisterUiThreadListener(this, "Prompt:ShowTop");
 
         if (AppConstants.MOZ_ANDROID_BEAM) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 // null this out even though the docs say it's not needed,
                 // because the source code looks like it will only do this
@@ -1582,18 +1634,18 @@ public class BrowserApp extends GeckoApp
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
             GeckoThread.waitOnGecko();
         }
 
         if (mRestartIntent != null) {
             // Restarting, so let Restarter kill us.
             final Intent intent = new Intent();
             intent.setClass(getApplicationContext(), Restarter.class)
-                  .putExtra("pid", Process.myPid())
-                  .putExtra(Intent.EXTRA_INTENT, mRestartIntent);
+                    .putExtra("pid", Process.myPid())
+                    .putExtra(Intent.EXTRA_INTENT, mRestartIntent);
             startService(intent);
         } else {
             // Exiting, so kill our own process.
             Process.killProcess(Process.myPid());
         }
     }
 
     @Override
@@ -1671,31 +1723,31 @@ public class BrowserApp extends GeckoApp
         if (mFormAssistPopup != null) {
             mFormAssistPopup.onMetricsChanged(aMetrics);
         }
     }
 
     @Override
     public void onPanZoomStopped() {
         if (!mDynamicToolbar.isEnabled() || isHomePagerVisible() ||
-            mBrowserChrome.getVisibility() != View.VISIBLE) {
+                mBrowserChrome.getVisibility() != View.VISIBLE) {
             return;
         }
 
         // Make sure the toolbar is fully hidden or fully shown when the user
         // lifts their finger, depending on various conditions.
         ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
         float toolbarTranslation = mLayerView.getDynamicToolbarAnimator().getToolbarTranslation();
 
         boolean shortPage = metrics.getPageHeight() < metrics.getHeight();
         boolean atBottomOfLongPage =
-            FloatUtils.fuzzyEquals(metrics.pageRectBottom, metrics.viewportRectBottom())
-            && (metrics.pageRectBottom > 2 * metrics.getHeight());
+                FloatUtils.fuzzyEquals(metrics.pageRectBottom, metrics.viewportRectBottom())
+                        && (metrics.pageRectBottom > 2 * metrics.getHeight());
         Log.v(LOGTAG, "On pan/zoom stopped, short page: " + shortPage
-            + "; atBottomOfLongPage: " + atBottomOfLongPage);
+                + "; atBottomOfLongPage: " + atBottomOfLongPage);
         if (shortPage || atBottomOfLongPage) {
             mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
         }
     }
 
     public void refreshToolbarHeight() {
         ThreadUtils.assertOnUiThread();
 
@@ -1851,17 +1903,17 @@ public class BrowserApp extends GeckoApp
                 if (mBrowserToolbar.isEditing()) {
                     mBrowserToolbar.cancelEdit();
                 }
                 openOptionsMenu();
                 break;
 
             case "Menu:Update":
                 updateAddonMenuItem(message.getInt("id") + ADDON_MENU_OFFSET,
-                                    message.getBundle("options"));
+                        message.getBundle("options"));
                 break;
 
             case "Menu:Add":
                 final MenuItemInfo info = new MenuItemInfo();
                 info.label = message.getString("name");
                 info.id = message.getInt("id") + ADDON_MENU_OFFSET;
                 info.checked = message.getBoolean("checked", false);
                 info.enabled = message.getBoolean("enabled", true);
@@ -1885,17 +1937,17 @@ public class BrowserApp extends GeckoApp
                 recordSearch(GeckoSharedPrefs.forProfile(this), message.getString("identifier"),
                         TelemetryContract.Method.ACTIONBAR);
                 break;
 
             case "Prompt:ShowTop":
                 // Bring this activity to front so the prompt is visible..
                 Intent bringToFrontIntent = new Intent();
                 bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
-                                                AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
+                        AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
                 bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
                 startActivity(bringToFrontIntent);
                 break;
 
             case "Tab:Added":
                 if (message.getBoolean("cancelEditMode")) {
                     // Set the target tab to null so it does not get selected (on editing
                     // mode exit) in lieu of the tab that we're going to open and select.
@@ -1903,17 +1955,17 @@ public class BrowserApp extends GeckoApp
                     mBrowserToolbar.cancelEdit();
                 }
                 break;
 
             case "Video:Play":
                 if (SwitchBoard.isInExperiment(this, Experiments.HLS_VIDEO_PLAYBACK)) {
                     mVideoPlayer.start(Uri.parse(message.getString("uri")));
                     Telemetry.sendUIEvent(TelemetryContract.Event.SHOW,
-                                          TelemetryContract.Method.CONTENT, "playhls");
+                            TelemetryContract.Method.CONTENT, "playhls");
                 }
                 break;
 
             case "CharEncoding:Data":
                 final GeckoBundle[] charsets = message.getBundleArray("charsets");
                 final int selected = message.getInt("selected");
 
                 final String[] titleArray = new String[charsets.length];
@@ -1955,17 +2007,17 @@ public class BrowserApp extends GeckoApp
 
             case "Experiments:GetActive":
                 final List<String> experiments = SwitchBoard.getActiveExperiments(this);
                 callback.sendSuccess(experiments.toArray(new String[experiments.size()]));
                 break;
 
             case "Experiments:SetOverride":
                 Experiments.setOverride(getContext(), message.getString("name"),
-                                        message.getBoolean("isEnabled"));
+                        message.getBoolean("isEnabled"));
                 break;
 
             case "Experiments:ClearOverride":
                 Experiments.clearOverride(getContext(), message.getString("name"));
                 break;
 
             case "Favicon:Request":
                 final String url = message.getString("url");
@@ -2029,40 +2081,40 @@ public class BrowserApp extends GeckoApp
                 Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT",
                         (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0));
                 Telemetry.addToHistogram("FENNEC_CUSTOM_HOMEPAGE",
                         (TextUtils.isEmpty(Tabs.getHomepage(this)) ? 0 : 1));
 
                 final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
                 final boolean hasCustomHomepanels =
                         prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY) ||
-                        prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY_OLD);
+                                prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY_OLD);
 
                 Telemetry.addToHistogram("FENNEC_HOMEPANELS_CUSTOM", hasCustomHomepanels ? 1 : 0);
 
                 Telemetry.addToHistogram("FENNEC_READER_VIEW_CACHE_SIZE",
                         SavedReaderViewHelper.getSavedReaderViewHelper(getContext())
-                                             .getDiskSpacedUsedKB());
+                                .getDiskSpacedUsedKB());
 
                 if (Versions.feature16Plus) {
                     Telemetry.addToHistogram("BROWSER_IS_ASSIST_DEFAULT",
                             (isDefaultBrowser(Intent.ACTION_ASSIST) ? 1 : 0));
                 }
 
                 Telemetry.addToHistogram("FENNEC_ORBOT_INSTALLED",
-                    ContextUtils.isPackageInstalled(getContext(), "org.torproject.android") ? 1 : 0);
+                        ContextUtils.isPackageInstalled(getContext(), "org.torproject.android") ? 1 : 0);
                 break;
 
             case "Website:AppInstalled":
                 final String name = message.getString("name");
                 final String startUrl = message.getString("start_url");
                 final String manifestPath = message.getString("manifest_path");
                 final Bitmap icon = FaviconDecoder
-                    .decodeDataURI(getContext(), message.getString("icon"))
-                    .getBestBitmap(GeckoAppShell.getPreferredIconSize());
+                        .decodeDataURI(getContext(), message.getString("icon"))
+                        .getBestBitmap(GeckoAppShell.getPreferredIconSize());
                 createAppShortcut(name, startUrl, manifestPath, icon);
                 break;
 
             case "Website:AppInstallFailed":
                 final String title = message.getString("title");
                 final String bookmarkUrl = message.getString("url");
                 createBrowserShortcut(title, bookmarkUrl);
                 break;
@@ -2129,17 +2181,17 @@ public class BrowserApp extends GeckoApp
                 final String location = message.getString("location");
                 final boolean hasImage = message.getBoolean("hasImage");
                 final String metadata = message.getString("metadata");
 
                 final ContentProviderClient contentProviderClient = getContentResolver()
                         .acquireContentProviderClient(BrowserContract.PageMetadata.CONTENT_URI);
                 if (contentProviderClient == null) {
                     Log.w(LOGTAG, "Failed to obtain content provider client for: " +
-                                  BrowserContract.PageMetadata.CONTENT_URI);
+                            BrowserContract.PageMetadata.CONTENT_URI);
                     return;
                 }
                 try {
                     GlobalPageMetadata.getInstance().add(
                             BrowserDB.from(getProfile()),
                             contentProviderClient,
                             location, hasImage, metadata);
                 } finally {
@@ -2199,20 +2251,21 @@ public class BrowserApp extends GeckoApp
     public void showNormalTabs() {
         showTabs(TabsPanel.Panel.NORMAL_TABS);
     }
 
     @Override
     public void showPrivateTabs() {
         showTabs(TabsPanel.Panel.PRIVATE_TABS);
     }
+
     /**
-    * Ensure the TabsPanel view is properly inflated and returns
-    * true when the view has been inflated, false otherwise.
-    */
+     * Ensure the TabsPanel view is properly inflated and returns
+     * true when the view has been inflated, false otherwise.
+     */
     private boolean ensureTabsPanelExists() {
         if (mTabsPanel != null) {
             return false;
         }
 
         ViewStub tabsPanelStub = (ViewStub) findViewById(R.id.tabs_panel);
         mTabsPanel = (TabsPanel) tabsPanelStub.inflate();
 
@@ -2289,17 +2342,17 @@ public class BrowserApp extends GeckoApp
     }
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Override
     public void onTabsLayoutChange(int width, int height) {
         int animationLength = TABS_ANIMATION_DURATION;
 
         if (mMainLayoutAnimator != null) {
-            animationLength = Math.max(1, animationLength - (int)mMainLayoutAnimator.getRemainingTime());
+            animationLength = Math.max(1, animationLength - (int) mMainLayoutAnimator.getRemainingTime());
             mMainLayoutAnimator.stop(false);
         }
 
         if (areTabsShown()) {
             mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
             // Hide the web content from accessibility tools even though it's visible
             // so that you can't examine it as long as the tabs are being shown.
             if (Versions.feature16Plus) {
@@ -2309,18 +2362,18 @@ public class BrowserApp extends GeckoApp
             if (Versions.feature16Plus) {
                 mLayerView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
             }
         }
 
         mMainLayoutAnimator = new PropertyAnimator(animationLength, sTabsInterpolator);
         mMainLayoutAnimator.addPropertyAnimationListener(this);
         mMainLayoutAnimator.attach(mMainLayout,
-                                   PropertyAnimator.Property.SCROLL_Y,
-                                   -height);
+                PropertyAnimator.Property.SCROLL_Y,
+                -height);
 
         mTabsPanel.prepareTabsAnimation(mMainLayoutAnimator);
         mBrowserToolbar.triggerTabsPanelTransition(mMainLayoutAnimator, areTabsShown());
 
         // If the tabs panel is animating onto the screen, pin the dynamic
         // toolbar.
         if (mDynamicToolbar.isEnabled()) {
             if (width > 0 && height > 0) {
@@ -2362,19 +2415,19 @@ public class BrowserApp extends GeckoApp
     }
 
     /**
      * Attempts to switch to an open tab with the given URL.
      * <p>
      * If the tab exists, this method cancels any in-progress editing as well as
      * calling {@link Tabs#selectTab(int)}.
      *
-     * @param url of tab to switch to.
+     * @param url   of tab to switch to.
      * @param flags to obey: if {@link OnUrlOpenListener.Flags#ALLOW_SWITCH_TO_TAB}
-     *        is not present, return false.
+     *              is not present, return false.
      * @return true if we successfully switched to a tab, false otherwise.
      */
     private boolean maybeSwitchToTab(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
         if (!flags.contains(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)) {
             return false;
         }
 
         final Tabs tabs = Tabs.getInstance();
@@ -2524,17 +2577,17 @@ public class BrowserApp extends GeckoApp
     }
 
     private void commitEditingMode() {
         if (!mBrowserToolbar.isEditing()) {
             return;
         }
 
         Telemetry.stopUISession(TelemetryContract.Session.AWESOMESCREEN,
-                                TelemetryContract.Reason.COMMIT);
+                TelemetryContract.Reason.COMMIT);
 
         final String url = mBrowserToolbar.commitEdit();
 
         // HACK: We don't know the url that will be loaded when hideHomePager is initially called
         // in BrowserToolbar's onStopEditing listener so on the awesomescreen, hideHomePager will
         // use the url "about:home" and return without taking any action. hideBrowserSearch is
         // then called, but since hideHomePager changes both HomePager and LayerView visibility
         // and exited without taking an action, no Views are displayed and graphical corruption is
@@ -2600,40 +2653,39 @@ public class BrowserApp extends GeckoApp
                 // Otherwise, construct a search query from the bookmark keyword.
                 // Replace lower case bookmark keywords with URLencoded search query or
                 // replace upper case bookmark keywords with un-encoded search query.
                 // This makes it match the same behaviour as on Firefox for the desktop.
                 final String searchUrl = keywordUrl.replace("%s", URLEncoder.encode(keywordSearch)).replace("%S", keywordSearch);
 
                 Tabs.getInstance().loadUrl(searchUrl, Tabs.LOADURL_USER_ENTERED);
                 Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL,
-                                      TelemetryContract.Method.ACTIONBAR,
-                                      "keyword");
+                        TelemetryContract.Method.ACTIONBAR,
+                        "keyword");
             }
         });
     }
 
     /**
      * Records in telemetry that a search has occurred.
      *
      * @param where where the search was started from
      */
     private static void recordSearch(@NonNull final SharedPreferences prefs, @NonNull final String engineIdentifier,
-            @NonNull final TelemetryContract.Method where) {
+                                     @NonNull final TelemetryContract.Method where) {
         // We could include the engine identifier as an extra but we'll
         // just capture that with core ping telemetry (bug 1253319).
         Telemetry.sendUIEvent(TelemetryContract.Event.SEARCH, where);
         SearchCountMeasurements.incrementSearch(prefs, engineIdentifier, where.toString());
     }
 
     /**
      * Store search query in SearchHistoryProvider.
      *
-     * @param query
-     *        a search query to store. We won't store empty queries.
+     * @param query a search query to store. We won't store empty queries.
      */
     private void storeSearchQuery(final String query) {
         if (TextUtils.isEmpty(query)) {
             return;
         }
 
         // Filter out URLs and long suggestions
         if (query.length() > 50 || Pattern.matches("^(https?|ftp|file)://.*", query)) {
@@ -2662,21 +2714,21 @@ public class BrowserApp extends GeckoApp
             showBrowserSearch();
             mBrowserSearch.filter(searchTerm, handler);
         }
     }
 
     /**
      * Selects the target tab for editing mode. This is expected to be the tab selected on editing
      * mode entry, unless it is subsequently overridden.
-     *
+     * <p>
      * A background tab may be selected while editing mode is active (e.g. popups), causing the
      * new url to load in the newly selected tab. Call this method on editing mode exit to
      * mitigate this.
-     *
+     * <p>
      * Note that this method is disabled for new tablets because we can see the selected tab in the
      * tab strip and, when the selected tab changes during editing mode as in this hack, the
      * temporarily selected tab is visible to users.
      */
     private void selectTargetTabForEditingMode() {
         if (HardwareUtils.isTablet()) {
             return;
         }
@@ -2798,17 +2850,17 @@ public class BrowserApp extends GeckoApp
         if (mFirstrunAnimationContainer == null) {
             final ViewStub firstrunPagerStub = (ViewStub) findViewById(R.id.firstrun_pager_stub);
             mFirstrunAnimationContainer = (FirstrunAnimationContainer) firstrunPagerStub.inflate();
             mFirstrunAnimationContainer.load(getApplicationContext(), getSupportFragmentManager());
             mFirstrunAnimationContainer.registerOnFinishListener(new FirstrunAnimationContainer.OnFinishListener() {
                 @Override
                 public void onFinish() {
                     if (mFirstrunAnimationContainer.showBrowserHint() &&
-                        TextUtils.isEmpty(Tabs.getHomepage(BrowserApp.this))) {
+                            TextUtils.isEmpty(Tabs.getHomepage(BrowserApp.this))) {
                         enterEditingMode();
                     }
                 }
             });
         }
 
         mHomeScreenContainer.setVisibility(View.VISIBLE);
     }
@@ -2836,17 +2888,17 @@ public class BrowserApp extends GeckoApp
         // Show the toolbar before hiding about:home so the
         // onMetricsChanged callback still works.
         if (mDynamicToolbar.isEnabled()) {
             mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
         }
 
         if (mHomeScreen == null) {
             if (ActivityStream.isEnabled(this) &&
-                !ActivityStream.isHomePanel()) {
+                    !ActivityStream.isHomePanel()) {
                 final ViewStub asStub = (ViewStub) findViewById(R.id.activity_stream_stub);
                 mHomeScreen = (HomeScreen) asStub.inflate();
             } else {
                 final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
                 mHomeScreen = (HomeScreen) homePagerStub.inflate();
 
                 // For now these listeners are HomePager specific. In future we might want
                 // to have a more abstracted data storage, with one Bundle containing all
@@ -2897,20 +2949,20 @@ public class BrowserApp extends GeckoApp
                         mHomeScreenContainer.removeView(homeBanner);
                     }
                 });
             }
         }
 
         mHomeScreenContainer.setVisibility(View.VISIBLE);
         mHomeScreen.load(getSupportLoaderManager(),
-                        getSupportFragmentManager(),
-                        panelId,
-                        panelRestoreData,
-                        animator);
+                getSupportFragmentManager(),
+                panelId,
+                panelRestoreData,
+                animator);
 
         // Hide the web content so it cannot be focused by screen readers.
         hideWebContentOnPropertyAnimationEnd(animator);
     }
 
     private void hideWebContentOnPropertyAnimationEnd(final PropertyAnimator animator) {
         if (animator == null) {
             hideWebContent();
@@ -2935,16 +2987,17 @@ public class BrowserApp extends GeckoApp
     private void hideWebContent() {
         // The view is set to INVISIBLE, rather than GONE, to avoid
         // the additional requestLayout() call.
         mLayerView.setVisibility(View.INVISIBLE);
     }
 
     /**
      * Hide the Onboarding pager on user action, and don't show any onFinish hints.
+     *
      * @param method TelemetryContract method by which action was taken
      * @return boolean of whether pager was visible
      */
     private boolean hideFirstrunPager(TelemetryContract.Method method) {
         if (!isFirstrunVisible()) {
             return false;
         }
 
@@ -3179,20 +3232,20 @@ public class BrowserApp extends GeckoApp
 
         addAddonMenuItemToMenu(mMenu, info);
     }
 
     private void removeAddonMenuItem(int id) {
         // Remove add-on menu item from cache, if available.
         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
             for (MenuItemInfo item : mAddonMenuItemsCache) {
-                 if (item.id == id) {
-                     mAddonMenuItemsCache.remove(item);
-                     break;
-                 }
+                if (item.id == id) {
+                    mAddonMenuItemsCache.remove(item);
+                    break;
+                }
             }
         }
 
         if (mMenu == null)
             return;
 
         final MenuItem menuItem = mMenu.findItem(id);
         if (menuItem != null)
@@ -3231,17 +3284,17 @@ public class BrowserApp extends GeckoApp
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         // Sets mMenu = menu.
         super.onCreateOptionsMenu(menu);
 
         // Inform the menu about the action-items bar.
         if (menu instanceof GeckoMenu &&
-            HardwareUtils.isTablet()) {
+                HardwareUtils.isTablet()) {
             ((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
         }
 
         MenuInflater inflater = getMenuInflater();
         inflater.inflate(R.menu.browser_app_menu, mMenu);
 
         // Add add-on menu items, if any exist.
         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
@@ -3362,20 +3415,20 @@ public class BrowserApp extends GeckoApp
         final MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
 
         // Only show the "Quit" menu item on pre-ICS, television devices,
         // or if the user has explicitly enabled the clear on shutdown pref.
         // (We check the pref last to save the pref read.)
         // In ICS+, it's easy to kill an app through the task switcher.
         final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
         final boolean visible = HardwareUtils.isTelevision() ||
-                                prefs.getBoolean(GeckoPreferences.PREFS_SHOW_QUIT_MENU, false) ||
-                                !PrefUtils.getStringSet(prefs,
-                                                        ClearOnShutdownPref.PREF,
-                                                        new HashSet<String>()).isEmpty();
+                prefs.getBoolean(GeckoPreferences.PREFS_SHOW_QUIT_MENU, false) ||
+                !PrefUtils.getStringSet(prefs,
+                        ClearOnShutdownPref.PREF,
+                        new HashSet<String>()).isEmpty();
         aMenu.findItem(R.id.quit).setVisible(visible);
 
         // If tab data is unavailable we disable most of the context menu and related items and
         // return early.
         if (tab == null || tab.getURL() == null) {
             bookmark.setEnabled(false);
             back.setEnabled(false);
             forward.setEnabled(false);
@@ -3524,18 +3577,18 @@ public class BrowserApp extends GeckoApp
             }
         }
 
         final boolean privateTabVisible = Restrictions.isAllowed(this, Restrictable.PRIVATE_BROWSING);
         MenuUtils.safeSetVisible(aMenu, R.id.new_private_tab, privateTabVisible);
 
         // Disable PDF generation (save and print) for about:home and xul pages.
         boolean allowPDF = (!(isAboutHome(tab) ||
-                               tab.getContentType().equals("application/vnd.mozilla.xul+xml") ||
-                               tab.getContentType().startsWith("video/")));
+                tab.getContentType().equals("application/vnd.mozilla.xul+xml") ||
+                tab.getContentType().startsWith("video/")));
         saveAsPDF.setEnabled(allowPDF);
         print.setEnabled(allowPDF);
         print.setVisible(Versions.feature19Plus);
 
         // Disable find in page for about:home, since it won't work on Java content.
         findInPage.setEnabled(!isAboutHome(tab));
 
         charEncoding.setVisible(GeckoPreferences.getCharEncodingState());
@@ -3802,18 +3855,18 @@ public class BrowserApp extends GeckoApp
                     GuestSession.hideNotification(context);
                 }
                 doRestart();
             }
         });
 
         Resources res = getResources();
         ps.setButtons(new String[] {
-            res.getString(R.string.guest_session_dialog_continue),
-            res.getString(R.string.guest_session_dialog_cancel)
+                res.getString(R.string.guest_session_dialog_continue),
+                res.getString(R.string.guest_session_dialog_cancel)
         });
 
         int titleString = 0;
         int msgString = 0;
         if (type == GuestModeDialog.ENTERING) {
             titleString = R.string.new_guest_session_title;
             msgString = R.string.new_guest_session_text;
         } else {
@@ -3830,17 +3883,17 @@ public class BrowserApp extends GeckoApp
     private boolean handleBackLongPress() {
         // If the tab search history is already shown, do nothing.
         TabHistoryFragment frag = (TabHistoryFragment) getSupportFragmentManager().findFragmentByTag(TAB_HISTORY_FRAGMENT_TAG);
         if (frag != null) {
             return true;
         }
 
         Tab tab = Tabs.getInstance().getSelectedTab();
-        if (tab != null  && !tab.isEditing()) {
+        if (tab != null && !tab.isEditing()) {
             return tabHistoryController.showTabHistory(tab, TabHistoryController.HistoryAction.ALL);
         }
 
         return false;
     }
 
     /**
      * This will detect if the key pressed is back. If so, will show the history.
@@ -3957,19 +4010,19 @@ public class BrowserApp extends GeckoApp
     }
 
     private void showTabQueuePromptIfApplicable(final SafeIntent intent) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 // We only want to show the prompt if the browser has been opened from an external url
                 if (TabQueueHelper.TAB_QUEUE_ENABLED && mInitialized
-                                                     && Intent.ACTION_VIEW.equals(intent.getAction())
-                                                     && !intent.getBooleanExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, false)
-                                                     && TabQueueHelper.shouldShowTabQueuePrompt(BrowserApp.this)) {
+                        && Intent.ACTION_VIEW.equals(intent.getAction())
+                        && !intent.getBooleanExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, false)
+                        && TabQueueHelper.shouldShowTabQueuePrompt(BrowserApp.this)) {
                     Intent promptIntent = new Intent(BrowserApp.this, TabQueuePrompt.class);
                     startActivityForResult(promptIntent, ACTIVITY_REQUEST_TAB_QUEUE);
                 }
             }
         });
     }
 
     // HomePager.OnUrlOpenListener
@@ -4068,17 +4121,19 @@ public class BrowserApp extends GeckoApp
 
     // BrowserSearch.OnEditSuggestionListener
     @Override
     public void onEditSuggestion(String suggestion) {
         mBrowserToolbar.onEditSuggestion(suggestion);
     }
 
     @Override
-    public int getLayout() { return R.layout.gecko_app; }
+    public int getLayout() {
+        return R.layout.gecko_app;
+    }
 
     public SearchEngineManager getSearchEngineManager() {
         return mSearchEngineManager;
     }
 
     // For use from tests only.
     @RobocopTarget
     public ReadingListHelper getReadingListHelper() {
@@ -4125,21 +4180,25 @@ public class BrowserApp extends GeckoApp
 
         // Only slide the urlbar out if it was hidden when the action mode started
         // Don't animate hiding it so that there's no flash as we switch back to url mode
         mDynamicToolbar.setTemporarilyVisible(false, VisibilityTransition.IMMEDIATE);
     }
 
     public static interface TabStripInterface {
         public void refresh();
-        /** Called to let the tab strip know it is now, or is now no longer, being hidden by
-         *  something being drawn over it.
+
+        /**
+         * Called to let the tab strip know it is now, or is now no longer, being hidden by
+         * something being drawn over it.
          */
         void tabStripIsCovered(boolean covered);
+
         void setOnTabChangedListener(OnTabAddedOrRemovedListener listener);
+
         interface OnTabAddedOrRemovedListener {
             void onTabChanged();
         }
     }
 
     @Override
     protected void recordStartupActionTelemetry(final String passedURL, final String action) {
         final TelemetryContract.Method method;
--- a/mobile/android/base/java/org/mozilla/gecko/deeplink/DeepLinkAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/deeplink/DeepLinkAction.java
@@ -2,17 +2,17 @@
 /* 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.deeplink;
 
 
-final class DeepLinkAction {
+public final class DeepLinkAction {
 
     private String uri;
     private TYPE type;
 
     public enum TYPE {
         BOOKMARK_LIST,
         DEFAULT_BROWSER,
         SIGN_UP
--- a/mobile/android/base/java/org/mozilla/gecko/deeplink/DeepLinkUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/deeplink/DeepLinkUtil.java
@@ -7,18 +7,18 @@
 package org.mozilla.gecko.deeplink;
 
 
 final public class DeepLinkUtil {
 
     public static final String SCHEME = "firefox";
 
     // If the action is a deep link, we should handle it.
-    public static boolean isDeepLink(String uri) {
+    public static String getDeepLink(String uri) {
         if (uri == null || uri.length() == 0) {
-            return false;
+            return null;
         }
-        if (uri.toLowerCase().startsWith(SCHEME)) {
-            return true;
+        if (uri.startsWith(SCHEME)) {
+            return uri.replace(SCHEME + "://", "");
         }
-        return false;
+        return null;
     }
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -405,16 +405,20 @@ gbjar.sources += ['java/org/mozilla/geck
     'db/SQLiteBridgeContentProvider.java',
     'db/SuggestedSites.java',
     'db/Table.java',
     'db/TabsAccessor.java',
     'db/TabsProvider.java',
     'db/UrlAnnotations.java',
     'db/URLImageDataTable.java',
     'db/URLMetadata.java',
+    'deeplink/DeepLinkAction.java',
+    'deeplink/DeepLinkListener.java',
+    'deeplink/DeepLinkStore.java',
+    'deeplink/DeepLinkUtil.java',
     'delegates/BookmarkStateChangeDelegate.java',
     'delegates/BrowserAppDelegate.java',
     'delegates/BrowserAppDelegateWithReference.java',
     'delegates/OfflineTabStatusDelegate.java',
     'delegates/ScreenshotDelegate.java',
     'delegates/TabsTrayVisibilityAwareDelegate.java',
     'DevToolsAuthHelper.java',
     'distribution/Distribution.java',
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -318,17 +318,17 @@
   <string name="content_notification_summary">&content_notification_summary;</string>
   <string name="content_notification_title_plural">&content_notification_title_plural;</string>
   <string name="content_notification_action_settings">&content_notification_action_settings2;</string>
   <string name="content_notification_action_read_now">&content_notification_action_read_now;</string>
   <string name="content_notification_updated_on">&content_notification_updated_on;</string>
 
   <string name="pref_default_browser">&pref_default_browser;</string>
   <string name="pref_default_browser_mozilla_support_tablet">&pref_default_browser_mozilla_support_tablet;</string>
-
+  <string name="pref_default_browser_link">https://support.mozilla.org/kb/make-firefox-default-browser-android?utm_source=inproduct&amp;utm_medium=settings&amp;utm_campaign=mobileandroid</string>
   <string name="pref_about_firefox">&pref_about_firefox;</string>
 
   <string name="pref_vendor_faqs">&pref_vendor_faqs;</string>
   <!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/faq -->
   <string name="faq_link">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/faq</string>
 
   <string name="pref_vendor_feedback">&pref_vendor_feedback;</string>
   <!-- https://input.mozilla.org/feedback/android/%VERSION%/%CHANNEL%/?utm_source=feedback-settings