Bug 885882: Add back context menu support for TopBookmarksView. [r=margaret]
authorSriram Ramasubramanian <sriram@mozilla.com>
Mon, 22 Jul 2013 13:08:38 -0700
changeset 143427 f74010b736a40ff826007fd5aa38264f328396a0
parent 143426 9aca0034429f25379e83639cfb5a246173330468
child 143428 475acbe5775e35f02e30cac831cbe65d4315523c
push id25130
push userlrocha@mozilla.com
push dateWed, 21 Aug 2013 09:41:27 +0000
treeherdermozilla-central@b2486721572e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret
bugs885882
milestone25.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 885882: Add back context menu support for TopBookmarksView. [r=margaret]
mobile/android/base/Makefile.in
mobile/android/base/home/BookmarksPage.java
mobile/android/base/home/HomeFragment.java
mobile/android/base/home/HomeListView.java
mobile/android/base/home/TopBookmarksView.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/resources/menu/top_bookmarks_contextmenu.xml
mobile/android/base/strings.xml.in
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -1013,16 +1013,17 @@ RES_COLOR = \
   res/color/url_bar_title_hint.xml \
   $(NULL)
 
 RES_MENU = \
   res/menu/browser_app_menu.xml \
   res/menu/gecko_app_menu.xml \
   res/menu/home_contextmenu.xml \
   res/menu/titlebar_contextmenu.xml \
+  res/menu/top_bookmarks_contextmenu.xml \
   res/menu-large-v11/browser_app_menu.xml \
   res/menu-v11/browser_app_menu.xml \
   res/menu-xlarge-v11/browser_app_menu.xml \
   $(NULL)
 
 JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
 
 ifdef MOZ_CRASHREPORTER
--- a/mobile/android/base/home/BookmarksPage.java
+++ b/mobile/android/base/home/BookmarksPage.java
@@ -2,43 +2,54 @@
  * 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.home;
 
 import org.mozilla.gecko.Favicons;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
+import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.PinBookmarkDialog.OnBookmarkSelectedListener;
 import org.mozilla.gecko.home.TopBookmarksView.OnPinBookmarkListener;
 import org.mozilla.gecko.home.TopBookmarksView.Thumbnail;
+import org.mozilla.gecko.home.TopBookmarksView.TopBookmarksContextMenuInfo;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.AsyncTaskLoader;
 import android.support.v4.content.Loader;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Toast;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 
 /**
  * A page in about:home that displays a ListView of bookmarks.
  */
@@ -106,20 +117,21 @@ public class BookmarksPage extends HomeF
                     + " must implement HomePager.OnUrlOpenListener");
         }
 
         mPinBookmarkListener = new PinBookmarkListener();
 
         mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
         mList.setOnUrlOpenListener(listener);
 
-        registerForContextMenu(mList);
-
         mTopBookmarks.setOnUrlOpenListener(listener);
         mTopBookmarks.setOnPinBookmarkListener(mPinBookmarkListener);
+
+        registerForContextMenu(mList);
+        registerForContextMenu(mTopBookmarks);
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
         // Setup the top bookmarks adapter.
         mTopBookmarksAdapter = new TopBookmarksAdapter(getActivity(), null);
