Bug 965453 - Add submenu for bookmarks. r=wesj
authorBrian Nicholson <bnicholson@mozilla.com>
Thu, 20 Feb 2014 21:27:04 -0800
changeset 170205 781da8091e21b23a7356418d7d48951fbea72aec
parent 170204 500650a9eb75c06ee3eb33e65986a4d81e85cb90
child 170206 a9af498c077df6d881ebc44fc05bba4075c74a07
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerswesj
bugs965453
milestone30.0a1
Bug 965453 - Add submenu for bookmarks. r=wesj
mobile/android/base/BrowserApp.java
mobile/android/base/ReaderModeUtils.java
mobile/android/base/Tab.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
mobile/android/base/resources/menu-v11/browser_app_menu.xml
mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
mobile/android/base/resources/menu/browser_app_menu.xml
mobile/android/base/strings.xml.in
mobile/android/base/toolbar/BrowserToolbar.java
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -638,52 +638,16 @@ abstract public class BrowserApp extends
 
     @Override
     public void onPause() {
         super.onPause();
         // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
         registerEventListener("Prompt:ShowTop");
     }
 
-    private void showBookmarkDialog() {
-        final Tab tab = Tabs.getInstance().getSelectedTab();
-        final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
-            @Override
-            public void onPromptFinished(String result) {
-                int itemId = -1;
-                try {
-                  itemId = new JSONObject(result).getInt("button");
-                } catch(JSONException ex) {
-                    Log.e(LOGTAG, "Exception reading bookmark prompt result", ex);
-                }
-
-                if (tab == null)
-                    return;
-
-                if (itemId == 0) {
-                    new EditBookmarkDialog(BrowserApp.this).show(tab.getURL());
-                } else if (itemId == 1) {
-                    String url = tab.getURL();
-                    String title = tab.getDisplayTitle();
-                    Bitmap favicon = tab.getFavicon();
-                    if (url != null && title != null) {
-                        GeckoAppShell.createShortcut(title, url, url, favicon, "");
-                    }
-                }
-            }
-        });
-
-        final Prompt.PromptListItem[] items = new Prompt.PromptListItem[2];
-        Resources res = getResources();
-        items[0] = new Prompt.PromptListItem(res.getString(R.string.contextmenu_edit_bookmark));
-        items[1] = new Prompt.PromptListItem(res.getString(R.string.contextmenu_add_to_launcher));
-
-        ps.show("", "", items, false);
-    }
-
     private void setDynamicToolbarEnabled(boolean enabled) {
         if (enabled) {
             if (mLayerView != null) {
                 mLayerView.getLayerClient().setOnMetricsChangedListener(this);
             }
             setToolbarMargin(0);
             mHomePagerContainer.setPadding(0, mViewFlipper.getHeight(), 0, 0);
         } else {
@@ -740,26 +704,17 @@ abstract public class BrowserApp extends
         }
 
         if (itemId == R.id.share) {
             shareCurrentUrl();
             return true;
         }
 
         if (itemId == R.id.subscribe) {
-            Tab tab = Tabs.getInstance().getSelectedTab();
-            if (tab != null && tab.hasFeeds()) {
-                JSONObject args = new JSONObject();
-                try {
-                    args.put("tabId", tab.getId());
-                } catch (JSONException e) {
-                    Log.e(LOGTAG, "error building json arguments");
-                }
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feeds:Subscribe", args.toString()));
-            }
+            subscribeToFeeds(Tabs.getInstance().getSelectedTab());
             return true;
         }
 
         if (itemId == R.id.add_search_engine) {
             Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab != null && tab.hasOpenSearch()) {
                 JSONObject args = new JSONObject();
                 try {
@@ -779,37 +734,16 @@ abstract public class BrowserApp extends
                 String url = tab.getURL();
                 if (url != null) {
                     Clipboard.setText(url);
                 }
             }
             return true;
         }
 
-        if (itemId == R.id.add_to_launcher) {
-            Tab tab = Tabs.getInstance().getSelectedTab();
-            if (tab == null) {
-                return true;
-            }
-
-            final String url = tab.getURL();
-            final String title = tab.getDisplayTitle();
-            if (url == null || title == null) {
-                return true;
-            }
-
-            final OnFaviconLoadedListener listener = new GeckoAppShell.CreateShortcutFaviconLoadedListener(url, title);
-            Favicons.getSizedFavicon(url,
-                    tab.getFaviconURL(),
-                    Integer.MAX_VALUE,
-                    LoadFaviconTask.FLAG_PERSIST,
-                    listener);
-            return true;
-        }
-
         return false;
     }
 
     @Override
     public void setAccessibilityEnabled(boolean enabled) {
         if (mAccessibilityEnabled == enabled) {
             return;
         }
@@ -2150,16 +2084,19 @@ abstract public class BrowserApp extends
         MenuItem forward = aMenu.findItem(R.id.forward);
         MenuItem share = aMenu.findItem(R.id.share);
         MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
         MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
         MenuItem findInPage = aMenu.findItem(R.id.find_in_page);
         MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
         MenuItem enterGuestMode = aMenu.findItem(R.id.new_guest_session);
         MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
+        MenuItem subscribe = aMenu.findItem(R.id.subscribe);
+        MenuItem addToReadingList = aMenu.findItem(R.id.reading_list_add);
+        MenuItem save = aMenu.findItem(R.id.save);
 
         // Only show the "Quit" menu item on pre-ICS or television devices.
         // In ICS+, it's easy to kill an app through the task switcher.
         aMenu.findItem(R.id.quit).setVisible(Build.VERSION.SDK_INT < 14 || HardwareUtils.isTelevision());
 
         if (tab == null || tab.getURL() == null) {
             bookmark.setEnabled(false);
             back.setEnabled(false);
@@ -2174,21 +2111,20 @@ abstract public class BrowserApp extends
             MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, false);
             MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, false);
             MenuUtils.safeSetEnabled(aMenu, R.id.site_settings, false);
             MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, false);
 
             return true;
         }
 
