Bug 878136: Move ContextMenu functionality from AwesomeBar to BrowserApp. [r=lucasr]
authorSriram Ramasubramanian <sriram@mozilla.com>
Fri, 31 May 2013 14:36:39 -0700
changeset 143394 478e2819dbf732bd594e3fb8c19e2a14234a3c6d
parent 143393 ea79867c3ffda6170afe96fb8a8bdd2085548a84
child 143395 95d4652040a644772a431aa4d24a68efd7fc6ff7
push id32723
push useremorley@mozilla.com
push dateWed, 21 Aug 2013 12:10:14 +0000
treeherdermozilla-inbound@ab6bc4d9e4c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslucasr
bugs878136
milestone24.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 878136: Move ContextMenu functionality from AwesomeBar to BrowserApp. [r=lucasr]
mobile/android/base/GeckoAppShell.java
mobile/android/base/Makefile.in
mobile/android/base/home/BookmarksPage.java
mobile/android/base/home/HomeFragment.java
mobile/android/base/home/HomeListView.java
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -674,18 +674,18 @@ public class GeckoAppShell
     static void createShortcut(String aTitle, String aURI, String aIconData, String aType) {
         if ("webapp".equals(aType)) {
             Log.w(LOGTAG, "createShortcut with no unique URI should not be used for aType = webapp!");
         }
 
         createShortcut(aTitle, aURI, aURI, aIconData, aType);
     }
 
