diff --git a/mobile/android/base/GeckoViewsFactory.java b/mobile/android/base/GeckoViewsFactory.java index be136174d9a..3db52cf3235 100644 --- a/mobile/android/base/GeckoViewsFactory.java +++ b/mobile/android/base/GeckoViewsFactory.java @@ -5,6 +5,8 @@ package org.mozilla.gecko; import org.mozilla.gecko.gfx.LayerView; +import org.mozilla.gecko.home.BookmarkFolderView; +import org.mozilla.gecko.home.TwoLinePageRow; import org.mozilla.gecko.menu.MenuItemDefault; import org.mozilla.gecko.widget.AboutHomeView; import org.mozilla.gecko.widget.AddonsSection; @@ -87,6 +89,8 @@ public final class GeckoViewsFactory implements LayoutInflater.Factory { mFactoryMap.put("TextSwitcher", GeckoTextSwitcher.class.getConstructor(arg1Class, arg2Class)); mFactoryMap.put("TextView", GeckoTextView.class.getConstructor(arg1Class, arg2Class)); mFactoryMap.put("FaviconView", FaviconView.class.getConstructor(arg1Class, arg2Class)); + mFactoryMap.put("home.BookmarkFolderView", BookmarkFolderView.class.getConstructor(arg1Class, arg2Class)); + mFactoryMap.put("home.TwoLinePageRow", TwoLinePageRow.class.getConstructor(arg1Class, arg2Class)); } catch (NoSuchMethodException nsme) { Log.e(LOGTAG, "Unable to initialize views factory", nsme); } diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index a74812ef3a3..94a7046a3cc 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -214,8 +214,11 @@ FENNEC_JAVA_FILES = \ gfx/TouchEventHandler.java \ gfx/ViewTransform.java \ gfx/VirtualLayer.java \ + home/BookmarksPage.java \ + home/BookmarkFolderView.java \ home/HomePager.java \ home/HomePagerTabStrip.java \ + home/TwoLinePageRow.java \ menu/GeckoMenu.java \ menu/GeckoMenuInflater.java \ menu/GeckoMenuItem.java \ @@ -430,6 +433,8 @@ RES_LAYOUT = \ res/layout/awesomebar_tab_indicator.xml \ res/layout/awesomebar_tabs.xml \ res/layout/bookmark_edit.xml \ + res/layout/bookmark_folder_row.xml \ + res/layout/bookmark_item_row.xml \ res/layout/browser_toolbar.xml \ res/layout/datetime_picker.xml \ res/layout/doorhangerpopup.xml \ @@ -460,6 +465,7 @@ RES_LAYOUT = \ res/layout/tabs_item_cell.xml \ res/layout/tabs_item_row.xml \ res/layout/text_selection_handles.xml \ + res/layout/two_line_page_row.xml \ res/layout/list_item_header.xml \ res/layout/select_dialog_list.xml \ res/layout/select_dialog_multichoice.xml \ @@ -544,6 +550,10 @@ RES_VALUES_V14 = \ res/values-v14/styles.xml \ $(NULL) +RES_VALUES_V16 = \ + res/values-v16/styles.xml \ + $(NULL) + RES_XML = \ res/xml/preferences_datareporting.xml \ $(SYNC_RES_XML) \ @@ -591,6 +601,8 @@ RES_DRAWABLE_MDPI = \ res/drawable-mdpi/awesomebar_tab_right.9.png \ res/drawable-mdpi/awesomebar_sep_left.9.png \ res/drawable-mdpi/awesomebar_sep_right.9.png \ + res/drawable-mdpi/bookmark_folder_closed.png \ + res/drawable-mdpi/bookmark_folder_opened.png \ res/drawable-mdpi/desktop_notification.png \ res/drawable-mdpi/ic_addons_empty.png \ res/drawable-mdpi/ic_menu_addons_filler.png \ @@ -705,6 +717,8 @@ RES_DRAWABLE_HDPI = \ res/drawable-hdpi/awesomebar_tab_right.9.png \ res/drawable-hdpi/awesomebar_sep_left.9.png \ res/drawable-hdpi/awesomebar_sep_right.9.png \ + res/drawable-hdpi/bookmark_folder_closed.png \ + res/drawable-hdpi/bookmark_folder_opened.png \ res/drawable-hdpi/ic_addons_empty.png \ res/drawable-hdpi/ic_menu_addons_filler.png \ res/drawable-hdpi/ic_menu_bookmark_add.png \ @@ -799,6 +813,8 @@ RES_DRAWABLE_XHDPI = \ res/drawable-xhdpi/awesomebar_tab_right.9.png \ res/drawable-xhdpi/awesomebar_sep_left.9.png \ res/drawable-xhdpi/awesomebar_sep_right.9.png \ + res/drawable-xhdpi/bookmark_folder_closed.png \ + res/drawable-xhdpi/bookmark_folder_opened.png \ res/drawable-xhdpi/ic_addons_empty.png \ res/drawable-xhdpi/ic_menu_addons_filler.png \ res/drawable-xhdpi/ic_menu_bookmark_add.png \ @@ -1040,6 +1056,7 @@ MOZ_ANDROID_DRAWABLES += \ mobile/android/base/resources/drawable/awesomebar_tab_indicator.xml \ mobile/android/base/resources/drawable/awesomebar_tab_selected.xml \ mobile/android/base/resources/drawable/awesomebar_tab_unselected.xml \ + mobile/android/base/resources/drawable/bookmark_folder.xml \ mobile/android/base/resources/drawable/favicon_bg.xml \ mobile/android/base/resources/drawable/handle_end_level.xml \ mobile/android/base/resources/drawable/handle_start_level.xml \ @@ -1065,7 +1082,7 @@ MOZ_ANDROID_DRAWABLES += \ MOZ_BRANDING_DRAWABLE_MDPI = $(shell if test -e $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn; then cat $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn | tr '\n' ' '; fi) -RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_LARGE_LAND_V11) $(RES_LAYOUT_LARGE_V11) $(RES_LAYOUT_XLARGE_V11) $(RES_LAYOUT_XLARGE_LAND_V11) $(RES_VALUES) $(RES_VALUES_LAND) $(RES_VALUES_V11) $(RES_VALUES_LARGE_V11) $(RES_VALUES_LARGE_LAND_V11) $(RES_VALUES_XLARGE_V11) $(RES_VALUES_LAND_V14) $(RES_VALUES_V14) $(RES_XML) $(RES_XML_V11) $(RES_ANIM) $(RES_DRAWABLE_MDPI) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_XHDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LARGE_MDPI_V11) $(RES_DRAWABLE_LARGE_HDPI_V11) $(RES_DRAWABLE_LARGE_XHDPI_V11) $(RES_DRAWABLE_XLARGE_MDPI_V11) $(RES_DRAWABLE_XLARGE_HDPI_V11) $(RES_DRAWABLE_XLARGE_XHDPI_V11) $(RES_COLOR) $(RES_MENU) +RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_LARGE_LAND_V11) $(RES_LAYOUT_LARGE_V11) $(RES_LAYOUT_XLARGE_V11) $(RES_LAYOUT_XLARGE_LAND_V11) $(RES_VALUES) $(RES_VALUES_LAND) $(RES_VALUES_V11) $(RES_VALUES_LARGE_V11) $(RES_VALUES_LARGE_LAND_V11) $(RES_VALUES_XLARGE_V11) $(RES_VALUES_LAND_V14) $(RES_VALUES_V14) $(RES_VALUES_V16) $(RES_XML) $(RES_XML_V11) $(RES_ANIM) $(RES_DRAWABLE_MDPI) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_XHDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LARGE_MDPI_V11) $(RES_DRAWABLE_LARGE_HDPI_V11) $(RES_DRAWABLE_LARGE_XHDPI_V11) $(RES_DRAWABLE_XLARGE_MDPI_V11) $(RES_DRAWABLE_XLARGE_HDPI_V11) $(RES_DRAWABLE_XLARGE_XHDPI_V11) $(RES_COLOR) $(RES_MENU) RES_DIRS= \ res/layout \ @@ -1077,7 +1094,8 @@ RES_DIRS= \ res/values-v11 \ res/values-large-v11 \ res/values-xlarge-v11 \ - res/values-land-v14 \ + res/values-v14 \ + res/values-v16 \ res/xml \ res/xml-v11 \ res/anim \ diff --git a/mobile/android/base/home/BookmarkFolderView.java b/mobile/android/base/home/BookmarkFolderView.java new file mode 100644 index 00000000000..996e6d9ba3a --- /dev/null +++ b/mobile/android/base/home/BookmarkFolderView.java @@ -0,0 +1,55 @@ +/* -*- 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.R; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +public class BookmarkFolderView extends TextView { + private static final int[] STATE_OPEN = { R.attr.state_open }; + + private boolean mIsOpen = false; + + public BookmarkFolderView(Context context) { + super(context); + } + + public BookmarkFolderView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BookmarkFolderView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public int[] onCreateDrawableState(int extraSpace) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + + if (mIsOpen) { + mergeDrawableStates(drawableState, STATE_OPEN); + } + + return drawableState; + } + + public void open() { + if (!mIsOpen) { + mIsOpen = true; + refreshDrawableState(); + } + } + + public void close() { + if (mIsOpen) { + mIsOpen = false; + refreshDrawableState(); + } + } +} diff --git a/mobile/android/base/home/BookmarksPage.java b/mobile/android/base/home/BookmarksPage.java new file mode 100644 index 00000000000..1fe305278d2 --- /dev/null +++ b/mobile/android/base/home/BookmarksPage.java @@ -0,0 +1,443 @@ +/* -*- 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.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.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 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; + + // Folder title for the currently shown list of bookmarks. + private BookmarkFolderView mFolderView; + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + // Intialize the adapter. + mCursorAdapter = new BookmarksListAdapter(getActivity()); + } + + @Override + public void onDetach() { + super.onDetach(); + + // Can't use getters for adapter. It will create one if null. + if (mCursorAdapter != null) { + final Cursor cursor = mCursorAdapter.getCursor(); + mCursorAdapter = null; + + // Gingerbread locks the DB when closing a cursor, so do it in the background. + ThreadUtils.postToBackgroundThread(new Runnable() { + @Override + public void run() { + if (cursor != null && !cursor.isClosed()) + cursor.close(); + } + }); + } + } + + @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()); + return mList; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // Folder title view, is always in open state. + mFolderView = (BookmarkFolderView) LayoutInflater.from(getActivity()).inflate(R.layout.bookmark_folder_row, null); + mFolderView.open(); + + // We need to add the header before we set the adapter, hence make it null + refreshListWithCursor(null); + + EventHandlers eventHandlers = new EventHandlers(); + mList.setOnTouchListener(eventHandlers); + mList.setOnItemClickListener(eventHandlers); + mList.setOnCreateContextMenuListener(eventHandlers); + mList.setOnKeyListener(GamepadUtils.getListItemClickDispatcher()); + + mQueryTask = new BookmarksQueryTask(); + mQueryTask.execute(); + } + + @Override + public void onDestroyView() { + mList = null; + mFolderView = null; + super.onDestroyView(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Reattach the fragment, forcing a reinflation of its view. + // We use commitAllowingStateLoss() instead of commit() here to avoid + // an IllegalStateException. If the phone is rotated while Fennec + // is in the background, onConfigurationChanged() is fired. + // onConfigurationChanged() is called before onResume(), so + // using commit() would throw an IllegalStateException since it can't + // be used between the Activity's onSaveInstanceState() and + // onResume(). + if (isVisible()) { + getFragmentManager().beginTransaction() + .detach(this) + .attach(this) + .commitAllowingStateLoss(); + } + } + + private void refreshListWithCursor(Cursor cursor) { + // We need to add the header before we set the adapter, hence making it null. + mList.setAdapter(null); + + // Add a header view based on the root folder. + if (mFolderId == Bookmarks.FIXED_ROOT_ID) { + if (mList.getHeaderViewsCount() == 1) { + mList.removeHeaderView(mFolderView); + } + } else { + if (mList.getHeaderViewsCount() == 0) { + mList.addHeaderView(mFolderView, null, true); + } + + mFolderView.setText(mFolderTitle); + } + + // This will update the cursorAdapter to use the new one if it already exists. + mCursorAdapter.changeCursor(cursor); + mList.setAdapter(mCursorAdapter); + + // Reset the task. + mQueryTask = null; + } + + /** + * Internal class to handle different event listeners on the ListView. + */ + private class EventHandlers implements AdapterView.OnItemClickListener, + View.OnCreateContextMenuListener, + View.OnTouchListener { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + final ListView list = (ListView) parent; + final int headerCount = list.getHeaderViewsCount(); + + // If we tap on the header view, move back to parent folder. + if (headerCount == 1 && position == 0) { + mCursorAdapter.moveToParentFolder(); + return; + } + + Cursor cursor = mCursorAdapter.getCursor(); + if (cursor == null) { + return; + } + + // The header view takes up a spot in the list + if (headerCount == 1) { + position--; + } + + cursor.moveToPosition(position); + + int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE)); + if (type == Bookmarks.TYPE_FOLDER) { + // If we're clicking on a folder, update adapter to move to that folder + int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID)); + String folderTitle = mCursorAdapter.getFolderTitle(position); + mCursorAdapter.moveToChildFolder(folderId, folderTitle); + } else { + // Otherwise, just open the URL + String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); + Tabs.getInstance().loadUrl(url); + } + } + + @Override + public boolean onTouch(View view, MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + // 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) { + return; + } + + String keyword = null; + int keywordCol = cursor.getColumnIndex(URLColumns.KEYWORD); + if (keywordCol != -1) { + keyword = cursor.getString(keywordCol); + } + + 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()); + 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) { + menu.findItem(R.id.remove_history).setVisible(false); + } + menu.setHeaderTitle(title); + + menu.findItem(R.id.remove_history).setVisible(false); + menu.findItem(R.id.open_in_reader).setVisible(false); + return; + } + } + + /** + * Adapter to back the ListView with a list of bookmarks. + */ + private class BookmarksListAdapter extends SimpleCursorAdapter { + private static final int VIEW_TYPE_ITEM = 0; + private static final int VIEW_TYPE_FOLDER = 1; + + private static final int VIEW_TYPE_COUNT = 2; + + // mParentStack holds folder id/title pairs that allow us to navigate + // back up the folder heirarchy. + private LinkedList> mParentStack; + + public BookmarksListAdapter(Context context) { + // Initializing with a null cursor. + super(context, -1, null, new String[] {}, new int[] {}); + + mParentStack = new LinkedList>(); + + // Add the root folder to the stack + Pair rootFolder = new Pair(mFolderId, mFolderTitle); + mParentStack.addFirst(rootFolder); + } + + // Refresh the current folder by executing a new task. + private void refreshCurrentFolder() { + // Cancel any pre-existing async refresh tasks + if (mQueryTask != null) { + mQueryTask.cancel(false); + } + + Pair folderPair = mParentStack.getFirst(); + mFolderId = folderPair.first; + mFolderTitle = folderPair.second; + + mQueryTask = new BookmarksQueryTask(); + mQueryTask.execute(new Integer(mFolderId)); + } + + /** + * Moves to parent folder, if one exists. + */ + public void moveToParentFolder() { + // If we're already at the root, we can't move to a parent folder + if (mParentStack.size() != 1) { + mParentStack.removeFirst(); + refreshCurrentFolder(); + } + } + + /** + * Moves to child folder, given a folderId. + * + * @param folderId The id of the folder to show. + * @param folderTitle The title of the folder to show. + */ + public void moveToChildFolder(int folderId, String folderTitle) { + Pair folderPair = new Pair(folderId, folderTitle); + mParentStack.addFirst(folderPair); + refreshCurrentFolder(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getItemViewType(int position) { + Cursor c = getCursor(); + + if (c.moveToPosition(position) && + c.getInt(c.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER) { + return VIEW_TYPE_FOLDER; + } + + // Default to retuning normal item type + return VIEW_TYPE_ITEM; + } + + /** + * {@inheritDoc} + */ + @Override + public int getViewTypeCount() { + return VIEW_TYPE_COUNT; + } + + /** + * Get the title of the folder for a given position. + * + * @param position The position of the view. + * @return The title of the folder at the position. + */ + public String getFolderTitle(int position) { + Cursor c = getCursor(); + if (!c.moveToPosition(position)) { + return ""; + } + + String guid = c.getString(c.getColumnIndexOrThrow(Bookmarks.GUID)); + + // If we don't have a special GUID, just return the folder title from the DB. + if (guid == null || guid.length() == 12) { + return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE)); + } + + // Use localized strings for special folder names. + if (guid.equals(Bookmarks.FAKE_DESKTOP_FOLDER_GUID)) { + return getResources().getString(R.string.bookmarks_folder_desktop); + } else if (guid.equals(Bookmarks.MENU_FOLDER_GUID)) { + return getResources().getString(R.string.bookmarks_folder_menu); + } else if (guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID)) { + return getResources().getString(R.string.bookmarks_folder_toolbar); + } else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) { + return getResources().getString(R.string.bookmarks_folder_unfiled); + } + + // If for some reason we have a folder with a special GUID, but it's not one of + // the special folders we expect in the UI, just return the title from the DB. + return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE)); + } + + /** + * {@inheritDoc} + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final int viewType = getItemViewType(position); + + if (convertView == null) { + if (viewType == VIEW_TYPE_ITEM) { + convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.bookmark_item_row, null); + } else { + convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.bookmark_folder_row, null); + } + } + + Cursor cursor = getCursor(); + if (!cursor.moveToPosition(position)) { + throw new IllegalStateException("Couldn't move cursor to position " + position); + } + + if (viewType == VIEW_TYPE_ITEM) { + TwoLinePageRow row = (TwoLinePageRow) convertView; + row.updateFromCursor(cursor); + } else { + BookmarkFolderView row = (BookmarkFolderView) convertView; + row.setText(getFolderTitle(position)); + } + + return convertView; + } + } + + /** + * AsyncTask to query the DB for bookmarks. + */ + private class BookmarksQueryTask extends AsyncTask { + @Override + protected Cursor doInBackground(Integer... folderIds) { + int folderId = Bookmarks.FIXED_ROOT_ID; + + if (folderIds.length != 0) { + folderId = folderIds[0].intValue(); + } + + return BrowserDB.getBookmarksInFolder(getActivity().getContentResolver(), folderId); + } + + @Override + protected void onPostExecute(final Cursor cursor) { + refreshListWithCursor(cursor); + } + } +} diff --git a/mobile/android/base/home/HomePager.java b/mobile/android/base/home/HomePager.java index 037be933853..24582437a82 100644 --- a/mobile/android/base/home/HomePager.java +++ b/mobile/android/base/home/HomePager.java @@ -5,6 +5,7 @@ package org.mozilla.gecko.home; +import org.mozilla.gecko.R; import org.mozilla.gecko.widget.AboutHome; import android.content.Context; @@ -26,6 +27,7 @@ public class HomePager extends ViewPager { // List of pages in order. private enum Page { + BOOKMARKS } private EnumMap mPages = new EnumMap(Page.class); @@ -50,6 +52,7 @@ public class HomePager extends ViewPager { TabsAdapter adapter = new TabsAdapter(fm); // Add the pages to the adapter in order. + adapter.addTab(Page.BOOKMARKS, BookmarksPage.class, null, getContext().getString(R.string.bookmarks_title)); setAdapter(adapter); setVisibility(VISIBLE); diff --git a/mobile/android/base/home/TwoLinePageRow.java b/mobile/android/base/home/TwoLinePageRow.java new file mode 100644 index 00000000000..e72e472327d --- /dev/null +++ b/mobile/android/base/home/TwoLinePageRow.java @@ -0,0 +1,92 @@ +/* -*- 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.Favicons; +import org.mozilla.gecko.R; +import org.mozilla.gecko.db.BrowserDB.URLColumns; +import org.mozilla.gecko.gfx.BitmapUtils; +import org.mozilla.gecko.widget.FaviconView; + +import android.content.Context; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class TwoLinePageRow extends LinearLayout { + + private final TextView mTitle; + private final TextView mUrl; + private final FaviconView mFavicon; + + public TwoLinePageRow(Context context) { + this(context, null); + } + + public TwoLinePageRow(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TwoLinePageRow(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + setGravity(Gravity.CENTER_VERTICAL); + + LayoutInflater.from(context).inflate(R.layout.two_line_page_row, this); + mTitle = (TextView) findViewById(R.id.title); + mUrl = (TextView) findViewById(R.id.url); + mFavicon = (FaviconView) findViewById(R.id.favicon); + } + + public void updateFromCursor(Cursor cursor) { + if (cursor == null) { + return; + } + + int titleIndex = cursor.getColumnIndexOrThrow(URLColumns.TITLE); + final String title = cursor.getString(titleIndex); + + int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL); + final String url = cursor.getString(urlIndex); + + // Use the URL instead of an empty title for consistency with the normal URL + // bar view - this is the equivalent of getDisplayTitle() in Tab.java + if (TextUtils.isEmpty(title)) { + mTitle.setText(url); + } else { + mTitle.setText(title); + } + + // Update the url with "Switch to tab" if needed. + // FIXME: Bug 877469: Add back switch to tab functionality. + Integer tabId = null; + if (tabId != null) { + mUrl.setText(R.string.switch_to_tab); + mUrl.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_url_bar_tab, 0, 0, 0); + } else { + mUrl.setText(url); + mUrl.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + + byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON)); + Bitmap favicon = null; + if (b != null) { + Bitmap bitmap = BitmapUtils.decodeByteArray(b); + if (bitmap != null) { + favicon = Favicons.getInstance().scaleImage(bitmap); + } + } + + mFavicon.updateImage(favicon, url); + } +} diff --git a/mobile/android/base/resources/drawable-hdpi/bookmark_folder_closed.png b/mobile/android/base/resources/drawable-hdpi/bookmark_folder_closed.png new file mode 100644 index 00000000000..fa29670fbd8 Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/bookmark_folder_closed.png differ diff --git a/mobile/android/base/resources/drawable-hdpi/bookmark_folder_opened.png b/mobile/android/base/resources/drawable-hdpi/bookmark_folder_opened.png new file mode 100644 index 00000000000..ab96b22db61 Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/bookmark_folder_opened.png differ diff --git a/mobile/android/base/resources/drawable-mdpi/bookmark_folder_closed.png b/mobile/android/base/resources/drawable-mdpi/bookmark_folder_closed.png new file mode 100644 index 00000000000..2ca9e218209 Binary files /dev/null and b/mobile/android/base/resources/drawable-mdpi/bookmark_folder_closed.png differ diff --git a/mobile/android/base/resources/drawable-mdpi/bookmark_folder_opened.png b/mobile/android/base/resources/drawable-mdpi/bookmark_folder_opened.png new file mode 100644 index 00000000000..9edb231d95e Binary files /dev/null and b/mobile/android/base/resources/drawable-mdpi/bookmark_folder_opened.png differ diff --git a/mobile/android/base/resources/drawable-xhdpi/bookmark_folder_closed.png b/mobile/android/base/resources/drawable-xhdpi/bookmark_folder_closed.png new file mode 100644 index 00000000000..7582d2f0727 Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi/bookmark_folder_closed.png differ diff --git a/mobile/android/base/resources/drawable-xhdpi/bookmark_folder_opened.png b/mobile/android/base/resources/drawable-xhdpi/bookmark_folder_opened.png new file mode 100644 index 00000000000..03076f7bd6b Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi/bookmark_folder_opened.png differ diff --git a/mobile/android/base/resources/drawable/bookmark_folder.xml b/mobile/android/base/resources/drawable/bookmark_folder.xml new file mode 100644 index 00000000000..5fa2ad3e23a --- /dev/null +++ b/mobile/android/base/resources/drawable/bookmark_folder.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/mobile/android/base/resources/layout/bookmark_folder_row.xml b/mobile/android/base/resources/layout/bookmark_folder_row.xml new file mode 100644 index 00000000000..4a21dd0dcd9 --- /dev/null +++ b/mobile/android/base/resources/layout/bookmark_folder_row.xml @@ -0,0 +1,11 @@ + + + + diff --git a/mobile/android/base/resources/layout/bookmark_item_row.xml b/mobile/android/base/resources/layout/bookmark_item_row.xml new file mode 100644 index 00000000000..97894cbc82e --- /dev/null +++ b/mobile/android/base/resources/layout/bookmark_item_row.xml @@ -0,0 +1,9 @@ + + + + diff --git a/mobile/android/base/resources/layout/two_line_page_row.xml b/mobile/android/base/resources/layout/two_line_page_row.xml new file mode 100644 index 00000000000..2c9f5731e6f --- /dev/null +++ b/mobile/android/base/resources/layout/two_line_page_row.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/mobile/android/base/resources/values-v16/styles.xml b/mobile/android/base/resources/values-v16/styles.xml new file mode 100644 index 00000000000..25fd7badcb2 --- /dev/null +++ b/mobile/android/base/resources/values-v16/styles.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/mobile/android/base/resources/values/attrs.xml b/mobile/android/base/resources/values/attrs.xml index dda76737b27..739f279e6b2 100644 --- a/mobile/android/base/resources/values/attrs.xml +++ b/mobile/android/base/resources/values/attrs.xml @@ -168,5 +168,9 @@ + + + + diff --git a/mobile/android/base/resources/values/colors.xml b/mobile/android/base/resources/values/colors.xml index 7366ec158a3..352ff6f6e04 100644 --- a/mobile/android/base/resources/values/colors.xml +++ b/mobile/android/base/resources/values/colors.xml @@ -37,7 +37,7 @@ --> #222222 - #666666 + #777777 #9198A1 diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml index 41d9b7c8fc4..875573dcd6a 100644 --- a/mobile/android/base/resources/values/dimens.xml +++ b/mobile/android/base/resources/values/dimens.xml @@ -36,6 +36,9 @@ 32dp 1dp + + 64dp + 400dp diff --git a/mobile/android/base/resources/values/styles.xml b/mobile/android/base/resources/values/styles.xml index baa5b651f92..828faba18d1 100644 --- a/mobile/android/base/resources/values/styles.xml +++ b/mobile/android/base/resources/values/styles.xml @@ -69,6 +69,27 @@ marquee + + + + + +