+        save.setVisible(!GeckoProfile.get(this).inGuestMode());
+
         bookmark.setEnabled(!AboutPages.isAboutReader(tab.getURL()));
-        bookmark.setVisible(!GeckoProfile.get(this).inGuestMode());
-        bookmark.setCheckable(true);
         bookmark.setChecked(tab.isBookmark());
-        bookmark.setIcon(tab.isBookmark() ? R.drawable.ic_menu_bookmark_remove : R.drawable.ic_menu_bookmark_add);
 
         back.setEnabled(tab.canDoBack());
         forward.setEnabled(tab.canDoForward());
         desktopMode.setChecked(tab.getDesktopMode());
         desktopMode.setIcon(tab.getDesktopMode() ? R.drawable.ic_menu_desktop_mode_on : R.drawable.ic_menu_desktop_mode_off);
 
         String url = tab.getURL();
         if (AboutPages.isAboutReader(url)) {
@@ -2268,43 +2204,47 @@ abstract public class BrowserApp extends
 
         charEncoding.setVisible(GeckoPreferences.getCharEncodingState());
 
         if (mProfile.inGuestMode())
             exitGuestMode.setVisible(true);
         else
             enterGuestMode.setVisible(true);
 
+        addToReadingList.setChecked(tab.isReadingListItem());
+        addToReadingList.setEnabled(tab.getReaderEnabled());
+
+        subscribe.setEnabled(tab.hasFeeds());
+
         return true;
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        Tab tab = null;
+        final Tab tab = Tabs.getInstance().getSelectedTab();
         Intent intent = null;
 
         final int itemId = item.getItemId();
 
         if (itemId == R.id.bookmark) {
-            tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
                 if (item.isChecked()) {
                     tab.removeBookmark();
                     Toast.makeText(this, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
                     item.setIcon(R.drawable.ic_menu_bookmark_add);
                 } else {
                     tab.addBookmark();
                     getButtonToast().show(false,
                         getResources().getString(R.string.bookmark_added),
-                        getResources().getString(R.string.bookmark_options),
+                        getResources().getString(R.string.contextmenu_edit_bookmark),
                         null,
                         new ButtonToast.ToastListener() {
                             @Override
                             public void onButtonClicked() {
-                                showBookmarkDialog();
+                                new EditBookmarkDialog(BrowserApp.this).show(tab.getURL());
                             }
 
                             @Override
                             public void onToastHidden(ButtonToast.ReasonHidden reason) { }
                         });
                     item.setIcon(R.drawable.ic_menu_bookmark_remove);
                 }
             }
@@ -2312,31 +2252,28 @@ abstract public class BrowserApp extends
         }
 
         if (itemId == R.id.share) {
             shareCurrentUrl();
             return true;
         }
 
         if (itemId == R.id.reload) {
-            tab = Tabs.getInstance().getSelectedTab();
             if (tab != null)
                 tab.doReload();
             return true;
         }
 
         if (itemId == R.id.back) {
-            tab = Tabs.getInstance().getSelectedTab();
             if (tab != null)
                 tab.doBack();
             return true;
         }
 
         if (itemId == R.id.forward) {
-            tab = Tabs.getInstance().getSelectedTab();
             if (tab != null)
                 tab.doForward();
             return true;
         }
 
         if (itemId == R.id.save_as_pdf) {
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SaveAs:PDF", null));
             return true;
@@ -2369,23 +2306,22 @@ abstract public class BrowserApp extends
         }
 
         if (itemId == R.id.find_in_page) {
             mFindInPageBar.show();
             return true;
         }
 
         if (itemId == R.id.desktop_mode) {
-            Tab selectedTab = Tabs.getInstance().getSelectedTab();
-            if (selectedTab == null)
+            if (tab == null)
                 return true;
             JSONObject args = new JSONObject();
             try {
                 args.put("desktopMode", !item.isChecked());
-                args.put("tabId", selectedTab.getId());
+                args.put("tabId", tab.getId());
             } catch (JSONException e) {
                 Log.e(LOGTAG, "error building json arguments");
             }
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("DesktopMode:Change", args.toString()));
             return true;
         }
 
         if (itemId == R.id.new_tab) {
@@ -2410,16 +2346,35 @@ abstract public class BrowserApp extends
 
         // We have a few menu items that can also be in the context menu. If
         // we have not already handled the item, give the context menu handler
         // a chance.
         if (onContextItemSelected(item)) {
             return true;
         }
 
+        if (itemId == R.id.launcher_add) {
+            addToLauncher(tab.getURL(), tab.getTitle(), tab.getFaviconURL());
+            return true;
+        }
+
+        if (itemId == R.id.reading_list_add) {
+            if (item.isChecked()) {
+                ReaderModeUtils.removeFromReadingList(tab.getURL());
+            } else {
+                ReaderModeUtils.addToReadingList(tab);
+            }
+            return true;
+        }
+
+        if (itemId == R.id.subscribe) {
+            subscribeToFeeds(tab);
+            return true;
+        }
+
         return super.onOptionsItemSelected(item);
     }
 
     private void showGuestModeDialog(final GuestModeDialog type) {
         final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
             @Override
             public void onPromptFinished(String result) {
                 try {
@@ -2454,16 +2409,43 @@ abstract public class BrowserApp extends
         } else {
             titleString = R.string.exit_guest_session_title;
             msgString = R.string.exit_guest_session_text;
         }
 
         ps.show(res.getString(titleString), res.getString(msgString), null, false);
     }
 
+    public void subscribeToFeeds(Tab tab) {
+        if (!tab.hasFeeds()) {
+            return;
+        }
+
+        JSONObject args = new JSONObject();
+        try {
+            args.put("tabId", tab.getId());
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "JSON error", e);
+        }
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feeds:Subscribe", args.toString()));
+    }
+
+    private void addToLauncher(String url, String title, String faviconUrl) {
+        if (url == null || title == null) {
+            return;
+        }
+
+        final OnFaviconLoadedListener listener = new GeckoAppShell.CreateShortcutFaviconLoadedListener(url, title);
+        Favicons.getSizedFavicon(url,
+                faviconUrl,
+                Integer.MAX_VALUE,
+                LoadFaviconTask.FLAG_PERSIST,
+                listener);
+    }
+
     /**
      * This will detect if the key pressed is back. If so, will show the history.
      */
     @Override
     public boolean onKeyLongPress(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
             Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
--- a/mobile/android/base/ReaderModeUtils.java
+++ b/mobile/android/base/ReaderModeUtils.java
@@ -1,17 +1,20 @@
 /* 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.json.JSONException;
+import org.json.JSONObject;
 import org.mozilla.gecko.util.StringUtils;
 
 import android.net.Uri;
+import android.util.Log;
 
 public class ReaderModeUtils {
     private static final String LOGTAG = "ReaderModeUtils";
 
     public static String getUrlFromAboutReader(String aboutReaderUrl) {
         return StringUtils.getQueryParameter(aboutReaderUrl, "url");
     }
 
@@ -40,9 +43,35 @@ public class ReaderModeUtils {
         String aboutReaderUrl = AboutPages.READER + "?url=" + Uri.encode(url);
 
         if (tabId >= 0) {
             aboutReaderUrl += "&tabId=" + tabId;
         }
 
         return aboutReaderUrl;
     }
+
+    public static void addToReadingList(Tab tab) {
+        if (!tab.getReaderEnabled()) {
+            return;
+        }
+
+        JSONObject json = new JSONObject();
+        try {
+            json.put("tabID", String.valueOf(tab.getId()));
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "JSON error - failing to add to reading list", e);
+            return;
+        }
+
+        GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Add", json.toString());
+        GeckoAppShell.sendEventToGecko(e);
+    }
+
+    public static void removeFromReadingList(String url) {
+        if (url == null) {
+            return;
+        }
+
+        GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", url);
+        GeckoAppShell.sendEventToGecko(e);
+    }
 }
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -3,16 +3,19 @@
  * 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.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.favicons.Favicons;
+import org.mozilla.gecko.favicons.LoadFaviconTask;
+import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -448,32 +451,16 @@ public class Tab {
                 if (url == null)
                     return;
 
                 BrowserDB.removeBookmarksWithURL(getContentResolver(), url);
             }
         });
     }
 
-    public void addToReadingList() {
-        if (!mReaderEnabled)
-            return;
-
-        JSONObject json = new JSONObject();
-        try {
-            json.put("tabID", String.valueOf(getId()));
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "JSON error - failing to add to reading list", e);
-            return;
-        }
-
-        GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Add", json.toString());
-        GeckoAppShell.sendEventToGecko(e);
-    }
-
     public void toggleReaderMode() {
         if (AboutPages.isAboutReader(mUrl)) {
             Tabs.getInstance().loadUrl(ReaderModeUtils.getUrlFromAboutReader(mUrl));
         } else if (mReaderEnabled) {
             mEnteringReaderMode = true;
             Tabs.getInstance().loadUrl(ReaderModeUtils.getAboutReaderForUrl(mUrl, mId));
         }
     }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -49,16 +49,17 @@
 <!ENTITY go "Go">
 <!ENTITY search "Search">
 <!ENTITY reload "Reload">
 <!ENTITY forward "Forward">
 <!ENTITY menu "Menu">
 <!ENTITY back "Back">
 <!ENTITY stop "Stop">
 <!ENTITY site_security "Site Security">
+<!ENTITY save "Save">
 
 <!ENTITY close_tab "Close Tab">
 <!ENTITY one_tab "1 tab">
 <!-- Localization note (num_tabs2) : Number of tabs is always more than one.
      We can't use android plural forms, sadly. See bug #753859. -->
 <!ENTITY num_tabs2 "&formatD; tabs">
 <!ENTITY new_tab_opened "New tab opened">
 
@@ -271,16 +272,17 @@ size. -->
 
 <!-- Localization note (site_settings_*) : These strings are used in the "Site Settings"
      dialog that appears after selecting the "Edit Site Settings" context menu item. -->
 <!ENTITY site_settings_title3       "Site Settings">
 <!ENTITY site_settings_cancel       "Cancel">
 <!ENTITY site_settings_clear        "Clear">
 <!ENTITY site_settings_no_settings  "There are no settings to clear.">
 
+<!ENTITY reading_list_add "Add to Reading List">
 <!ENTITY reading_list_added "Page added to your Reading List">
 <!ENTITY reading_list_removed "Page removed from your Reading List">
 <!ENTITY reading_list_failed "Failed to add page to your Reading List">
 <!ENTITY reading_list_duplicate "Page already in your Reading List">
 
 <!-- Localization note : These strings are used as alternate text for accessibility.
      They are not visible in the UI. -->
 <!ENTITY page_action_dropmarker_description "Additional Actions">
--- a/mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
@@ -15,20 +15,40 @@
           android:title="@string/back"
           android:visible="false"/>
 
     <item android:id="@+id/forward"
           android:icon="@drawable/ic_menu_forward"
           android:title="@string/forward"
           android:visible="false"/>
 
-    <item android:id="@+id/bookmark"
+    <item android:id="@+id/save"
           android:icon="@drawable/ic_menu_bookmark_add"
-          android:title="@string/bookmark"
-          android:showAsAction="ifRoom"/>
+          android:title="@string/save"
+          android:showAsAction="ifRoom">
+
+        <menu>
+
+            <item android:id="@+id/bookmark"
+                  android:title="@string/bookmark"
+                  android:checkable="true" />
+
+            <item android:id="@+id/reading_list_add"
+                  android:title="@string/reading_list_add"
+                  android:checkable="true" />
+
+            <item android:id="@+id/launcher_add"
+                  android:title="@string/contextmenu_add_to_launcher" />
+
+            <item android:id="@+id/subscribe"
+                  android:title="@string/contextmenu_subscribe" />
+
+        </menu>
+
+    </item>
 
     <item android:id="@+id/share"
           android:icon="@drawable/ic_menu_share"
           android:title="@string/share"
           android:showAsAction="ifRoom"/>
 
     <item android:id="@+id/new_tab"
           android:icon="@drawable/ic_menu_new_tab"
--- a/mobile/android/base/resources/menu-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-v11/browser_app_menu.xml
@@ -15,20 +15,40 @@
           android:title="@string/forward"
           android:showAsAction="always"/>
 
     <item android:id="@+id/reload"
           android:icon="@drawable/ic_menu_reload"
           android:title="@string/reload"
           android:showAsAction="always"/>
 
-    <item android:id="@+id/bookmark"
+    <item android:id="@+id/save"
           android:icon="@drawable/ic_menu_bookmark_add"
-          android:title="@string/bookmark"
-          android:showAsAction="ifRoom"/>
+          android:title="@string/save"
+          android:showAsAction="ifRoom">
+
+        <menu>
+
+            <item android:id="@+id/bookmark"
+                  android:title="@string/bookmark"
+                  android:checkable="true" />
+
+            <item android:id="@+id/reading_list_add"
+                  android:title="@string/reading_list_add"
+                  android:checkable="true" />
+
+            <item android:id="@+id/launcher_add"
+                  android:title="@string/contextmenu_add_to_launcher" />
+
+            <item android:id="@+id/subscribe"
+                  android:title="@string/contextmenu_subscribe" />
+
+        </menu>
+
+    </item>
 
     <item android:id="@+id/share"
           android:icon="@drawable/ic_menu_share"
           android:title="@string/share"
           android:showAsAction="ifRoom"/>
 
     <item android:id="@+id/new_tab"
           android:icon="@drawable/ic_menu_new_tab"
--- a/mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
@@ -15,20 +15,40 @@
           android:title="@string/back"
           android:visible="false"/>
 
     <item android:id="@+id/forward"
           android:icon="@drawable/ic_menu_forward"
           android:title="@string/forward"
           android:visible="false"/>
 
-    <item android:id="@+id/bookmark"
+    <item android:id="@+id/save"
           android:icon="@drawable/ic_menu_bookmark_add"
-          android:title="@string/bookmark"
-          android:showAsAction="always"/>
+          android:title="@string/save"
+          android:showAsAction="always">
+
+        <menu>
+
+            <item android:id="@+id/bookmark"
+                  android:title="@string/bookmark"
+                  android:checkable="true" />
+
+            <item android:id="@+id/reading_list_add"
+                  android:title="@string/reading_list_add"
+                  android:checkable="true" />
+
+            <item android:id="@+id/launcher_add"
+                  android:title="@string/contextmenu_add_to_launcher" />
+
+            <item android:id="@+id/subscribe"
+                  android:title="@string/contextmenu_subscribe" />
+
+        </menu>
+
+    </item>
 
     <item android:id="@+id/share"
           android:icon="@drawable/ic_menu_share"
           android:title="@string/share"
           android:showAsAction="ifRoom"/>
 
     <item android:id="@+id/new_tab"
           android:icon="@drawable/ic_menu_new_tab"
--- a/mobile/android/base/resources/menu/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu/browser_app_menu.xml
@@ -13,19 +13,39 @@
           android:icon="@drawable/ic_menu_back"
           android:title="@string/back"
           android:visible="false"/>
 
     <item android:id="@+id/forward"
           android:icon="@drawable/ic_menu_forward"
           android:title="@string/forward"/>
 
-    <item android:id="@+id/bookmark"
+    <item android:id="@+id/save"
           android:icon="@drawable/ic_menu_bookmark_add"
-          android:title="@string/bookmark"/>
+          android:title="@string/save">
+
+        <menu>
+
+            <item android:id="@+id/bookmark"
+                  android:title="@string/bookmark"
+                  android:checkable="true" />
+
+            <item android:id="@+id/reading_list_add"
+                  android:title="@string/reading_list_add"
+                  android:checkable="true" />
+
+            <item android:id="@+id/launcher_add"
+                  android:title="@string/contextmenu_add_to_launcher" />
+
+            <item android:id="@+id/subscribe"
+                  android:title="@string/contextmenu_subscribe" />
+
+        </menu>
+
+    </item>
 
     <item android:id="@+id/new_tab"
           android:icon="@drawable/ic_menu_new_tab"
           android:title="@string/new_tab"/>
 
     <item android:id="@+id/new_private_tab"
           android:icon="@drawable/ic_menu_new_private_tab"
           android:title="@string/new_private_tab"/>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -218,22 +218,24 @@
   <string name="apps">&apps;</string>
   <string name="char_encoding">&char_encoding;</string>
   <string name="new_tab">&new_tab;</string>
   <string name="new_private_tab">&new_private_tab;</string>
   <string name="close_all_tabs">&close_all_tabs;</string>
   <string name="tabs_normal">&tabs_normal;</string>
   <string name="tabs_private">&tabs_private;</string>
   <string name="tabs_synced">&tabs_synced;</string>
+  <string name="save">&save;</string>
 
   <string name="site_settings_title">&site_settings_title3;</string>
   <string name="site_settings_cancel">&site_settings_cancel;</string>
   <string name="site_settings_clear">&site_settings_clear;</string>
   <string name="site_settings_no_settings">&site_settings_no_settings;</string>
 
+  <string name="reading_list_add">&reading_list_add;</string>
   <string name="reading_list_added">&reading_list_added;</string>
   <string name="reading_list_removed">&reading_list_removed;</string>
   <string name="reading_list_failed">&reading_list_failed;</string>
   <string name="reading_list_duplicate">&reading_list_duplicate;</string>
 
   <string name="page_action_dropmarker_description">&page_action_dropmarker_description;</string>
 
   <string name="contextmenu_open_new_tab">&contextmenu_open_new_tab;</string>
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -6,16 +6,17 @@
 package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.LightweightTheme;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.ReaderModeUtils;
 import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.MenuPopup;
@@ -25,17 +26,16 @@ import org.mozilla.gecko.toolbar.Toolbar
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.widget.GeckoImageButton;
 import org.mozilla.gecko.widget.GeckoImageView;
 import org.mozilla.gecko.widget.GeckoRelativeLayout;
-
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
 import android.os.Build;
@@ -1340,20 +1340,17 @@ public class BrowserToolbar extends Geck
     public void handleMessage(String event, JSONObject message) {
         Log.d(LOGTAG, "handleMessage: " + event);
         if (event.equals("Reader:Click")) {
             Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
                 tab.toggleReaderMode();
             }
         } else if (event.equals("Reader:LongClick")) {
-            Tab tab = Tabs.getInstance().getSelectedTab();
-            if (tab != null) {
-                tab.addToReadingList();
-            }
+            ReaderModeUtils.addToReadingList(Tabs.getInstance().getSelectedTab());
         }
     }
 
     @Override
     public void onLightweightThemeChanged() {
         Drawable drawable = mTheme.getDrawable(this);
         if (drawable == null)
             return;