-    // internal, for non-webapps
-    static void createShortcut(String aTitle, String aURI, Bitmap aBitmap, String aType) {
+    // for non-webapps
+    public static void createShortcut(String aTitle, String aURI, Bitmap aBitmap, String aType) {
         createShortcut(aTitle, aURI, aURI, aBitmap, aType);
     }
 
     // internal, for webapps
     static void createShortcut(String aTitle, String aURI, String aUniqueURI, String aIconData, String aType) {
         createShortcut(aTitle, aURI, aUniqueURI, BitmapUtils.getBitmapFromDataURI(aIconData), aType);
     }
 
@@ -1053,22 +1053,22 @@ public class GeckoAppShell
      *
      * @param targetURI the string spec of the URI to open.
      * @param mimeType an optional MIME type string.
      * @param action an Android action specifier, such as
      *               <code>Intent.ACTION_SEND</code>.
      * @param title the title to use in <code>ACTION_SEND</code> intents.
      * @return true if the activity started successfully; false otherwise.
      */
-    static boolean openUriExternal(String targetURI,
-                                   String mimeType,
-                                   String packageName,
-                                   String className,
-                                   String action,
-                                   String title) {
+    public static boolean openUriExternal(String targetURI,
+                                          String mimeType,
+                                          String packageName,
+                                          String className,
+                                          String action,
+                                          String title) {
         final Context context = getContext();
         final Intent intent = getOpenURIIntent(context, targetURI,
                                                mimeType, action, title);
 
         if (intent == null) {
             return false;
         }
 
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -217,16 +217,18 @@ FENNEC_JAVA_FILES = \
   gfx/TextureGenerator.java \
   gfx/TextureReaper.java \
   gfx/TileLayer.java \
   gfx/TouchEventHandler.java \
   gfx/ViewTransform.java \
   gfx/VirtualLayer.java \
   home/BookmarksPage.java \
   home/BookmarkFolderView.java \
+  home/HomeFragment.java \
+  home/HomeListView.java \
   home/HomePager.java \
   home/HomePagerTabStrip.java \
   home/FadedTextView.java \
   home/TwoLinePageRow.java \
   menu/GeckoMenu.java \
   menu/GeckoMenuInflater.java \
   menu/GeckoMenuItem.java \
   menu/GeckoSubMenu.java \
--- a/mobile/android/base/home/BookmarksPage.java
+++ b/mobile/android/base/home/BookmarksPage.java
@@ -4,61 +4,57 @@
  * 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.Tab;
 import org.mozilla.gecko.Tabs;
 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.home.HomeListView.HomeContextMenuInfo;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.Cursor;
-import android.graphics.Bitmap;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.util.Log;
 import android.util.Pair;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
 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.ListView;
 import android.widget.SimpleCursorAdapter;
-import android.widget.TextView;
 
 import java.util.LinkedList;
 
 /**
  * A page in about:home that displays a ListView of bookmarks.
  */
-public class BookmarksPage extends Fragment {
+public class BookmarksPage extends HomeFragment {
     public static final String LOGTAG = "GeckoBookmarksPage";
 
     private int mFolderId = Bookmarks.FIXED_ROOT_ID;
     private String mFolderTitle = "";
 
     private BookmarksListAdapter mCursorAdapter = null;
     private BookmarksQueryTask mQueryTask = null;
 
     // The view shown by the fragment.
-    private ListView mList;
+    private HomeListView mList;
 
     // Folder title for the currently shown list of bookmarks.
     private BookmarkFolderView mFolderView;
 
     // On URL open listener
     private OnUrlOpenListener mUrlOpenListener;
 
     public BookmarksPage() {
@@ -103,17 +99,17 @@ public class BookmarksPage extends Fragm
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         super.onCreateView(inflater, container, savedInstanceState);
 
         // All list views are styled to look the same with a global activity theme.
         // If the style of the list changes, inflate it from an XML.
-        mList = new ListView(container.getContext());
+        mList = new HomeListView(container.getContext());
         return mList;
     }
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
 
         // Folder title view, is always in open state.
@@ -235,55 +231,44 @@ public class BookmarksPage extends Fragm
                 // take focus away from awesome bar to hide the keyboard
                 view.requestFocus();
             }
             return false;
         }
 
         @Override
         public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
-            ListView list = (ListView) view;
-            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
-            Object selectedItem = list.getItemAtPosition(info.position);
-            Cursor cursor = (Cursor) selectedItem;
-
-            // Don't show the context menu for folders
-            if (cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER) {
+            if (!(menuInfo instanceof HomeContextMenuInfo)) {
                 return;
             }
 
-            String keyword = null;
-            int keywordCol = cursor.getColumnIndex(URLColumns.KEYWORD);
-            if (keywordCol != -1) {
-                keyword = cursor.getString(keywordCol);
+            HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
+
+            // Don't show the context menu for folders.
+            if (info.isFolder) {
+                return;
             }
 
-            int id = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
-            String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
-            String title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
-            byte[] favicon = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON));
-            int display = Combined.DISPLAY_NORMAL;
-
-            MenuInflater inflater = new MenuInflater(list.getContext());
+            MenuInflater inflater = new MenuInflater(view.getContext());
             inflater.inflate(R.menu.awesomebar_contextmenu, menu);
 
             // 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);
 
             // Hide "Remove" item if there isn't a valid history ID
-            if (id < 0) {
+            if (info.rowId < 0) {
                 menu.findItem(R.id.remove_history).setVisible(false);
             }
-            menu.setHeaderTitle(title);
+            menu.setHeaderTitle(info.title);
  
             menu.findItem(R.id.remove_history).setVisible(false);
             menu.findItem(R.id.open_in_reader).setVisible(false);
             return;
         }
     }
 
     /**
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/HomeFragment.java
@@ -0,0 +1,167 @@
+/* -*- 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.GeckoAppShell;
+import org.mozilla.gecko.R;
+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;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.Toast;
+
+/**
+ * HomeFragment is an empty fragment that can be added to the HomePager.
+ * Subclasses can add their own views. 
+ */
+public class HomeFragment extends Fragment {
+    // Log Tag.
+    private static final String LOGTAG="GeckoHomeFragment";
+
+    // Share MIME type.
+    private static final String SHARE_MIME_TYPE = "text/plain";
+
+    // URL to Title replacement regex.
+    private static final String REGEX_URL_TO_TITLE = "^([a-z]+://)?(www\\.)?";
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        final Activity activity = getActivity();
+        HomeContextMenuInfo info = null;
+
+        try {
+            ContextMenuInfo menuInfo = item.getMenuInfo();
+            info = (HomeContextMenuInfo) menuInfo;
+        } catch(ClassCastException e) {
+            throw new IllegalArgumentException("ContextMenuInfo is not of type HomeContextMenuInfo");
+        }
+
+        switch(item.getItemId()) {
+            case R.id.share: {
+                if (info.url == null) {
+                    Log.e(LOGTAG, "Can't share because URL is null");
+                    break;
+                }
+
+                GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
+                                              Intent.ACTION_SEND, info.title);
+                return true;
+            }
+
+            case R.id.add_to_launcher: {
+                if (info.url == null) {
+                    Log.e(LOGTAG, "Can't add to home screen because URL is null");
+                    break;
+                }
+
+                Bitmap bitmap = null;
+                if (info.favicon != null && info.favicon.length > 0) {
+                    bitmap = BitmapUtils.decodeByteArray(info.favicon);
+                }
+
+                String shortcutTitle = TextUtils.isEmpty(info.title) ? info.url.replaceAll(REGEX_URL_TO_TITLE, "") : info.title;
+                GeckoAppShell.createShortcut(shortcutTitle, info.url, bitmap, "");
+                return true;
+            }
+
+            case R.id.open_private_tab:
+            case R.id.open_new_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;
+                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.edit_bookmark: {
+                AlertDialog.Builder editPrompt = new AlertDialog.Builder(activity);
+                final View editView = LayoutInflater.from(activity).inflate(R.layout.bookmark_edit, null);
+                editPrompt.setTitle(R.string.bookmark_edit_title);
+                editPrompt.setView(editView);
+
+                final EditText nameText = ((EditText) editView.findViewById(R.id.edit_bookmark_name));
+                final EditText locationText = ((EditText) editView.findViewById(R.id.edit_bookmark_location));
+                final EditText keywordText = ((EditText) editView.findViewById(R.id.edit_bookmark_keyword));
+                nameText.setText(info.title);
+                locationText.setText(info.url);
+                keywordText.setText(info.keyword);
+
+                final int rowId = info.rowId;
+                editPrompt.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        (new UiAsyncTask<Void, Void, Void>(ThreadUtils.getBackgroundHandler()) {
+                            @Override
+                            public Void doInBackground(Void... params) {
+                                String newUrl = locationText.getText().toString().trim();
+                                String newKeyword = keywordText.getText().toString().trim();
+                                BrowserDB.updateBookmark(activity.getContentResolver(), rowId, newUrl, nameText.getText().toString(), newKeyword);
+                                return null;
+                            }
+
+                            @Override
+                            public void onPostExecute(Void result) {
+                                Toast.makeText(activity, R.string.bookmark_updated, Toast.LENGTH_SHORT).show();
+                            }
+                        }).execute();
+                    }
+                });
+
+                editPrompt.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                          // do nothing
+                      }
+                });
+
+                final AlertDialog dialog = editPrompt.create();
+                dialog.show();
+                return true;
+            }
+
+            case R.id.remove_bookmark: {
+                final int rowId = info.rowId;
+                (new UiAsyncTask<Void, Void, Void>(ThreadUtils.getBackgroundHandler()) {
+                    @Override
+                    public Void doInBackground(Void... params) {
+                        BrowserDB.removeBookmark(activity.getContentResolver(), rowId);
+                        return null;
+                    }
+
+                    @Override
+                    public void onPostExecute(Void result) {
+                        Toast.makeText(activity, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
+                    }
+                }).execute();
+                return true;
+            }
+        }
+        return false;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/HomeListView.java
@@ -0,0 +1,96 @@
+/* -*- 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.db.BrowserContract.Bookmarks;
+import org.mozilla.gecko.db.BrowserContract.Combined;
+import org.mozilla.gecko.db.BrowserDB.URLColumns;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.util.AttributeSet;
+import android.view.ContextMenu.ContextMenuInfo;
+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.
+ */
+public class HomeListView extends ListView
+                          implements OnItemLongClickListener {
+
+    // ContextMenuInfo associated with the currently long pressed list item.
+    private HomeContextMenuInfo mContextMenuInfo;
+
+    public HomeListView(Context context) {
+        this(context, null);
+    }
+
+    public HomeListView(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.attr.listViewStyle);
+    }
+
+    public HomeListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        setOnItemLongClickListener(this);
+    }
+
+    @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);
+    }
+
+    @Override
+    public ContextMenuInfo getContextMenuInfo() {
+        return mContextMenuInfo;
+    }
+
+    /**
+     * A ContextMenuInfo for HomeListView that adds details from the cursor.
+     */
+    public static class HomeContextMenuInfo extends AdapterContextMenuInfo {
+        public int rowId;
+        public String url;
+        public byte[] favicon;
+        public String title;
+        public String keyword;
+        public int display;
+        public boolean isFolder;
+
+        public HomeContextMenuInfo(View targetView, int position, long id, Cursor cursor) {
+            super(targetView, position, id);
+
+            if (cursor == null) {
+                return;
+            }
+
+            isFolder = (cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER);
+
+            if (isFolder) {
+                return;
+            }
+
+            int keywordCol = cursor.getColumnIndex(URLColumns.KEYWORD);
+            if (keywordCol != -1) {
+                keyword = cursor.getString(keywordCol);
+            } else {
+                keyword = null;
+            }
+
+            rowId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
+            url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
+            title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
+            favicon = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON));
+            display = Combined.DISPLAY_NORMAL;
+        }
+    }
+}