@@ -173,16 +185,128 @@ public class BookmarksPage extends HomeF
         if (isVisible()) {
             getFragmentManager().beginTransaction()
                                 .detach(this)
                                 .attach(this)
                                 .commitAllowingStateLoss();
         }
     }
 
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        if (menuInfo == null) {
+            return;
+        }
+
+        // HomeFragment will handle the default case.
+        if (menuInfo instanceof HomeContextMenuInfo) {
+            super.onCreateContextMenu(menu, view, menuInfo);
+        }
+
+        if (!(menuInfo instanceof TopBookmarksContextMenuInfo)) {
+            return;
+        }
+
+        MenuInflater inflater = new MenuInflater(view.getContext());
+        inflater.inflate(R.menu.top_bookmarks_contextmenu, menu);
+
+        TopBookmarksContextMenuInfo info = (TopBookmarksContextMenuInfo) menuInfo;
+
+        if (!TextUtils.isEmpty(info.url)) {
+            // Show Open Private Tab if we're in private mode, Open New Tab otherwise
+            boolean isPrivate = false;
+            Tab tab = Tabs.getInstance().getSelectedTab();
+            if (tab != null) {
+                isPrivate = tab.isPrivate();
+            }
+
+            menu.findItem(R.id.open_new_tab).setVisible(!isPrivate);
+            menu.findItem(R.id.open_private_tab).setVisible(isPrivate);
+
+            if (info.isPinned) {
+                menu.findItem(R.id.top_bookmarks_pin).setVisible(false);
+            } else {
+                menu.findItem(R.id.top_bookmarks_unpin).setVisible(false);
+            }
+        } else {
+            menu.findItem(R.id.open_new_tab).setVisible(false);
+            menu.findItem(R.id.open_private_tab).setVisible(false);
+            menu.findItem(R.id.top_bookmarks_pin).setVisible(false);
+            menu.findItem(R.id.top_bookmarks_unpin).setVisible(false);
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        ContextMenuInfo menuInfo = item.getMenuInfo();
+
+        // HomeFragment will handle the default case.
+        if (menuInfo == null || !(menuInfo instanceof TopBookmarksContextMenuInfo)) {
+            return false;
+        }
+
+        TopBookmarksContextMenuInfo info = (TopBookmarksContextMenuInfo) menuInfo;
+        final Activity activity = getActivity();
+
+        switch(item.getItemId()) {
+            case R.id.open_new_tab:
+            case R.id.open_private_tab: {
+                if (info.url == null) {
+                    Log.e(LOGTAG, "Can't open in new tab because URL is null");
+                    break;
+                }
+
+                int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
+                if (item.getItemId() == R.id.open_private_tab)
+                    flags |= Tabs.LOADURL_PRIVATE;
+
+                Tabs.getInstance().loadUrl(info.url, flags);
+                Toast.makeText(activity, R.string.new_tab_opened, Toast.LENGTH_SHORT).show();
+                return true;
+            }
+
+            case R.id.top_bookmarks_pin: {
+                final String url = info.url;
+                final String title = info.title;
+                final int position = info.position;
+                final Context context = getActivity().getApplicationContext();
+
+                ThreadUtils.postToBackgroundThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        BrowserDB.pinSite(context.getContentResolver(), url, title, position);
+                    }
+                });
+
+                return true;
+            }
+
+            case R.id.top_bookmarks_unpin: {
+                final int position = info.position;
+                final Context context = getActivity().getApplicationContext();
+
+                ThreadUtils.postToBackgroundThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        BrowserDB.unpinSite(context.getContentResolver(), position);
+                    }
+                });
+
+                return true;
+            }
+
+            case R.id.top_bookmarks_edit: {
+                mPinBookmarkListener.onPinBookmark(info.position);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     /**
      * Listener for pinning bookmarks.
      */
     private class PinBookmarkListener implements OnPinBookmarkListener,
                                                  OnBookmarkSelectedListener {
         // Tag for the PinBookmarkDialog fragment.
         private static final String TAG_PIN_BOOKMARK = "pin_bookmark";
 
--- a/mobile/android/base/home/HomeFragment.java
+++ b/mobile/android/base/home/HomeFragment.java
@@ -1,18 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.EditBookmarkDialog;
+import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
-import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
@@ -47,17 +47,17 @@ class HomeFragment extends Fragment {
     protected void showSubPage(Fragment subPage) {
         getActivity().getSupportFragmentManager().beginTransaction()
                 .addToBackStack(null).replace(R.id.home_pager_container, subPage, HomePager.SUBPAGE_TAG)
                 .commitAllowingStateLoss();
     }
 
     @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
-        if (!(menuInfo instanceof HomeContextMenuInfo)) {
+        if (menuInfo == null || !(menuInfo instanceof HomeContextMenuInfo)) {
             return;
         }
 
         HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
 
         // Don't show the context menu for folders.
         if (info.isFolder) {
             return;
@@ -82,25 +82,23 @@ class HomeFragment extends Fragment {
         menu.setHeaderTitle(info.title);
 
         menu.findItem(R.id.remove_history).setVisible(false);
         menu.findItem(R.id.open_in_reader).setVisible(false);
     }
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
-        final Activity activity = getActivity();
-        HomeContextMenuInfo info = null;
+        ContextMenuInfo menuInfo = item.getMenuInfo();
+        if (menuInfo == null || !(menuInfo instanceof HomeContextMenuInfo)) {
+            return false;
+        }
 
-        try {
-            ContextMenuInfo menuInfo = item.getMenuInfo();
-            info = (HomeContextMenuInfo) menuInfo;
-        } catch(ClassCastException e) {
-            throw new IllegalArgumentException("ContextMenuInfo is not of type HomeContextMenuInfo");
-        }
+        HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
+        final Activity activity = getActivity();
 
         switch(item.getItemId()) {
             case R.id.share: {
                 if (info.url == null) {
                     Log.e(LOGTAG, "Can't share because URL is null");
                     break;
                 }
 
--- a/mobile/android/base/home/HomeListView.java
+++ b/mobile/android/base/home/HomeListView.java
@@ -12,17 +12,16 @@ import org.mozilla.gecko.db.BrowserDB.UR
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.util.AttributeSet;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.MotionEvent;
 import android.view.View;
-
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemLongClickListener;
 import android.widget.ListView;
 
 /**
  * HomeListView is a custom extension of ListView, that packs a HomeContextMenuInfo
  * when any of its rows is long pressed.
  */
@@ -63,19 +62,27 @@ public class HomeListView extends ListVi
             requestFocus();
         }
 
         return false;
     }
 
     @Override
     public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
-        Cursor cursor = (Cursor) parent.getItemAtPosition(position);
-        mContextMenuInfo = new HomeContextMenuInfo(view, position, id, cursor);
-        return showContextMenuForChild(HomeListView.this);
+        Object item = parent.getItemAtPosition(position);
+
+        // HomeListView could hold headers too. Add a context menu info only for its children.
+        if (item instanceof Cursor) {
+            Cursor cursor = (Cursor) item;
+            mContextMenuInfo = new HomeContextMenuInfo(view, position, id, cursor);
+            return showContextMenuForChild(HomeListView.this);
+        } else {
+            mContextMenuInfo = null;
+            return false;
+        }
     }
 
     @Override
     public ContextMenuInfo getContextMenuInfo() {
         return mContextMenuInfo;
     }
 
     public OnUrlOpenListener getOnUrlOpenListener() {
--- a/mobile/android/base/home/TopBookmarksView.java
+++ b/mobile/android/base/home/TopBookmarksView.java
@@ -2,22 +2,26 @@
  * 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.home;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.ThumbnailHelper;
+import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
+import org.mozilla.gecko.db.BrowserDB.URLColumns;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 
 import android.content.Context;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.View;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.GridView;
 
 import java.util.Map;
 
 /**
@@ -39,16 +43,19 @@ public class TopBookmarksView extends Gr
     private final int mNumColumns;
 
     // On URL open listener.
     private OnUrlOpenListener mUrlOpenListener;
 
     // Pin bookmark listener.
     private OnPinBookmarkListener mPinBookmarkListener;
 
+    // Context menu info.
+    private TopBookmarksContextMenuInfo mContextMenuInfo;
+
     // Temporary cache to store the thumbnails until the next layout pass.
     private Map<String, Thumbnail> mThumbnailsCache;
 
     /**
      *  Class to hold the bitmap of cached thumbnails/favicons.
      */
     public static class Thumbnail {
         // Thumbnail or favicon.
@@ -99,16 +106,25 @@ public class TopBookmarksView extends Gr
                     }
                 } else {
                     if (mPinBookmarkListener != null) {
                         mPinBookmarkListener.onPinBookmark(position);
                     }
                 }
             }
         });
+
+        setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+            @Override
+            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+                Cursor cursor = (Cursor) parent.getItemAtPosition(position);
+                mContextMenuInfo = new TopBookmarksContextMenuInfo(view, position, id, cursor);
+                return showContextMenuForChild(TopBookmarksView.this);
+            }
+        });
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
         mUrlOpenListener = null;
         mPinBookmarkListener = null;
