Bug 783312 - Provide a context menu to edit/pin sites. r=mfinkle
authorWes Johnston <wjohnston@mozilla.com>
Fri, 28 Dec 2012 13:46:10 -0800
changeset 126293 a9c2f983a4ead793572db5d359199bb5d184a4e4
parent 126292 c2c914a1870b088c1a956cfabde9755e3e2de59d
child 126294 96daad5f3d983bb26e217d35f96720245ebccd77
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs783312
milestone20.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 783312 - Provide a context menu to edit/pin sites. r=mfinkle
mobile/android/base/AboutHomeContent.java
mobile/android/base/BrowserApp.java
mobile/android/base/Makefile.in
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/resources/menu/abouthome_topsites_contextmenu.xml
mobile/android/base/strings.xml.in
--- a/mobile/android/base/AboutHomeContent.java
+++ b/mobile/android/base/AboutHomeContent.java
@@ -6,16 +6,17 @@
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
 import org.mozilla.gecko.db.BrowserDB.PinnedSite;
+import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.GeckoAsyncTask;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -40,17 +41,20 @@ import android.graphics.PorterDuffXfermo
 import android.graphics.RectF;
 import android.os.SystemClock;
 import android.text.SpannableString;
 import android.text.TextUtils;
 import android.text.style.StyleSpan;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.ContextMenu;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
+import android.view.MenuInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.AbsListView;
 import android.widget.GridView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -171,16 +175,39 @@ public class AboutHomeContent extends Sc
                     return;
                 }
 
                 if (mUriLoadCallback != null)
                     mUriLoadCallback.callback(spec);
             }
         });
 
+        mTopSitesGrid.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
+            public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+                AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
+                mTopSitesGrid.setSelectedPosition(info.position);
+
+                MenuInflater inflater = mActivity.getMenuInflater();
+                inflater.inflate(R.menu.abouthome_topsites_contextmenu, menu);
+
+                // If nothing is pinned at all, hide both clear items
+                TopSitesCursorWrapper cursor = (TopSitesCursorWrapper)mTopSitesAdapter.getCursor();
+                if (!cursor.hasPinnedSites()) {
+                    menu.findItem(R.id.abouthome_topsites_clearall).setVisible(false);
+                    menu.findItem(R.id.abouthome_topsites_clear).setVisible(false);
+                } else {
+                    // If there's nothing pinned here, hide the clear item
+                    PinnedSite site = cursor.getPinnedSite(info.position);
+                    if (site == null) {
+                        menu.findItem(R.id.abouthome_topsites_clear).setVisible(false);
+                    }
+                }
+            }
+        });
+
         mPromoBox = (AboutHomePromoBox) findViewById(R.id.promo_box);
         mAddons = (AboutHomeSection) findViewById(R.id.recommended_addons);
         mLastTabs = (AboutHomeSection) findViewById(R.id.last_tabs);
         mRemoteTabs = (AboutHomeSection) findViewById(R.id.remote_tabs);
 
         mAddons.setOnMoreTextClickListener(new View.OnClickListener() {
             public void onClick(View v) {
                 if (mUriLoadCallback != null)
@@ -735,16 +762,18 @@ public class AboutHomeContent extends Sc
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         onLightweightThemeChanged();
     }
 
     public static class TopSitesGridView extends GridView {
+        int mSelected = -1;
+
         public TopSitesGridView(Context context, AttributeSet attrs) {
             super(context, attrs);
         }
 
         public int getColumnWidth() {
             return getColumnWidth(getWidth());
         }
 
@@ -779,16 +808,24 @@ public class AboutHomeContent extends Sc
             // Just using getWidth() will use incorrect values during onMeasure when rotating the device
             // Instead we pass in the measuredWidth, which is correct
             int w = getColumnWidth(measuredWidth);
             ThumbnailHelper.getInstance().setThumbnailWidth(w);
             heightMeasureSpec = MeasureSpec.makeMeasureSpec((int)(w*ThumbnailHelper.THUMBNAIL_ASPECT_RATIO*numRows) + getPaddingTop() + getPaddingBottom(),
                                                                  MeasureSpec.EXACTLY);
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         }
+
+        public void setSelectedPosition(int position) {
+            mSelected = position;
+        }
+
+        public int getSelectedPosition() {
+            return mSelected;
+        }
     }
 
     private class TopSitesViewHolder {
         public TextView titleView = null;
         public ImageView thumbnailView = null;
         public String url = null;
     }
 
@@ -844,16 +881,78 @@ public class AboutHomeContent extends Sc
             if (!c.isAfterLast()) {
                 url = c.getString(c.getColumnIndex(URLColumns.URL));
                 title = c.getString(c.getColumnIndex(URLColumns.TITLE));
             }
             return buildView(url, title, convertView);
         }
     }
 
+    private void clearThumbnail(TopSitesViewHolder holder) {
+        holder.titleView.setText("");
+        holder.url = "";
+        holder.thumbnailView.setImageResource(R.drawable.abouthome_thumbnail_bg);
+        holder.thumbnailView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+    }
+
+    public void clearAllSites() {
+        final ContentResolver resolver = mActivity.getContentResolver();
+
+        // Clear the view quickly to make things appear responsive
+        for (int i = 0; i < mTopSitesGrid.getChildCount(); i++) {
+            View v = mTopSitesGrid.getChildAt(i);
+            TopSitesViewHolder holder = (TopSitesViewHolder) v.getTag();
+            clearThumbnail(holder);
+        }
+
+        (new GeckoAsyncTask<Void, Void, Void>(GeckoApp.mAppContext, GeckoAppShell.getHandler()) {
+            @Override
+            public Void doInBackground(Void... params) {
+                ContentResolver resolver = mActivity.getContentResolver();
+                BrowserDB.unpinAllSites(resolver);
+                return null;
+            }
+
+            @Override
+            public void onPostExecute(Void v) {
+                update(EnumSet.of(UpdateFlags.TOP_SITES));
+            }
+        }).execute();
+    }
+
+    public void clearSite() {
+        final int position = mTopSitesGrid.getSelectedPosition();
+        View v = mTopSitesGrid.getChildAt(position);
+        TopSitesViewHolder holder = (TopSitesViewHolder) v.getTag();
+
+        // Quickly update the view so that there isn't as much lag between the request and response
+        clearThumbnail(holder);
+        (new GeckoAsyncTask<Void, Void, Void>(GeckoApp.mAppContext, GeckoAppShell.getHandler()) {
+            @Override
+            public Void doInBackground(Void... params) {
+                ContentResolver resolver = mActivity.getContentResolver();
+                BrowserDB.unpinSite(resolver, position);
+                return null;
+            }
+
+            @Override
+            public void onPostExecute(Void v) {
+                update(EnumSet.of(UpdateFlags.TOP_SITES));
+            }
+        }).execute();
+    }
+
+    public void editSite() {
+        int position = mTopSitesGrid.getSelectedPosition();
+       View v = mTopSitesGrid.getChildAt(position);
+
+        TopSitesViewHolder holder = (TopSitesViewHolder) v.getTag();
+        editSite(holder.url, position);
+    }
+
     // Edit the site at position. Provide a url to start editing with
     public void editSite(String url, final int position) {
         Intent intent = new Intent(mContext, AwesomeBar.class);
         intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
         intent.putExtra(AwesomeBar.TARGET_KEY, AwesomeBar.Target.PICK_SITE.toString());
         if (url != null && !TextUtils.isEmpty(url)) {
             intent.putExtra(AwesomeBar.CURRENT_URL_KEY, url);
         }
@@ -863,16 +962,17 @@ public class AboutHomeContent extends Sc
                 final String title = data.getStringExtra(AwesomeBar.TITLE_KEY);
                 final String url = data.getStringExtra(AwesomeBar.URL_KEY);
 
                 // update the database on a background thread
                 (new GeckoAsyncTask<Void, Void, Void>(GeckoApp.mAppContext, GeckoAppShell.getHandler()) {
                     @Override
                     public Void doInBackground(Void... params) {
                         final ContentResolver resolver = mActivity.getContentResolver();
+                        Log.i(LOGTAG, "Pin : " + url + " and " + title);
                         BrowserDB.pinSite(resolver, url, (title == null ? url : title), position);
                         return null;
                     }
         
                     @Override
                     public void onPostExecute(Void v) {
                         update(EnumSet.of(UpdateFlags.TOP_SITES));
                     }
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1023,16 +1023,34 @@ abstract public class BrowserApp extends
         findInPage.setEnabled(!tab.getURL().equals("about:home"));
 
         charEncoding.setVisible(GeckoPreferences.getCharEncodingState());
 
         return true;
     }
 
     @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.abouthome_topsites_edit:
+                mAboutHomeContent.editSite();
+                return true;
+
+            case R.id.abouthome_topsites_clear:
+                mAboutHomeContent.clearSite();
+                return true;
+
+            case R.id.abouthome_topsites_clearall:
+                mAboutHomeContent.clearAllSites();
+                return true;
+        }
+        return false;
+    }
+
+    @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         Tab tab = null;
         Intent intent = null;
         switch (item.getItemId()) {
             case R.id.bookmark:
                 tab = Tabs.getInstance().getSelectedTab();
                 if (tab != null) {
                     if (item.isChecked()) {
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -1009,16 +1009,17 @@ RES_COLOR = \
   $(NULL)
 
 RES_MENU = \
   res/menu/awesomebar_contextmenu.xml \
   res/menu/gecko_app_menu.xml \
   res/menu/tabs_menu.xml \
   res/menu/tabs_switcher_menu.xml \
   res/menu/titlebar_contextmenu.xml \
+  res/menu/abouthome_topsites_contextmenu.xml \
   res/menu-v11/tabs_menu.xml \
   $(NULL)
 
 JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
 
 ifdef MOZ_CRASHREPORTER
 FENNEC_PP_JAVA_FILES += CrashReporter.java
 MOZ_ANDROID_DRAWABLES += mobile/android/base/resources/drawable/crash_reporter.png
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -196,16 +196,19 @@ size. -->
 <!ENTITY button_set "Set">
 <!ENTITY button_clear "Clear">
 
 <!ENTITY abouthome_addons_title "Add-ons for your &brandShortName;">
 <!ENTITY abouthome_addons_browse "Browse all &brandShortName; add-ons">
 <!ENTITY abouthome_last_tabs_title "Your tabs from last time">
 <!ENTITY abouthome_last_tabs_open "Open all tabs from last time">
 <!ENTITY abouthome_top_sites_title "Top sites">
+<!ENTITY abouthome_topsites_edit "Edit">
+<!ENTITY abouthome_topsites_clear "Clear">
+<!ENTITY abouthome_topsites_clearall "Clear All">
 <!-- Localization note (abouthome_about_sync3, abouthome_about_apps2): The
      chevron (ex: "»"; unicode= U+00BB) is used as an arrow to show that
      clicking this text in the promotions box will perform some action. Note
      that a non-breaking space (unicode= U+00A0) should be used between this
      character and the remainder of the string to prevent word wrap. -->
 <!ENTITY abouthome_about_sync3 "Set up Firefox Sync to access bookmarks, history and tabs from your other devices&#x00A0;»">
 <!ENTITY abouthome_about_apps3 "Get apps from the Firefox Marketplace and discover the best the Web has to offer&#x00A0;»">
 <!-- Localization note (abouthome_sync_bold_name, abouthome_apps_bold_name):
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/menu/abouthome_topsites_contextmenu.xml
@@ -0,0 +1,17 @@
+<?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/abouthome_topsites_edit"
+          android:title="@string/abouthome_topsites_edit"/>
+
+    <item android:id="@+id/abouthome_topsites_clear"
+          android:title="@string/abouthome_topsites_clear"/>
+
+    <item android:id="@+id/abouthome_topsites_clearall"
+          android:title="@string/abouthome_topsites_clearall"/>
+
+</menu>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -197,16 +197,20 @@
   <string name="abouthome_addons_browse">&abouthome_addons_browse;</string>
   <string name="abouthome_last_tabs_title">&abouthome_last_tabs_title;</string>
   <string name="abouthome_last_tabs_open">&abouthome_last_tabs_open;</string>
   <string name="abouthome_top_sites_title">&abouthome_top_sites_title;</string>
   <string name="abouthome_about_sync">&abouthome_about_sync3;</string>
   <string name="abouthome_about_apps">&abouthome_about_apps3;</string>
   <string name="abouthome_sync_bold_name">&abouthome_sync_bold_name;</string>
   <string name="abouthome_apps_bold_name">&abouthome_apps_bold_name2;</string>
+  
+  <string name="abouthome_topsites_edit">&abouthome_topsites_edit;</string>
+  <string name="abouthome_topsites_clear">&abouthome_topsites_clear;</string>
+  <string name="abouthome_topsites_clearall">&abouthome_topsites_clearall;</string>
 
   <string name="filepicker_title">&filepicker_title;</string>
   <string name="filepicker_audio_title">&filepicker_audio_title;</string>
   <string name="filepicker_image_title">&filepicker_image_title;</string>
   <string name="filepicker_video_title">&filepicker_video_title;</string>
 
   <!-- Default bookmarks. Use bookmarks titles shared with XUL from mobile's
        profile/bookmarks.inc. Don't expose the URLs to L10N. -->