@@ -186,16 +202,21 @@ public class TopBookmarksView extends Gr
 
         // If there are thumbnails in the cache, update them.
         if (mThumbnailsCache != null) {
             updateThumbnails(mThumbnailsCache);
             mThumbnailsCache = null;
         }
     }
 
+    @Override
+    public ContextMenuInfo getContextMenuInfo() {
+        return mContextMenuInfo;
+    }
+
     /**
      * Set an url open listener to be used by this view.
      *
      * @param listener An url open listener for this view.
      */
     public void setOnUrlOpenListener(OnUrlOpenListener listener) {
         mUrlOpenListener = listener;
     }
@@ -250,9 +271,30 @@ public class TopBookmarksView extends Gr
                 } else if (thumbnail.isThumbnail) {
                     view.displayThumbnail(thumbnail.bitmap);
                 } else {
                     view.displayFavicon(thumbnail.bitmap);
                 }
             }
         }
     }
+
+    /**
+     * A ContextMenuInfo for TopBoomarksView that adds details from the cursor.
+     */
+    public static class TopBookmarksContextMenuInfo extends AdapterContextMenuInfo {
+        public String url;
+        public String title;
+        public boolean isPinned;
+
+        public TopBookmarksContextMenuInfo(View targetView, int position, long id, Cursor cursor) {
+            super(targetView, position, id);
+
+            if (cursor == null) {
+                return;
+            }
+
+            url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
+            title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
+            isPinned = ((TopSitesCursorWrapper) cursor).isPinned();
+        }
+    }
 }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -218,16 +218,19 @@ size. -->
 <!ENTITY contextmenu_add_to_launcher "Add to Home Screen">
 <!ENTITY contextmenu_share "Share">
 <!ENTITY contextmenu_pasteandgo "Paste &amp; Go">
 <!ENTITY contextmenu_paste "Paste">
 <!ENTITY contextmenu_copyurl "Copy Address">
 <!ENTITY contextmenu_edit_bookmark "Edit">
 <!ENTITY contextmenu_subscribe "Subscribe to Page">
 <!ENTITY contextmenu_site_settings "Edit Site Settings">
+<!ENTITY contextmenu_top_bookmarks_edit "Edit">
+<!ENTITY contextmenu_top_bookmarks_pin "Pin Site">
+<!ENTITY contextmenu_top_bookmarks_unpin "Unpin Site">
 
 <!ENTITY pref_titlebar_mode "Title bar">
 <!ENTITY pref_titlebar_mode_title "Show page title">
 <!ENTITY pref_titlebar_mode_url "Show page address">
 
 <!ENTITY history_removed "Page removed">
 
 <!ENTITY bookmark_add "Add a bookmark">
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/menu/top_bookmarks_contextmenu.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@+id/open_new_tab"
+          android:title="@string/contextmenu_open_new_tab"/>
+
+    <item android:id="@+id/open_private_tab"
+          android:title="@string/contextmenu_open_private_tab"/>
+
+    <item android:id="@+id/top_bookmarks_edit"
+          android:title="@string/contextmenu_top_bookmarks_edit"/>
+
+    <item android:id="@+id/top_bookmarks_pin"
+          android:title="@string/contextmenu_top_bookmarks_pin"/>
+
+    <item android:id="@+id/top_bookmarks_unpin"
+          android:title="@string/contextmenu_top_bookmarks_unpin"/>
+
+</menu>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -202,16 +202,19 @@
   <string name="contextmenu_add_to_launcher">&contextmenu_add_to_launcher;</string>
   <string name="contextmenu_share">&contextmenu_share;</string>
   <string name="contextmenu_pasteandgo">&contextmenu_pasteandgo;</string>
   <string name="contextmenu_paste">&contextmenu_paste;</string>
   <string name="contextmenu_copyurl">&contextmenu_copyurl;</string>
   <string name="contextmenu_edit_bookmark">&contextmenu_edit_bookmark;</string>
   <string name="contextmenu_subscribe">&contextmenu_subscribe;</string>
   <string name="contextmenu_site_settings">&contextmenu_site_settings;</string>
+  <string name="contextmenu_top_bookmarks_edit">&contextmenu_top_bookmarks_edit;</string>
+  <string name="contextmenu_top_bookmarks_pin">&contextmenu_top_bookmarks_pin;</string>
+  <string name="contextmenu_top_bookmarks_unpin">&contextmenu_top_bookmarks_unpin;</string>
 
   <string name="pref_titlebar_mode">&pref_titlebar_mode;</string>
   <string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
   <string name="pref_titlebar_mode_url">&pref_titlebar_mode_url;</string>
 
   <string name="history_removed">&history_removed;</string>
 
   <string name="bookmark_add">&bookmark_add;</string>