Merge m-c to fx-team.

This commit is contained in:
Ryan VanderMeulen 2013-09-20 18:19:03 -04:00
commit c06b89cd15
54 changed files with 1195 additions and 1154 deletions

View File

@ -1,4 +1,4 @@
{
"revision": "5f1e3b7ecd82f69d2a2cac293114c4cf4b3b8d13",
"revision": "2815b6670938908c94b8e927401fa8895c12d794",
"repo_path": "/integration/gaia-central"
}

View File

@ -1401,7 +1401,7 @@ abstract public class BrowserApp extends GeckoApp
animator.setUseHardwareLayer(false);
mBrowserToolbar.startEditing(url, animator);
showHomePagerWithAnimator(HomePager.Page.HISTORY, animator);
showHomePagerWithAnimator(HomePager.Page.TOP_SITES, animator);
animator.start();
}

View File

@ -217,7 +217,6 @@ FENNEC_JAVA_FILES = \
home/BookmarksListView.java \
home/BookmarksPage.java \
home/BookmarkFolderView.java \
home/BookmarkThumbnailView.java \
home/BrowserSearch.java \
home/HistoryPage.java \
home/HomeFragment.java \
@ -228,9 +227,8 @@ FENNEC_JAVA_FILES = \
home/FadedTextView.java \
home/LastTabsPage.java \
home/MostRecentPage.java \
home/MostVisitedPage.java \
home/MultiTypeCursorAdapter.java \
home/PinBookmarkDialog.java \
home/PinSiteDialog.java \
home/ReadingListPage.java \
home/SearchEngine.java \
home/SearchEngineRow.java \
@ -238,9 +236,10 @@ FENNEC_JAVA_FILES = \
home/SimpleCursorLoader.java \
home/SuggestClient.java \
home/TabMenuStrip.java \
home/TopBookmarkItemView.java \
home/TopBookmarksAdapter.java \
home/TopBookmarksView.java \
home/TopSitesGridItemView.java \
home/TopSitesGridView.java \
home/TopSitesPage.java \
home/TopSitesThumbnailView.java \
home/TwoLinePageRow.java \
menu/GeckoMenu.java \
menu/GeckoMenuInflater.java \
@ -476,12 +475,12 @@ RES_LAYOUT = \
res/layout/home_last_tabs_page.xml \
res/layout/home_history_list.xml \
res/layout/home_most_recent_page.xml \
res/layout/home_most_visited_page.xml \
res/layout/home_pager.xml \
res/layout/home_reading_list_page.xml \
res/layout/home_search_item_row.xml \
res/layout/home_banner.xml \
res/layout/home_suggestion_prompt.xml \
res/layout/home_top_sites_page.xml \
res/layout/web_app.xml \
res/layout/launch_app_list.xml \
res/layout/launch_app_listitem.xml \
@ -491,7 +490,7 @@ RES_LAYOUT = \
res/layout/notification_icon_text.xml \
res/layout/notification_progress.xml \
res/layout/notification_progress_text.xml \
res/layout/pin_bookmark_dialog.xml \
res/layout/pin_site_dialog.xml \
res/layout/preference_rightalign_icon.xml \
res/layout/preference_search_engine.xml \
res/layout/preference_search_tip.xml \
@ -510,7 +509,7 @@ RES_LAYOUT = \
res/layout/tabs_item_cell.xml \
res/layout/tabs_item_row.xml \
res/layout/text_selection_handles.xml \
res/layout/top_bookmark_item_view.xml \
res/layout/top_sites_grid_item_view.xml \
res/layout/two_line_page_row.xml \
res/layout/list_item_header.xml \
res/layout/select_dialog_list.xml \
@ -709,12 +708,13 @@ RES_DRAWABLE_MDPI = \
res/drawable-mdpi/menu_item_check.png \
res/drawable-mdpi/menu_item_more.png \
res/drawable-mdpi/menu_item_uncheck.png \
res/drawable-mdpi/pin.png \
res/drawable-mdpi/shield.png \
res/drawable-mdpi/shield_doorhanger.png \
res/drawable-mdpi/tabs_normal.png \
res/drawable-mdpi/tabs_private.png \
res/drawable-mdpi/tabs_synced.png \
res/drawable-mdpi/top_bookmark_add.png \
res/drawable-mdpi/top_site_add.png \
res/drawable-mdpi/urlbar_stop.png \
res/drawable-mdpi/reader.png \
res/drawable-mdpi/reader_cropped.png \
@ -817,12 +817,13 @@ RES_DRAWABLE_HDPI = \
res/drawable-hdpi/menu_item_check.png \
res/drawable-hdpi/menu_item_more.png \
res/drawable-hdpi/menu_item_uncheck.png \
res/drawable-hdpi/pin.png \
res/drawable-hdpi/shield.png \
res/drawable-hdpi/shield_doorhanger.png \
res/drawable-hdpi/tabs_normal.png \
res/drawable-hdpi/tabs_private.png \
res/drawable-hdpi/tabs_synced.png \
res/drawable-hdpi/top_bookmark_add.png \
res/drawable-hdpi/top_site_add.png \
res/drawable-hdpi/urlbar_stop.png \
res/drawable-hdpi/reader.png \
res/drawable-hdpi/reader_cropped.png \
@ -897,7 +898,7 @@ RES_DRAWABLE_XHDPI = \
res/drawable-xhdpi/find_close.png \
res/drawable-xhdpi/find_next.png \
res/drawable-xhdpi/find_prev.png \
res/drawable-xhdpi/top_bookmark_add.png \
res/drawable-xhdpi/top_site_add.png \
res/drawable-xhdpi/urlbar_stop.png \
res/drawable-xhdpi/reader.png \
res/drawable-xhdpi/reader_cropped.png \
@ -915,6 +916,7 @@ RES_DRAWABLE_XHDPI = \
res/drawable-xhdpi/menu_item_check.png \
res/drawable-xhdpi/menu_item_more.png \
res/drawable-xhdpi/menu_item_uncheck.png \
res/drawable-xhdpi/pin.png \
res/drawable-xhdpi/shield.png \
res/drawable-xhdpi/shield_doorhanger.png \
res/drawable-xhdpi/tab_indicator_divider.9.png \
@ -1073,7 +1075,7 @@ RES_COLOR = \
res/color/select_item_multichoice.xml \
res/color/tertiary_text.xml \
res/color/tertiary_text_inverse.xml \
res/color/top_bookmark_item_title.xml \
res/color/top_sites_grid_item_title.xml \
res/color/url_bar_title.xml \
res/color/url_bar_title_hint.xml \
$(NULL)
@ -1083,7 +1085,7 @@ RES_MENU = \
res/menu/gecko_app_menu.xml \
res/menu/home_contextmenu.xml \
res/menu/titlebar_contextmenu.xml \
res/menu/top_bookmarks_contextmenu.xml \
res/menu/top_sites_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 \
@ -1101,7 +1103,7 @@ RES_DRAWABLE += \
$(SYNC_RES_DRAWABLE) \
res/drawable/action_bar_button.xml \
res/drawable/action_bar_button_inverse.xml \
res/drawable/bookmark_thumbnail_bg.xml \
res/drawable/top_sites_thumbnail_bg.xml \
res/drawable/url_bar_bg.xml \
res/drawable/url_bar_entry.xml \
res/drawable/url_bar_nav_button.xml \

View File

@ -93,7 +93,7 @@ public class Tab {
mUserSearch = "";
mExternal = external;
mParentId = parentId;
mAboutHomePage = HomePager.Page.BOOKMARKS;
mAboutHomePage = HomePager.Page.TOP_SITES;
mTitle = title == null ? "" : title;
mFavicon = null;
mFaviconUrl = null;

View File

@ -39,9 +39,9 @@ public class BrowserDB {
public Cursor filter(ContentResolver cr, CharSequence constraint, int limit);
// This should only return frecent bookmarks, BrowserDB.getTopBookmarks will do the
// This should onlyl return frecent sites, BrowserDB.getTopSites will do the
// work to combine that list with the pinned sites list
public Cursor getTopBookmarks(ContentResolver cr, int limit);
public Cursor getTopSites(ContentResolver cr, int limit);
public void updateVisitedHistory(ContentResolver cr, String uri);
@ -138,12 +138,12 @@ public class BrowserDB {
return sDb.filter(cr, constraint, limit);
}
public static Cursor getTopBookmarks(ContentResolver cr, int limit) {
// Note this is not a single query anymore, but actually returns a mixture of two queries,
// one for top bookmarks, and one for pinned sites (which are actually bookmarks as well).
Cursor topBookmarks = sDb.getTopBookmarks(cr, limit);
Cursor pinnedSites = sDb.getPinnedSites(cr, limit);
return new TopSitesCursorWrapper(pinnedSites, topBookmarks, limit);
public static Cursor getTopSites(ContentResolver cr, int minLimit, int maxLimit) {
// Note this is not a single query anymore, but actually returns a mixture
// of two queries, one for topSites and one for pinned sites.
Cursor pinnedSites = sDb.getPinnedSites(cr, minLimit);
Cursor topSites = sDb.getTopSites(cr, maxLimit - pinnedSites.getCount());
return new TopSitesCursorWrapper(pinnedSites, topSites, minLimit);
}
public static void updateVisitedHistory(ContentResolver cr, String uri) {
@ -342,12 +342,12 @@ public class BrowserDB {
int mSize = 0;
private SparseArray<PinnedSite> mPinnedSites = null;
public TopSitesCursorWrapper(Cursor pinnedCursor, Cursor normalCursor, int size) {
public TopSitesCursorWrapper(Cursor pinnedCursor, Cursor normalCursor, int minSize) {
super(normalCursor);
setPinnedSites(pinnedCursor);
mCursor = normalCursor;
mSize = size;
mSize = Math.max(minSize, mPinnedSites.size() + mCursor.getCount());
}
public void setPinnedSites(Cursor c) {
@ -452,6 +452,20 @@ public class BrowserDB {
return 0;
}
@Override
public int getInt(int columnIndex) {
if (hasPinnedSites()) {
PinnedSite site = getPinnedSite(mIndex);
if (site != null) {
return 0;
}
}
if (!super.isBeforeFirst() && !super.isAfterLast())
return super.getInt(columnIndex);
return 0;
}
@Override
public String getString(int columnIndex) {
if (hasPinnedSites()) {

View File

@ -231,13 +231,9 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
}
@Override
public Cursor getTopBookmarks(ContentResolver cr, int limit) {
// Only select bookmarks. Unfortunately, we need to query the combined view,
// instead of just the bookmarks table, in order to do the frecency calculation.
String selection = Combined.BOOKMARK_ID + " IS NOT NULL";
// Filter out sites that are pinned.
selection = DBUtils.concatenateWhere(selection, Combined.URL + " NOT IN (SELECT " +
public Cursor getTopSites(ContentResolver cr, int limit) {
// Filter out sites that are pinned
String selection = DBUtils.concatenateWhere("", Combined.URL + " NOT IN (SELECT " +
Bookmarks.URL + " FROM bookmarks WHERE " +
DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " == ? AND " +
DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)");
@ -245,7 +241,10 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return filterAllSites(cr,
new String[] { Combined._ID,
Combined.URL,
Combined.TITLE },
Combined.TITLE,
Combined.DISPLAY,
Combined.BOOKMARK_ID,
Combined.HISTORY_ID },
"",
limit,
BrowserDB.ABOUT_PAGES_URL_FILTER,

View File

@ -14,7 +14,6 @@ import android.content.Context;
import android.database.Cursor;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AdapterView;
@ -31,12 +30,6 @@ public class BookmarksListView extends HomeListView
implements AdapterView.OnItemClickListener{
public static final String LOGTAG = "GeckoBookmarksListView";
// The last motion event that was intercepted.
private MotionEvent mMotionEvent;
// The default touch slop.
private int mTouchSlop;
public BookmarksListView(Context context) {
this(context, null);
}
@ -47,9 +40,6 @@ public class BookmarksListView extends HomeListView
public BookmarksListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Scaled touch slop for this context.
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
@ -70,36 +60,6 @@ public class BookmarksListView extends HomeListView
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch(event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Store the event by obtaining a copy.
mMotionEvent = MotionEvent.obtain(event);
break;
}
case MotionEvent.ACTION_MOVE: {
if ((mMotionEvent != null) &&
(Math.abs(event.getY() - mMotionEvent.getY()) > mTouchSlop)) {
// The user is scrolling. Pass the last event to this view,
// and make this view scroll.
onTouchEvent(mMotionEvent);
return true;
}
break;
}
default: {
mMotionEvent = null;
break;
}
}
// Do default interception.
return super.onInterceptTouchEvent(event);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final ListView list = (ListView) parent;

View File

@ -8,21 +8,12 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.PropertyAnimator.Property;
import org.mozilla.gecko.animation.ViewHelper;
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.TopBookmarksAdapter.Thumbnail;
import org.mozilla.gecko.home.TopBookmarksView.OnPinBookmarkListener;
import org.mozilla.gecko.home.TopBookmarksView.TopBookmarksContextMenuInfo;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
@ -32,27 +23,14 @@ 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.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
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.
@ -63,57 +41,24 @@ public class BookmarksPage extends HomeFragment {
// Cursor loader ID for list of bookmarks.
private static final int LOADER_ID_BOOKMARKS_LIST = 0;
// Cursor loader ID for grid of bookmarks.
private static final int LOADER_ID_TOP_BOOKMARKS = 1;
// Loader ID for thumbnails.
private static final int LOADER_ID_THUMBNAILS = 2;
// Key for bookmarks folder id.
private static final String BOOKMARKS_FOLDER_KEY = "folder_id";
// Key for thumbnail urls.
private static final String THUMBNAILS_URLS_KEY = "urls";
// List of bookmarks.
private BookmarksListView mList;
// Grid of top bookmarks.
private TopBookmarksView mTopBookmarks;
// Banner to show snippets.
private HomeBanner mBanner;
// Adapter for list of bookmarks.
private BookmarksListAdapter mListAdapter;
// Adapter for grid of bookmarks.
private TopBookmarksAdapter mTopBookmarksAdapter;
// Callback for cursor loaders.
private CursorLoaderCallbacks mLoaderCallbacks;
// Callback for thumbnail loader.
private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks;
// Listener for pinning bookmarks.
private PinBookmarkListener mPinBookmarkListener;
// Raw Y value of the last event that happened on the list view.
private float mListTouchY = -1;
// Scrolling direction of the banner.
private boolean mSnapBannerToTop;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.home_bookmarks_page, container, false);
mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
mTopBookmarks = new TopBookmarksView(getActivity());
mList.addHeaderView(mTopBookmarks);
return view;
}
@ -129,26 +74,10 @@ public class BookmarksPage extends HomeFragment {
+ " must implement HomePager.OnUrlOpenListener");
}
mPinBookmarkListener = new PinBookmarkListener();
mList.setTag(HomePager.LIST_TAG_BOOKMARKS);
mList.setOnUrlOpenListener(listener);
mList.setHeaderDividersEnabled(false);
mTopBookmarks.setOnUrlOpenListener(listener);
mTopBookmarks.setOnPinBookmarkListener(mPinBookmarkListener);
registerForContextMenu(mList);
registerForContextMenu(mTopBookmarks);
mBanner = (HomeBanner) view.findViewById(R.id.home_banner);
mList.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
BookmarksPage.this.handleListTouchEvent(event);
return false;
}
});
}
@Override
@ -157,10 +86,6 @@ public class BookmarksPage extends HomeFragment {
final Activity activity = getActivity();
// Setup the top bookmarks adapter.
mTopBookmarksAdapter = new TopBookmarksAdapter(activity, null);
mTopBookmarks.setAdapter(mTopBookmarksAdapter);
// Setup the list adapter.
mListAdapter = new BookmarksListAdapter(activity, null);
mListAdapter.setOnRefreshFolderListener(new OnRefreshFolderListener() {
@ -180,7 +105,6 @@ public class BookmarksPage extends HomeFragment {
// Create callbacks before the initial loader is started.
mLoaderCallbacks = new CursorLoaderCallbacks();
mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
loadIfVisible();
}
@ -188,9 +112,6 @@ public class BookmarksPage extends HomeFragment {
public void onDestroyView() {
mList = null;
mListAdapter = null;
mTopBookmarks = null;
mTopBookmarksAdapter = null;
mPinBookmarkListener = null;
super.onDestroyView();
}
@ -214,204 +135,9 @@ public class BookmarksPage extends HomeFragment {
}
}
private void handleListTouchEvent(MotionEvent event) {
// Ignore the event if the banner is hidden for this session.
if (mBanner.isDismissed()) {
return;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mListTouchY = event.getRawY();
break;
}
case MotionEvent.ACTION_MOVE: {
// There is a chance that we won't receive ACTION_DOWN, if the touch event
// actually started on the Grid instead of the List. Treat this as first event.
if (mListTouchY == -1) {
mListTouchY = event.getRawY();
return;
}
final float curY = event.getRawY();
final float delta = mListTouchY - curY;
mSnapBannerToTop = (delta > 0.0f) ? false : true;
final float height = mBanner.getHeight();
float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta;
// Clamp the values to be between 0 and height.
if (newTranslationY < 0.0f) {
newTranslationY = 0.0f;
} else if (newTranslationY > height) {
newTranslationY = height;
}
ViewHelper.setTranslationY(mBanner, newTranslationY);
mListTouchY = curY;
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
mListTouchY = -1;
final float y = ViewHelper.getTranslationY(mBanner);
final float height = mBanner.getHeight();
if (y > 0.0f && y < height) {
final PropertyAnimator animator = new PropertyAnimator(100);
animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height);
animator.start();
}
break;
}
}
}
@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;
menu.setHeaderTitle(info.getDisplayTitle());
if (!TextUtils.isEmpty(info.url)) {
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.top_bookmarks_open_new_tab).setVisible(false);
menu.findItem(R.id.top_bookmarks_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();
final int itemId = item.getItemId();
if (itemId == R.id.top_bookmarks_open_new_tab || itemId == R.id.top_bookmarks_open_private_tab) {
if (info.url == null) {
Log.e(LOGTAG, "Can't open in new tab because URL is null");
return false;
}
int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
if (item.getItemId() == R.id.top_bookmarks_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;
}
if (itemId == 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;
}
if (itemId == 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;
}
if (itemId == R.id.top_bookmarks_edit) {
mPinBookmarkListener.onPinBookmark(info.position);
return true;
}
return false;
}
@Override
protected void load() {
final LoaderManager manager = getLoaderManager();
manager.initLoader(LOADER_ID_BOOKMARKS_LIST, null, mLoaderCallbacks);
manager.initLoader(LOADER_ID_TOP_BOOKMARKS, null, mLoaderCallbacks);
}
/**
* Listener for pinning bookmarks.
*/
private class PinBookmarkListener implements OnPinBookmarkListener,
OnBookmarkSelectedListener {
// Tag for the PinBookmarkDialog fragment.
private static final String TAG_PIN_BOOKMARK = "pin_bookmark";
// Position of the pin.
private int mPosition;
@Override
public void onPinBookmark(int position) {
mPosition = position;
final FragmentManager manager = getActivity().getSupportFragmentManager();
PinBookmarkDialog dialog = (PinBookmarkDialog) manager.findFragmentByTag(TAG_PIN_BOOKMARK);
if (dialog == null) {
dialog = PinBookmarkDialog.newInstance();
}
dialog.setOnBookmarkSelectedListener(this);
dialog.show(manager, TAG_PIN_BOOKMARK);
}
@Override
public void onBookmarkSelected(final String url, final String title) {
final int position = mPosition;
final Context context = getActivity().getApplicationContext();
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
BrowserDB.pinSite(context.getContentResolver(), url, title, position);
}
});
}
getLoaderManager().initLoader(LOADER_ID_BOOKMARKS_LIST, null, mLoaderCallbacks);
}
/**
@ -435,221 +161,28 @@ public class BookmarksPage extends HomeFragment {
}
}
/**
* Loader for the grid for top bookmarks.
*/
private static class TopBookmarksLoader extends SimpleCursorLoader {
public TopBookmarksLoader(Context context) {
super(context);
}
@Override
public Cursor loadCursor() {
final int max = getContext().getResources().getInteger(R.integer.number_of_top_sites);
return BrowserDB.getTopBookmarks(getContext().getContentResolver(), max);
}
}
/**
* Loader callbacks for the LoaderManager of this fragment.
*/
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch(id) {
case LOADER_ID_BOOKMARKS_LIST: {
if (args == null) {
return new BookmarksLoader(getActivity());
} else {
return new BookmarksLoader(getActivity(), args.getInt(BOOKMARKS_FOLDER_KEY));
}
}
case LOADER_ID_TOP_BOOKMARKS: {
return new TopBookmarksLoader(getActivity());
}
if (args == null) {
return new BookmarksLoader(getActivity());
} else {
return new BookmarksLoader(getActivity(), args.getInt(BOOKMARKS_FOLDER_KEY));
}
return null;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
final int loaderId = loader.getId();
switch(loaderId) {
case LOADER_ID_BOOKMARKS_LIST: {
mListAdapter.swapCursor(c);
mList.setHeaderDividersEnabled(c != null && c.getCount() > 0);
break;
}
case LOADER_ID_TOP_BOOKMARKS: {
mTopBookmarksAdapter.swapCursor(c);
// Load the thumbnails.
if (c.getCount() > 0 && c.moveToFirst()) {
final ArrayList<String> urls = new ArrayList<String>();
do {
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
urls.add(url);
} while (c.moveToNext());
if (urls.size() > 0) {
Bundle bundle = new Bundle();
bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
}
}
break;
}
}
mListAdapter.swapCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
final int loaderId = loader.getId();
switch(loaderId) {
case LOADER_ID_BOOKMARKS_LIST: {
if (mList != null) {
mListAdapter.swapCursor(null);
}
break;
}
case LOADER_ID_TOP_BOOKMARKS: {
if (mTopBookmarks != null) {
mTopBookmarksAdapter.swapCursor(null);
break;
}
}
}
}
}
/**
* An AsyncTaskLoader to load the thumbnails from a cursor.
*/
private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Thumbnail>> {
private Map<String, Thumbnail> mThumbnails;
private ArrayList<String> mUrls;
public ThumbnailsLoader(Context context, ArrayList<String> urls) {
super(context);
mUrls = urls;
}
@Override
public Map<String, Thumbnail> loadInBackground() {
if (mUrls == null || mUrls.size() == 0) {
return null;
}
final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
// Query the DB for thumbnails.
final ContentResolver cr = getContext().getContentResolver();
final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls);
try {
if (cursor != null && cursor.moveToFirst()) {
do {
// Try to get the thumbnail, if cursor is valid.
String url = cursor.getString(cursor.getColumnIndexOrThrow(Thumbnails.URL));
final byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(Thumbnails.DATA));
final Bitmap bitmap = (b == null ? null : BitmapUtils.decodeByteArray(b));
if (bitmap != null) {
thumbnails.put(url, new Thumbnail(bitmap, true));
}
} while (cursor.moveToNext());
}
} finally {
if (cursor != null) {
cursor.close();
}
}
// Query the DB for favicons for the urls without thumbnails.
for (String url : mUrls) {
if (!thumbnails.containsKey(url)) {
final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url);
if (bitmap != null) {
// Favicons.scaleImage can return several different size favicons,
// but will at least prevent this from being too large.
thumbnails.put(url, new Thumbnail(Favicons.scaleImage(bitmap), false));
}
}
}
return thumbnails;
}
@Override
public void deliverResult(Map<String, Thumbnail> thumbnails) {
if (isReset()) {
mThumbnails = null;
return;
}
mThumbnails = thumbnails;
if (isStarted()) {
super.deliverResult(thumbnails);
}
}
@Override
protected void onStartLoading() {
if (mThumbnails != null) {
deliverResult(mThumbnails);
}
if (takeContentChanged() || mThumbnails == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
public void onCanceled(Map<String, Thumbnail> thumbnails) {
mThumbnails = null;
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped.
onStopLoading();
mThumbnails = null;
}
}
/**
* Loader callbacks for the thumbnails on TopBookmarksView.
*/
private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Thumbnail>> {
@Override
public Loader<Map<String, Thumbnail>> onCreateLoader(int id, Bundle args) {
return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
}
@Override
public void onLoadFinished(Loader<Map<String, Thumbnail>> loader, Map<String, Thumbnail> thumbnails) {
if (mTopBookmarksAdapter != null) {
mTopBookmarksAdapter.updateThumbnails(thumbnails);
}
}
@Override
public void onLoaderReset(Loader<Map<String, Thumbnail>> loader) {
if (mTopBookmarksAdapter != null) {
mTopBookmarksAdapter.updateThumbnails(null);
if (mList != null) {
mListAdapter.swapCursor(null);
}
}
}

View File

@ -10,6 +10,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.TextView;
@ -24,6 +25,9 @@ public class FadedTextView extends TextView {
// Width of the fade effect from end of the view.
private int mFadeWidth;
// Padding for compound drawables.
private int mCompoundPadding;
public FadedTextView(Context context) {
this(context, null);
}
@ -38,6 +42,8 @@ public class FadedTextView extends TextView {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadedTextView);
mFadeWidth = a.getDimensionPixelSize(R.styleable.FadedTextView_fadeWidth, 0);
a.recycle();
mCompoundPadding = getCompoundDrawablePadding();
}
/**
@ -50,11 +56,18 @@ public class FadedTextView extends TextView {
// Layout doesn't return a proper width for getWidth().
// Instead check the width of the first line, as we've restricted to just one line.
if (getLayout().getLineWidth(0) > width) {
final Drawable leftDrawable = getCompoundDrawables()[0];
int drawableWidth = 0;
if (leftDrawable != null) {
drawableWidth = leftDrawable.getIntrinsicWidth() + mCompoundPadding;
width -= drawableWidth;
}
int color = getCurrentTextColor();
float stop = ((float) (width - mFadeWidth) / (float) width);
LinearGradient gradient = new LinearGradient(0, 0, width, 0,
new int[] { color, color, 0x0 },
new float[] { 0, stop, 1.0f },
new float[] { 0, stop, 1.0f - (drawableWidth / width) },
Shader.TileMode.CLAMP);
getPaint().setShader(gradient);
} else {

View File

@ -25,7 +25,7 @@ public class HistoryPage extends HomeFragment
private static final String LOGTAG = "GeckoHistoryPage";
private IconTabWidget mTabWidget;
private int mSelectedTab;
private boolean initializeVisitedPage;
private boolean initializeRecentPage;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -38,7 +38,6 @@ public class HistoryPage extends HomeFragment
mTabWidget = (IconTabWidget) view.findViewById(R.id.tab_icon_widget);
mTabWidget.addTab(R.drawable.icon_most_visited, R.string.home_most_visited_title);
mTabWidget.addTab(R.drawable.icon_most_recent, R.string.home_most_recent_title);
mTabWidget.addTab(R.drawable.icon_last_tabs, R.string.home_last_tabs_title);
@ -50,11 +49,11 @@ public class HistoryPage extends HomeFragment
@Override
public void load() {
// Show most visited page as the initial page.
// Show most recent page as the initial page.
// Since we detach/attach on config change, this prevents from replacing current fragment.
if (!initializeVisitedPage) {
showMostVisitedPage();
initializeVisitedPage = true;
if (!initializeRecentPage) {
showMostRecentPage();
initializeRecentPage = true;
}
}
@ -65,10 +64,8 @@ public class HistoryPage extends HomeFragment
}
if (index == 0) {
showMostVisitedPage();
} else if (index == 1) {
showMostRecentPage();
} else if (index == 2) {
} else if (index == 1) {
showLastTabsPage();
}
@ -95,15 +92,10 @@ public class HistoryPage extends HomeFragment
subPage.setArguments(args);
getChildFragmentManager().beginTransaction()
.addToBackStack(null).replace(R.id.visited_page_container, subPage)
.addToBackStack(null).replace(R.id.history_page_container, subPage)
.commitAllowingStateLoss();
}
private void showMostVisitedPage() {
final MostVisitedPage mostVisitedPage = MostVisitedPage.newInstance();
showSubPage(mostVisitedPage);
}
private void showMostRecentPage() {
final MostRecentPage mostRecentPage = MostRecentPage.newInstance();
showSubPage(mostRecentPage);

View File

@ -8,6 +8,7 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.util.HardwareUtils;
import android.content.Context;
import android.os.Build;
@ -38,6 +39,7 @@ public class HomePager extends ViewPager {
// List of pages in order.
public enum Page {
HISTORY,
TOP_SITES,
BOOKMARKS,
READING_LIST
}
@ -47,7 +49,7 @@ public class HomePager extends ViewPager {
static final String LIST_TAG_HISTORY = "history";
static final String LIST_TAG_BOOKMARKS = "bookmarks";
static final String LIST_TAG_READING_LIST = "reading_list";
static final String LIST_TAG_MOST_VISITED = "most_visited";
static final String LIST_TAG_TOP_SITES = "top_sites";
static final String LIST_TAG_MOST_RECENT = "most_recent";
static final String LIST_TAG_LAST_TABS = "last_tabs";
@ -90,9 +92,9 @@ public class HomePager extends ViewPager {
super(context, attrs);
mContext = context;
// This is to keep all 3 pages in memory after they are
// This is to keep all 4 pages in memory after they are
// selected in the pager.
setOffscreenPageLimit(2);
setOffscreenPageLimit(3);
}
@Override
@ -131,14 +133,19 @@ public class HomePager extends ViewPager {
// Only animate on post-HC devices, when a non-null animator is given
final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
// Add the pages to the adapter in order.
adapter.addTab(Page.HISTORY, HistoryPage.class, new Bundle(),
getContext().getString(R.string.home_history_title));
adapter.addTab(Page.TOP_SITES, TopSitesPage.class, new Bundle(),
getContext().getString(R.string.home_top_sites_title));
adapter.addTab(Page.BOOKMARKS, BookmarksPage.class, new Bundle(),
getContext().getString(R.string.bookmarks_title));
adapter.addTab(Page.READING_LIST, ReadingListPage.class, new Bundle(),
getContext().getString(R.string.reading_list_title));
// On phones, the history tab is the first tab. On tablets, the
// history tab is the last tab.
adapter.addTab(HardwareUtils.isTablet() ? -1 : 0,
Page.HISTORY, HistoryPage.class, new Bundle(),
getContext().getString(R.string.home_history_title));
adapter.setCanLoadHint(!shouldAnimate);
setAdapter(adapter);
@ -226,8 +233,18 @@ public class HomePager extends ViewPager {
}
public void addTab(Page page, Class<?> clss, Bundle args, String title) {
addTab(-1, page, clss, args, title);
}
public void addTab(int index, Page page, Class<?> clss, Bundle args, String title) {
TabInfo info = new TabInfo(page, clss, args, title);
mTabs.add(info);
if (index >= 0) {
mTabs.add(index, info);
} else {
mTabs.add(info);
}
notifyDataSetChanged();
if (mDecor != null) {

View File

@ -1,225 +0,0 @@
/* -*- 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.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.EnumSet;
/**
* Fragment that displays frecency search results in a ListView.
*/
public class MostVisitedPage extends HomeFragment {
// Logging tag name
private static final String LOGTAG = "GeckoMostVisitedPage";
// Cursor loader ID for search query
private static final int LOADER_ID_FRECENCY = 0;
// Adapter for the list of search results
private VisitedAdapter mAdapter;
// The view shown by the fragment.
private ListView mList;
// The title for this HomeFragment page.
private TextView mTitle;
// Reference to the View to display when there are no results.
private View mEmptyView;
// Callbacks used for the search and favicon cursor loaders
private CursorLoaderCallbacks mCursorLoaderCallbacks;
// On URL open listener
private OnUrlOpenListener mUrlOpenListener;
public static MostVisitedPage newInstance() {
return new MostVisitedPage();
}
public MostVisitedPage() {
mUrlOpenListener = null;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mUrlOpenListener = (OnUrlOpenListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement HomePager.OnUrlOpenListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mUrlOpenListener = null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.home_most_visited_page, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mTitle = (TextView) view.findViewById(R.id.title);
if (mTitle != null) {
mTitle.setText(R.string.home_most_visited_title);
}
mList = (HomeListView) view.findViewById(R.id.list);
mList.setTag(HomePager.LIST_TAG_MOST_VISITED);
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Cursor c = mAdapter.getCursor();
if (c == null || !c.moveToPosition(position)) {
return;
}
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
// This item is a TwoLinePageRow, so we allow switch-to-tab.
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
}
});
registerForContextMenu(mList);
}
@Override
public void onDestroyView() {
super.onDestroyView();
mList = null;
mTitle = null;
mEmptyView = null;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Intialize the search adapter
mAdapter = new VisitedAdapter(getActivity(), null);
mList.setAdapter(mAdapter);
// Create callbacks before the initial loader is started
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
loadIfVisible();
}
@Override
protected void load() {
getLoaderManager().initLoader(LOADER_ID_FRECENCY, null, mCursorLoaderCallbacks);
}
private void updateUiFromCursor(Cursor c) {
if (c != null && c.getCount() > 0) {
if (mTitle != null) {
mTitle.setVisibility(View.VISIBLE);
}
return;
}
// Cursor is empty, so hide the title and set the
// empty view if it hasn't been set already.
if (mTitle != null) {
mTitle.setVisibility(View.GONE);
}
if (mEmptyView == null) {
// Set empty page view. We delay this so that the empty view won't flash.
ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
mEmptyView = emptyViewStub.inflate();
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
emptyIcon.setImageResource(R.drawable.icon_most_visited_empty);
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
emptyText.setText(R.string.home_most_visited_empty);
mList.setEmptyView(mEmptyView);
}
}
private static class FrecencyCursorLoader extends SimpleCursorLoader {
// Max number of search results
private static final int SEARCH_LIMIT = 50;
public FrecencyCursorLoader(Context context) {
super(context);
}
@Override
public Cursor loadCursor() {
final ContentResolver cr = getContext().getContentResolver();
return BrowserDB.filter(cr, "", SEARCH_LIMIT);
}
}
private class VisitedAdapter extends CursorAdapter {
public VisitedAdapter(Context context, Cursor cursor) {
super(context, cursor);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
final TwoLinePageRow row = (TwoLinePageRow) view;
row.updateFromCursor(cursor);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(parent.getContext()).inflate(R.layout.home_item_row, parent, false);
}
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new FrecencyCursorLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
mAdapter.swapCursor(c);
updateUiFromCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
}
}

View File

@ -28,12 +28,12 @@ import android.widget.EditText;
import android.widget.ListView;
/**
* Dialog fragment that displays frecency search results, for pinning as a bookmark, in a ListView.
* Dialog fragment that displays frecency search results, for pinning a site, in a GridView.
*/
class PinBookmarkDialog extends DialogFragment {
class PinSiteDialog extends DialogFragment {
// Listener for url selection
public static interface OnBookmarkSelectedListener {
public void onBookmarkSelected(String url, String title);
public static interface OnSiteSelectedListener {
public void onSiteSelected(String url, String title);
}
// Cursor loader ID for search query
@ -55,13 +55,13 @@ class PinBookmarkDialog extends DialogFragment {
private CursorLoaderCallbacks mLoaderCallbacks;
// Bookmark selected listener
private OnBookmarkSelectedListener mOnBookmarkSelectedListener;
private OnSiteSelectedListener mOnSiteSelectedListener;
public static PinBookmarkDialog newInstance() {
return new PinBookmarkDialog();
public static PinSiteDialog newInstance() {
return new PinSiteDialog();
}
private PinBookmarkDialog() {
private PinSiteDialog() {
}
@Override
@ -75,7 +75,7 @@ class PinBookmarkDialog extends DialogFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle 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.
return inflater.inflate(R.layout.pin_bookmark_dialog, container, false);
return inflater.inflate(R.layout.pin_site_dialog, container, false);
}
@Override
@ -102,7 +102,7 @@ class PinBookmarkDialog extends DialogFragment {
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mOnBookmarkSelectedListener != null) {
if (mOnSiteSelectedListener != null) {
final Cursor c = mAdapter.getCursor();
if (c == null || !c.moveToPosition(position)) {
return;
@ -110,7 +110,7 @@ class PinBookmarkDialog extends DialogFragment {
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
final String title = c.getString(c.getColumnIndexOrThrow(URLColumns.TITLE));
mOnBookmarkSelectedListener.onBookmarkSelected(url, title);
mOnSiteSelectedListener.onSiteSelected(url, title);
}
// Dismiss the fragment and the dialog.
@ -151,8 +151,8 @@ class PinBookmarkDialog extends DialogFragment {
SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mLoaderCallbacks, mSearchTerm);
}
public void setOnBookmarkSelectedListener(OnBookmarkSelectedListener listener) {
mOnBookmarkSelectedListener = listener;
public void setOnSiteSelectedListener(OnSiteSelectedListener listener) {
mOnSiteSelectedListener = listener;
}
private static class SearchAdapter extends CursorAdapter {

View File

@ -1,113 +0,0 @@
/* -*- 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.db.BrowserDB.TopSitesCursorWrapper;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.support.v4.widget.CursorAdapter;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import java.util.Map;
/**
* A cursor adapter holding the pinned and top bookmarks.
*/
public class TopBookmarksAdapter extends CursorAdapter {
// Cache to store the thumbnails.
private Map<String, Thumbnail> mThumbnails;
/**
* Class to hold the bitmap of cached thumbnails/favicons.
*/
public static class Thumbnail {
// Thumbnail or favicon.
private final boolean isThumbnail;
// Bitmap of thumbnail/favicon.
private final Bitmap bitmap;
public Thumbnail(Bitmap bitmap, boolean isThumbnail) {
this.bitmap = bitmap;
this.isThumbnail = isThumbnail;
}
}
public TopBookmarksAdapter(Context context, Cursor cursor) {
super(context, cursor);
}
/**
* {@inheritDoc}
*/
@Override
protected void onContentChanged() {
// Don't do anything. We don't want to regenerate every time
// our database is updated.
return;
}
/**
* Update the thumbnails returned by the db.
*
* @param thumbnails A map of urls and their thumbnail bitmaps.
*/
public void updateThumbnails(Map<String, Thumbnail> thumbnails) {
mThumbnails = thumbnails;
notifyDataSetChanged();
}
/**
* {@inheritDoc}
*/
@Override
public void bindView(View bindView, Context context, Cursor cursor) {
String url = "";
String title = "";
boolean pinned = false;
// Cursor is already moved to required position.
if (!cursor.isAfterLast()) {
url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
pinned = ((TopSitesCursorWrapper) cursor).isPinned();
}
TopBookmarkItemView view = (TopBookmarkItemView) bindView;
view.setTitle(title);
view.setUrl(url);
view.setPinned(pinned);
// If there is no url, then show "add bookmark".
if (TextUtils.isEmpty(url)) {
view.displayThumbnail(R.drawable.top_bookmark_add);
} else {
// Show the thumbnail.
Thumbnail thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null);
if (thumbnail == null) {
view.displayThumbnail(null);
} else if (thumbnail.isThumbnail) {
view.displayThumbnail(thumbnail.bitmap);
} else {
view.displayFavicon(thumbnail.bitmap);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new TopBookmarkItemView(context);
}
}

View File

@ -10,38 +10,29 @@ import org.mozilla.gecko.R;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.PathShape;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.ImageView.ScaleType;
/**
* A view that displays the thumbnail and the title/url for a bookmark.
* A view that displays the thumbnail and the title/url for a top/pinned site.
* If the title/url is longer than the width of the view, they are faded out.
* If there is no valid url, a default string is shown at 50% opacity.
* This is denoted by the empty state.
*/
public class TopBookmarkItemView extends RelativeLayout {
private static final String LOGTAG = "GeckoTopBookmarkItemView";
public class TopSitesGridItemView extends RelativeLayout {
private static final String LOGTAG = "GeckoTopSitesGridItemView";
// Empty state, to denote there is no valid url.
private static final int[] STATE_EMPTY = { android.R.attr.state_empty };
// A Pin Drawable to denote pinned sites.
private static Drawable sPinDrawable = null;
// Child views.
private final TextView mTitleView;
private final ImageView mThumbnailView;
private final ImageView mPinView;
// Data backing this view.
private String mTitle;
@ -53,22 +44,21 @@ public class TopBookmarkItemView extends RelativeLayout {
// Empty state.
private boolean mIsEmpty = true;
public TopBookmarkItemView(Context context) {
public TopSitesGridItemView(Context context) {
this(context, null);
}
public TopBookmarkItemView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.topBookmarkItemViewStyle);
public TopSitesGridItemView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.topSitesGridItemViewStyle);
}
public TopBookmarkItemView(Context context, AttributeSet attrs, int defStyle) {
public TopSitesGridItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater.from(context).inflate(R.layout.top_bookmark_item_view, this);
LayoutInflater.from(context).inflate(R.layout.top_sites_grid_item_view, this);
mTitleView = (TextView) findViewById(R.id.title);
mThumbnailView = (ImageView) findViewById(R.id.thumbnail);
mPinView = (ImageView) findViewById(R.id.pin);
}
/**
@ -135,7 +125,7 @@ public class TopBookmarkItemView extends RelativeLayout {
*/
public void setPinned(boolean pinned) {
mIsPinned = pinned;
mPinView.setBackgroundDrawable(pinned ? getPinDrawable() : null);
mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0);
}
/**
@ -200,26 +190,4 @@ public class TopBookmarkItemView extends RelativeLayout {
// Refresh for state change.
refreshDrawableState();
}
/**
* @return Drawable to be used as a pin.
*/
private Drawable getPinDrawable() {
if (sPinDrawable == null) {
int size = getResources().getDimensionPixelSize(R.dimen.top_bookmark_pinsize);
// Draw a little triangle in the upper right corner.
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(size, 0);
path.lineTo(size, size);
path.close();
sPinDrawable = new ShapeDrawable(new PathShape(path, size, size));
Paint p = ((ShapeDrawable) sPinDrawable).getPaint();
p.setColor(getResources().getColor(R.color.top_bookmark_pin));
}
return sPinDrawable;
}
}

View File

@ -26,19 +26,19 @@ import android.widget.GridView;
import java.util.EnumSet;
/**
* A grid view of top bookmarks and pinned tabs.
* Each cell in the grid is a TopBookmarkItemView.
* A grid view of top and pinned sites.
* Each cell in the grid is a TopSitesGridItemView.
*/
public class TopBookmarksView extends GridView {
private static final String LOGTAG = "GeckoTopBookmarksView";
public class TopSitesGridView extends GridView {
private static final String LOGTAG = "GeckoTopSitesGridView";
// Listener for pinning bookmarks.
public static interface OnPinBookmarkListener {
public void onPinBookmark(int position);
// Listener for pinning sites.
public static interface OnPinSiteListener {
public void onPinSite(int position);
}
// Max number of bookmarks that needs to be shown.
private final int mMaxBookmarks;
// Max number of top sites that needs to be shown.
private final int mMaxSites;
// Number of columns to show.
private final int mNumColumns;
@ -58,29 +58,29 @@ public class TopBookmarksView extends GridView {
// On URL open listener.
private OnUrlOpenListener mUrlOpenListener;
// Pin bookmark listener.
private OnPinBookmarkListener mPinBookmarkListener;
// Pin site listener.
private OnPinSiteListener mPinSiteListener;
// Context menu info.
private TopBookmarksContextMenuInfo mContextMenuInfo;
private TopSitesGridContextMenuInfo mContextMenuInfo;
public TopBookmarksView(Context context) {
public TopSitesGridView(Context context) {
this(context, null);
}
public TopBookmarksView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.topBookmarksViewStyle);
public TopSitesGridView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.topSitesGridViewStyle);
}
public TopBookmarksView(Context context, AttributeSet attrs, int defStyle) {
public TopSitesGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mMaxBookmarks = getResources().getInteger(R.integer.number_of_top_sites);
mMaxSites = getResources().getInteger(R.integer.number_of_top_sites);
mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols);
setNumColumns(mNumColumns);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopBookmarksView, defStyle, 0);
mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopBookmarksView_android_horizontalSpacing, 0x00);
mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopBookmarksView_android_verticalSpacing, 0x00);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopSitesGridView, defStyle, 0);
mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_horizontalSpacing, 0x00);
mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_verticalSpacing, 0x00);
a.recycle();
}
@ -94,18 +94,18 @@ public class TopBookmarksView extends GridView {
setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TopBookmarkItemView row = (TopBookmarkItemView) view;
TopSitesGridItemView row = (TopSitesGridItemView) view;
String url = row.getUrl();
// If the url is empty, the user can pin a bookmark.
// If the url is empty, the user can pin a site.
// If not, navigate to the page given by the url.
if (!TextUtils.isEmpty(url)) {
if (mUrlOpenListener != null) {
mUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
}
} else {
if (mPinBookmarkListener != null) {
mPinBookmarkListener.onPinBookmark(position);
if (mPinSiteListener != null) {
mPinSiteListener.onPinSite(position);
}
}
}
@ -115,8 +115,8 @@ public class TopBookmarksView extends GridView {
@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);
mContextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id, cursor);
return showContextMenuForChild(TopSitesGridView.this);
}
});
}
@ -126,7 +126,7 @@ public class TopBookmarksView extends GridView {
super.onDetachedFromWindow();
mUrlOpenListener = null;
mPinBookmarkListener = null;
mPinSiteListener = null;
}
/**
@ -161,7 +161,7 @@ public class TopBookmarksView extends GridView {
ThumbnailHelper.getInstance().setThumbnailWidth(childWidth);
// Get the first child from the adapter.
final View child = new TopBookmarkItemView(getContext());
final View child = new TopSitesGridItemView(getContext());
// Set a default LayoutParams on the child, if it doesn't have one on its own.
AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams();
@ -172,14 +172,14 @@ public class TopBookmarksView extends GridView {
}
// Measure the exact width of the child, and the height based on the width.
// Note: the child (and BookmarkThumbnailView) takes care of calculating its height.
// Note: the child (and TopSitesThumbnailView) takes care of calculating its height.
int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(childWidthSpec, childHeightSpec);
final int childHeight = child.getMeasuredHeight();
// Number of rows required to show these bookmarks.
final int rows = (int) Math.ceil((double) mMaxBookmarks / mNumColumns);
// Number of rows required to show these top sites.
final int rows = (int) Math.ceil((double) mMaxSites / mNumColumns);
final int childrenHeight = childHeight * rows;
final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0;
@ -205,24 +205,24 @@ public class TopBookmarksView extends GridView {
}
/**
* Set a pin bookmark listener to be used by this view.
* Set a pin site listener to be used by this view.
*
* @param listener A pin bookmark listener for this view.
* @param listener A pin site listener for this view.
*/
public void setOnPinBookmarkListener(OnPinBookmarkListener listener) {
mPinBookmarkListener = listener;
public void setOnPinSiteListener(OnPinSiteListener listener) {
mPinSiteListener = listener;
}
/**
* A ContextMenuInfo for TopBoomarksView that adds details from the cursor.
*/
public static class TopBookmarksContextMenuInfo extends AdapterContextMenuInfo {
public static class TopSitesGridContextMenuInfo extends AdapterContextMenuInfo {
public String url;
public String title;
public boolean isPinned;
public TopBookmarksContextMenuInfo(View targetView, int position, long id, Cursor cursor) {
public TopSitesGridContextMenuInfo(View targetView, int position, long id, Cursor cursor) {
super(targetView, position, id);
if (cursor == null) {

View File

@ -0,0 +1,768 @@
/* -*- 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.Favicons;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.PropertyAnimator.Property;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener;
import org.mozilla.gecko.home.TopSitesGridView.OnPinSiteListener;
import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
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.support.v4.widget.CursorAdapter;
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.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.EnumSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Fragment that displays frecency search results in a ListView.
*/
public class TopSitesPage extends HomeFragment {
// Logging tag name
private static final String LOGTAG = "GeckoTopSitesPage";
// Cursor loader ID for the top sites
private static final int LOADER_ID_TOP_SITES = 0;
// Loader ID for thumbnails
private static final int LOADER_ID_THUMBNAILS = 1;
// Key for thumbnail urls
private static final String THUMBNAILS_URLS_KEY = "urls";
// Adapter for the list of top sites
private VisitedAdapter mListAdapter;
// Adapter for the grid of top sites
private TopSitesGridAdapter mGridAdapter;
// List of top sites
private ListView mList;
// Grid of top sites
private TopSitesGridView mGrid;
// Reference to the View to display when there are no results.
private View mEmptyView;
// Banner to show snippets.
private HomeBanner mBanner;
// Raw Y value of the last event that happened on the list view.
private float mListTouchY = -1;
// Scrolling direction of the banner.
private boolean mSnapBannerToTop;
// Callbacks used for the search and favicon cursor loaders
private CursorLoaderCallbacks mCursorLoaderCallbacks;
// Callback for thumbnail loader
private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks;
// Listener for pinning sites
private PinSiteListener mPinSiteListener;
// On URL open listener
private OnUrlOpenListener mUrlOpenListener;
// Max number of entries shown in the grid from the cursor.
private int mMaxGridEntries;
/**
* Class to hold the bitmap of cached thumbnails/favicons.
*/
public static class Thumbnail {
// Thumbnail or favicon.
private final boolean isThumbnail;
// Bitmap of thumbnail/favicon.
private final Bitmap bitmap;
public Thumbnail(Bitmap bitmap, boolean isThumbnail) {
this.bitmap = bitmap;
this.isThumbnail = isThumbnail;
}
}
public static TopSitesPage newInstance() {
return new TopSitesPage();
}
public TopSitesPage() {
mUrlOpenListener = null;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mMaxGridEntries = activity.getResources().getInteger(R.integer.number_of_top_sites);
try {
mUrlOpenListener = (OnUrlOpenListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement HomePager.OnUrlOpenListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mUrlOpenListener = null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.home_top_sites_page, container, false);
mList = (HomeListView) view.findViewById(R.id.list);
mGrid = new TopSitesGridView(getActivity());
mList.addHeaderView(mGrid);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mPinSiteListener = new PinSiteListener();
mList.setTag(HomePager.LIST_TAG_TOP_SITES);
mList.setHeaderDividersEnabled(false);
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final ListView list = (ListView) parent;
final int headerCount = list.getHeaderViewsCount();
if (position < headerCount) {
// The click is on a header, don't do anything.
return;
}
// Absolute position for the adapter.
position += (mGridAdapter.getCount() - headerCount);
final Cursor c = mListAdapter.getCursor();
if (c == null || !c.moveToPosition(position)) {
return;
}
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
// This item is a TwoLinePageRow, so we allow switch-to-tab.
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
}
});
mGrid.setOnUrlOpenListener(mUrlOpenListener);
mGrid.setOnPinSiteListener(mPinSiteListener);
registerForContextMenu(mList);
registerForContextMenu(mGrid);
mBanner = (HomeBanner) view.findViewById(R.id.home_banner);
mList.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
TopSitesPage.this.handleListTouchEvent(event);
return false;
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
mList = null;
mGrid = null;
mEmptyView = null;
mListAdapter = null;
mGridAdapter = null;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Detach and reattach the fragment as the layout changes.
if (isVisible()) {
getFragmentManager().beginTransaction()
.detach(this)
.attach(this)
.commitAllowingStateLoss();
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final Activity activity = getActivity();
// Setup the top sites grid adapter.
mGridAdapter = new TopSitesGridAdapter(activity, null);
mGrid.setAdapter(mGridAdapter);
// Setup the top sites list adapter.
mListAdapter = new VisitedAdapter(activity, null);
mList.setAdapter(mListAdapter);
// Create callbacks before the initial loader is started
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
loadIfVisible();
}
@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 TopSitesGridContextMenuInfo)) {
return;
}
MenuInflater inflater = new MenuInflater(view.getContext());
inflater.inflate(R.menu.top_sites_contextmenu, menu);
TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
menu.setHeaderTitle(info.getDisplayTitle());
if (!TextUtils.isEmpty(info.url)) {
if (info.isPinned) {
menu.findItem(R.id.top_sites_pin).setVisible(false);
} else {
menu.findItem(R.id.top_sites_unpin).setVisible(false);
}
} else {
menu.findItem(R.id.top_sites_open_new_tab).setVisible(false);
menu.findItem(R.id.top_sites_open_private_tab).setVisible(false);
menu.findItem(R.id.top_sites_pin).setVisible(false);
menu.findItem(R.id.top_sites_unpin).setVisible(false);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
ContextMenuInfo menuInfo = item.getMenuInfo();
// HomeFragment will handle the default case.
if (menuInfo == null || !(menuInfo instanceof TopSitesGridContextMenuInfo)) {
return false;
}
TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
final Activity activity = getActivity();
final int itemId = item.getItemId();
if (itemId == R.id.top_sites_open_new_tab || itemId == R.id.top_sites_open_private_tab) {
if (info.url == null) {
Log.e(LOGTAG, "Can't open in new tab because URL is null");
return false;
}
int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
if (item.getItemId() == R.id.top_sites_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;
}
if (itemId == R.id.top_sites_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;
}
if (itemId == R.id.top_sites_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;
}
if (itemId == R.id.top_sites_edit) {
mPinSiteListener.onPinSite(info.position);
return true;
}
return false;
}
@Override
protected void load() {
getLoaderManager().initLoader(LOADER_ID_TOP_SITES, null, mCursorLoaderCallbacks);
}
/**
* Listener for pinning sites.
*/
private class PinSiteListener implements OnPinSiteListener,
OnSiteSelectedListener {
// Tag for the PinSiteDialog fragment.
private static final String TAG_PIN_SITE = "pin_site";
// Position of the pin.
private int mPosition;
@Override
public void onPinSite(int position) {
mPosition = position;
final FragmentManager manager = getActivity().getSupportFragmentManager();
PinSiteDialog dialog = (PinSiteDialog) manager.findFragmentByTag(TAG_PIN_SITE);
if (dialog == null) {
dialog = PinSiteDialog.newInstance();
}
dialog.setOnSiteSelectedListener(this);
dialog.show(manager, TAG_PIN_SITE);
}
@Override
public void onSiteSelected(final String url, final String title) {
final int position = mPosition;
final Context context = getActivity().getApplicationContext();
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
BrowserDB.pinSite(context.getContentResolver(), url, title, position);
}
});
}
}
private void handleListTouchEvent(MotionEvent event) {
// Ignore the event if the banner is hidden for this session.
if (mBanner.isDismissed()) {
return;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mListTouchY = event.getRawY();
break;
}
case MotionEvent.ACTION_MOVE: {
// There is a chance that we won't receive ACTION_DOWN, if the touch event
// actually started on the Grid instead of the List. Treat this as first event.
if (mListTouchY == -1) {
mListTouchY = event.getRawY();
return;
}
final float curY = event.getRawY();
final float delta = mListTouchY - curY;
mSnapBannerToTop = (delta > 0.0f) ? false : true;
final float height = mBanner.getHeight();
float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta;
// Clamp the values to be between 0 and height.
if (newTranslationY < 0.0f) {
newTranslationY = 0.0f;
} else if (newTranslationY > height) {
newTranslationY = height;
}
ViewHelper.setTranslationY(mBanner, newTranslationY);
mListTouchY = curY;
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
mListTouchY = -1;
final float y = ViewHelper.getTranslationY(mBanner);
final float height = mBanner.getHeight();
if (y > 0.0f && y < height) {
final PropertyAnimator animator = new PropertyAnimator(100);
animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height);
animator.start();
}
break;
}
}
}
private void updateUiFromCursor(Cursor c) {
mList.setHeaderDividersEnabled(c != null && c.getCount() > mMaxGridEntries);
if (c != null && c.getCount() > 0) {
return;
}
if (mEmptyView == null) {
// Set empty page view. We delay this so that the empty view won't flash.
ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
mEmptyView = emptyViewStub.inflate();
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
emptyIcon.setImageResource(R.drawable.icon_most_visited_empty);
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
emptyText.setText(R.string.home_most_visited_empty);
mList.setEmptyView(mEmptyView);
}
}
private static class TopSitesLoader extends SimpleCursorLoader {
// Max number of search results
private static final int SEARCH_LIMIT = 30;
private int mMaxGridEntries;
public TopSitesLoader(Context context) {
super(context);
mMaxGridEntries = context.getResources().getInteger(R.integer.number_of_top_sites);
}
@Override
public Cursor loadCursor() {
return BrowserDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT);
}
}
private class VisitedAdapter extends CursorAdapter {
public VisitedAdapter(Context context, Cursor cursor) {
super(context, cursor);
}
@Override
public int getCount() {
return Math.max(0, super.getCount() - mMaxGridEntries);
}
@Override
public Object getItem(int position) {
return super.getItem(position + mMaxGridEntries);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
final int position = cursor.getPosition();
cursor.moveToPosition(position + mMaxGridEntries);
final TwoLinePageRow row = (TwoLinePageRow) view;
row.updateFromCursor(cursor);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.bookmark_item_row, parent, false);
}
}
public class TopSitesGridAdapter extends CursorAdapter {
// Cache to store the thumbnails.
private Map<String, Thumbnail> mThumbnails;
public TopSitesGridAdapter(Context context, Cursor cursor) {
super(context, cursor);
}
@Override
public int getCount() {
return Math.min(mMaxGridEntries, super.getCount());
}
@Override
protected void onContentChanged() {
// Don't do anything. We don't want to regenerate every time
// our database is updated.
return;
}
/**
* Update the thumbnails returned by the db.
*
* @param thumbnails A map of urls and their thumbnail bitmaps.
*/
public void updateThumbnails(Map<String, Thumbnail> thumbnails) {
mThumbnails = thumbnails;
notifyDataSetChanged();
}
@Override
public void bindView(View bindView, Context context, Cursor cursor) {
String url = "";
String title = "";
boolean pinned = false;
// Cursor is already moved to required position.
if (!cursor.isAfterLast()) {
url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
pinned = ((TopSitesCursorWrapper) cursor).isPinned();
}
TopSitesGridItemView view = (TopSitesGridItemView) bindView;
view.setTitle(title);
view.setUrl(url);
view.setPinned(pinned);
// If there is no url, then show "add bookmark".
if (TextUtils.isEmpty(url)) {
view.displayThumbnail(R.drawable.top_site_add);
} else {
// Show the thumbnail.
Thumbnail thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null);
if (thumbnail == null) {
view.displayThumbnail(null);
} else if (thumbnail.isThumbnail) {
view.displayThumbnail(thumbnail.bitmap);
} else {
view.displayFavicon(thumbnail.bitmap);
}
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new TopSitesGridItemView(context);
}
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new TopSitesLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
mListAdapter.swapCursor(c);
mGridAdapter.swapCursor(c);
updateUiFromCursor(c);
// Load the thumbnails.
if (c.getCount() > 0 && c.moveToFirst()) {
final ArrayList<String> urls = new ArrayList<String>();
do {
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
urls.add(url);
} while (c.moveToNext());
if (urls.size() > 0) {
Bundle bundle = new Bundle();
bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
}
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (mListAdapter != null) {
mListAdapter.swapCursor(null);
}
if (mGridAdapter != null) {
mGridAdapter.swapCursor(null);
}
}
}
/**
* An AsyncTaskLoader to load the thumbnails from a cursor.
*/
private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Thumbnail>> {
private Map<String, Thumbnail> mThumbnails;
private ArrayList<String> mUrls;
public ThumbnailsLoader(Context context, ArrayList<String> urls) {
super(context);
mUrls = urls;
}
@Override
public Map<String, Thumbnail> loadInBackground() {
if (mUrls == null || mUrls.size() == 0) {
return null;
}
final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
// Query the DB for thumbnails.
final ContentResolver cr = getContext().getContentResolver();
final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls);
try {
if (cursor != null && cursor.moveToFirst()) {
do {
// Try to get the thumbnail, if cursor is valid.
String url = cursor.getString(cursor.getColumnIndexOrThrow(Thumbnails.URL));
final byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(Thumbnails.DATA));
final Bitmap bitmap = (b == null ? null : BitmapUtils.decodeByteArray(b));
if (bitmap != null) {
thumbnails.put(url, new Thumbnail(bitmap, true));
}
} while (cursor.moveToNext());
}
} finally {
if (cursor != null) {
cursor.close();
}
}
// Query the DB for favicons for the urls without thumbnails.
for (String url : mUrls) {
if (!thumbnails.containsKey(url)) {
final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url);
if (bitmap != null) {
// Favicons.scaleImage can return several different size favicons,
// but will at least prevent this from being too large.
thumbnails.put(url, new Thumbnail(Favicons.scaleImage(bitmap), false));
}
}
}
return thumbnails;
}
@Override
public void deliverResult(Map<String, Thumbnail> thumbnails) {
if (isReset()) {
mThumbnails = null;
return;
}
mThumbnails = thumbnails;
if (isStarted()) {
super.deliverResult(thumbnails);
}
}
@Override
protected void onStartLoading() {
if (mThumbnails != null) {
deliverResult(mThumbnails);
}
if (takeContentChanged() || mThumbnails == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
public void onCanceled(Map<String, Thumbnail> thumbnails) {
mThumbnails = null;
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped.
onStopLoading();
mThumbnails = null;
}
}
/**
* Loader callbacks for the thumbnails on TopSitesGridView.
*/
private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Thumbnail>> {
@Override
public Loader<Map<String, Thumbnail>> onCreateLoader(int id, Bundle args) {
return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
}
@Override
public void onLoadFinished(Loader<Map<String, Thumbnail>> loader, Map<String, Thumbnail> thumbnails) {
if (mGridAdapter != null) {
mGridAdapter.updateThumbnails(thumbnails);
}
}
@Override
public void onLoaderReset(Loader<Map<String, Thumbnail>> loader) {
if (mGridAdapter != null) {
mGridAdapter.updateThumbnails(null);
}
}
}
}

View File

@ -17,10 +17,10 @@ import android.util.AttributeSet;
import android.widget.ImageView;
/**
* A height constrained ImageView to show thumbnails of top bookmarks.
* A height constrained ImageView to show thumbnails of top and pinned sites.
*/
public class BookmarkThumbnailView extends ImageView {
private static final String LOGTAG = "GeckoBookmarkThumbnailView";
public class TopSitesThumbnailView extends ImageView {
private static final String LOGTAG = "GeckoTopSitesThumbnailView";
// 27.34% opacity filter for the dominant color.
private static final int COLOR_FILTER = 0x46FFFFFF;
@ -41,18 +41,18 @@ public class BookmarkThumbnailView extends ImageView {
sBorderPaint.setStyle(Paint.Style.STROKE);
}
public BookmarkThumbnailView(Context context) {
public TopSitesThumbnailView(Context context) {
this(context, null);
// A border will be drawn if needed.
setWillNotDraw(false);
}
public BookmarkThumbnailView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.bookmarkThumbnailViewStyle);
public TopSitesThumbnailView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.topSitesThumbnailViewStyle);
}
public BookmarkThumbnailView(Context context, AttributeSet attrs, int defStyle) {
public TopSitesThumbnailView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@ -95,7 +95,7 @@ public class BookmarkThumbnailView extends ImageView {
@Override
public void setBackgroundColor(int color) {
int colorFilter = color == 0 ? DEFAULT_COLOR : color & COLOR_FILTER;
Drawable drawable = getResources().getDrawable(R.drawable.bookmark_thumbnail_bg);
Drawable drawable = getResources().getDrawable(R.drawable.top_sites_thumbnail_bg);
drawable.setColorFilter(colorFilter, Mode.SRC_ATOP);
setBackgroundDrawable(drawable);
}

View File

@ -225,9 +225,9 @@ size. -->
<!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 contextmenu_top_sites_edit "Edit">
<!ENTITY contextmenu_top_sites_pin "Pin Site">
<!ENTITY contextmenu_top_sites_unpin "Unpin Site">
<!ENTITY pref_titlebar_mode "Title bar">
<!ENTITY pref_titlebar_mode_title "Show page title">
@ -270,6 +270,7 @@ size. -->
<!ENTITY button_set "Set">
<!ENTITY button_clear "Clear">
<!ENTITY home_top_sites_title "Top Sites">
<!ENTITY home_history_title "History">
<!ENTITY home_last_tabs_title "Tabs from last time">
<!ENTITY home_last_tabs_open "Open all tabs from last time">
@ -288,7 +289,7 @@ size. -->
<!ENTITY home_reading_list_hint_accessible "TIP: Save articles to your reading list by long pressing the reader mode button when it appears in the title bar.">
<!ENTITY home_most_visited_empty "Websites you visit most frequently show up here.">
<!ENTITY pin_bookmark_dialog_hint "Enter a search keyword">
<!ENTITY pin_site_dialog_hint "Enter a search keyword">
<!ENTITY filepicker_title "Choose File">
<!ENTITY filepicker_audio_title "Choose or record a sound">

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

View File

Before

Width:  |  Height:  |  Size: 151 B

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

View File

Before

Width:  |  Height:  |  Size: 137 B

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

View File

Before

Width:  |  Height:  |  Size: 156 B

After

Width:  |  Height:  |  Size: 156 B

View File

@ -6,7 +6,7 @@
<shape android:shape="rectangle" >
<stroke android:width="1px"
android:color="@color/doorhanger_divider_dark" />
android:color="@color/doorhanger_divider_light" />
<solid android:color="#00000000" />
</shape>

View File

@ -16,7 +16,7 @@
android:layout="@layout/home_history_tabs_indicator"
gecko:display="text"/>
<FrameLayout android:id="@+id/visited_page_container"
<FrameLayout android:id="@+id/history_page_container"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />

View File

@ -16,7 +16,7 @@
android:layout="@layout/home_history_tabs_indicator"
gecko:display="text"/>
<FrameLayout android:id="@+id/visited_page_container"
<FrameLayout android:id="@+id/history_page_container"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />

View File

@ -12,15 +12,4 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
style="@style/Widget.HomeBanner"
android:layout_width="fill_parent"
android:layout_height="@dimen/home_banner_height"
android:background="@drawable/home_banner"
android:layout_gravity="bottom"
android:gravity="center_vertical"
android:visibility="gone"
android:clickable="true"
android:focusable="true"/>
</FrameLayout>

View File

@ -11,7 +11,7 @@
android:layout_height="fill_parent"/>
<TextView android:id="@+id/title"
style="@style/Widget.Home.HistoryTabIndicator"
style="@style/Widget.Home.HistoryPageTitle"
android:visibility="gone"/>
<org.mozilla.gecko.home.HomeListView

View File

@ -8,7 +8,7 @@
android:layout_height="fill_parent"
android:orientation="vertical">
<FrameLayout android:id="@+id/visited_page_container"
<FrameLayout android:id="@+id/history_page_container"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" />

View File

@ -1,13 +0,0 @@
<?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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<include layout="@layout/home_history_list"/>
</LinearLayout>

View File

@ -0,0 +1,34 @@
<?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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ViewStub android:id="@+id/home_empty_view_stub"
android:layout="@layout/home_empty_page"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<org.mozilla.gecko.home.HomeListView
android:id="@+id/list"
style="@style/Widget.TopSitesListView"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
style="@style/Widget.HomeBanner"
android:layout_width="fill_parent"
android:layout_height="@dimen/home_banner_height"
android:background="@drawable/home_banner"
android:layout_gravity="bottom"
android:gravity="center_vertical"
android:visibility="gone"
android:clickable="true"
android:focusable="true"/>
</LinearLayout>

View File

@ -19,7 +19,7 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="6dip"
android:hint="@string/pin_bookmark_dialog_hint"
android:hint="@string/pin_site_dialog_hint"
android:background="@drawable/url_bar_entry"
android:textColor="@color/url_bar_title"
android:textColorHint="@color/url_bar_title_hint"

View File

@ -6,7 +6,7 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<org.mozilla.gecko.home.BookmarkThumbnailView
<org.mozilla.gecko.home.TopSitesThumbnailView
android:id="@+id/thumbnail"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
@ -14,18 +14,12 @@
<org.mozilla.gecko.home.FadedTextView
android:id="@+id/title"
style="@style/Widget.TopBookmarkItemTitle"
style="@style/Widget.TopSitesGridItemTitle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/thumbnail"
android:duplicateParentState="true"
android:drawablePadding="4dip"
gecko:fadeWidth="20dip"/>
<ImageView android:id="@+id/pin"
style="@style/Widget.TopBookmarkItemPin"
android:layout_width="@dimen/top_bookmark_pinsize"
android:layout_height="@dimen/top_bookmark_pinsize"
android:layout_alignTop="@id/thumbnail"
android:layout_alignRight="@id/thumbnail"/>
</merge>

View File

@ -5,19 +5,19 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/top_bookmarks_open_new_tab"
<item android:id="@+id/top_sites_open_new_tab"
android:title="@string/contextmenu_open_new_tab"/>
<item android:id="@+id/top_bookmarks_open_private_tab"
<item android:id="@+id/top_sites_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_sites_edit"
android:title="@string/contextmenu_top_sites_edit"/>
<item android:id="@+id/top_bookmarks_pin"
android:title="@string/contextmenu_top_bookmarks_pin"/>
<item android:id="@+id/top_sites_pin"
android:title="@string/contextmenu_top_sites_pin"/>
<item android:id="@+id/top_bookmarks_unpin"
android:title="@string/contextmenu_top_bookmarks_unpin"/>
<item android:id="@+id/top_sites_unpin"
android:title="@string/contextmenu_top_sites_unpin"/>
</menu>

View File

@ -21,15 +21,16 @@
</style>
<style name="Widget.BookmarksListView" parent="Widget.HomeListView">
<item name="android:paddingTop">30dp</item>
<item name="android:paddingLeft">120dp</item>
<item name="android:paddingRight">120dp</item>
<item name="android:scrollbarStyle">outsideOverlay</item>
<item name="topDivider">true</item>
</style>
<style name="Widget.TopBookmarksView" parent="Widget.GridView">
<style name="Widget.TopSitesGridView" parent="Widget.GridView">
<item name="android:paddingLeft">55dp</item>
<item name="android:paddingRight">55dp</item>
<item name="android:paddingTop">30dp</item>
<item name="android:paddingBottom">30dp</item>
<item name="android:horizontalSpacing">20dp</item>
<item name="android:verticalSpacing">20dp</item>

View File

@ -62,23 +62,36 @@
</style>
<style name="Widget.BookmarksListView" parent="Widget.HomeListView">
<item name="android:paddingTop">30dp</item>
<item name="android:paddingLeft">32dp</item>
<item name="android:paddingRight">32dp</item>
<item name="android:scrollbarStyle">outsideOverlay</item>
<item name="topDivider">true</item>
</style>
<style name="Widget.TopBookmarksView" parent="Widget.GridView">
<style name="Widget.TopSitesGridView" parent="Widget.GridView">
<item name="android:paddingLeft">5dp</item>
<item name="android:paddingRight">5dp</item>
<item name="android:paddingTop">30dp</item>
<item name="android:paddingBottom">30dp</item>
<item name="android:horizontalSpacing">10dp</item>
<item name="android:verticalSpacing">10dp</item>
</style>
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView">
<item name="android:paddingTop">30dp</item>
<item name="topDivider">true</item>
<style name="Widget.TopSitesListView" parent="Widget.BookmarksListView">
<item name="topDivider">false</item>
</style>
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView"/>
<style name="Widget.Home.HistoryPageTitle" parent="Widget.Home.HistoryTabIndicator">
<item name="android:layout_marginLeft">32dp</item>
<item name="android:layout_marginRight">32dp</item>
</style>
<style name="Widget.Home.HistoryListView">
<item name="android:paddingLeft">32dp</item>
<item name="android:paddingRight">32dp</item>
<item name="android:scrollbarStyle">outsideOverlay</item>
</style>
<style name="Widget.HomeBanner">

View File

@ -43,9 +43,9 @@
<item name="menuItemActionViewStyle">@style/Widget.MenuItemActionView</item>
<item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
<item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
<item name="bookmarkThumbnailViewStyle">@style/Widget.BookmarkThumbnailView</item>
<item name="topBookmarkItemViewStyle">@style/Widget.TopBookmarkItemView</item>
<item name="topBookmarksViewStyle">@style/Widget.TopBookmarksView</item>
<item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
<item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
<item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
<item name="homeListViewStyle">@style/Widget.HomeListView</item>
</style>

View File

@ -5,10 +5,9 @@
<resources>
<style name="Widget.TopBookmarksView" parent="Widget.GridView">
<style name="Widget.TopSitesGridView" parent="Widget.GridView">
<item name="android:paddingLeft">55dp</item>
<item name="android:paddingRight">55dp</item>
<item name="android:paddingTop">30dp</item>
<item name="android:paddingBottom">30dp</item>
<item name="android:horizontalSpacing">56dp</item>
<item name="android:verticalSpacing">20dp</item>

View File

@ -20,14 +20,14 @@
<!-- Default style for the BookmarksListView -->
<attr name="bookmarksListViewStyle" format="reference" />
<!-- Default style for the BookmarkThumbnailView -->
<attr name="bookmarkThumbnailViewStyle" format="reference" />
<!-- Default style for the TopSitesGridItemView -->
<attr name="topSitesGridItemViewStyle" format="reference" />
<!-- Default style for the TopBookmarkItemView -->
<attr name="topBookmarkItemViewStyle" format="reference" />
<!-- Default style for the TopSitesGridView -->
<attr name="topSitesGridViewStyle" format="reference" />
<!-- Default style for the TopBookmarksView -->
<attr name="topBookmarksViewStyle" format="reference" />
<!-- Default style for the TopSitesThumbnailView -->
<attr name="topSitesThumbnailViewStyle" format="reference" />
<!-- Default style for the HomeListView -->
<attr name="homeListViewStyle" format="reference" />
@ -212,7 +212,7 @@
</attr>
</declare-styleable>
<declare-styleable name="TopBookmarksView">
<declare-styleable name="TopSitesGridView">
<attr name="android:horizontalSpacing"/>
<attr name="android:verticalSpacing"/>
</declare-styleable>

View File

@ -75,7 +75,6 @@
<color name="suggestion_primary">#dddddd</color>
<color name="suggestion_pressed">#bbbbbb</color>
<color name="tab_row_pressed">#4D000000</color>
<color name="top_bookmark_pin">#55000000</color>
<color name="dialogtitle_textcolor">#ffffff</color>
<color name="textbox_background">#FFF</color>

View File

@ -71,7 +71,6 @@
<dimen name="text_selection_handle_width">47dp</dimen>
<dimen name="text_selection_handle_height">58dp</dimen>
<dimen name="text_selection_handle_shadow">11dp</dimen>
<dimen name="top_bookmark_pinsize">20dp</dimen>
<dimen name="validation_message_height">50dp</dimen>
<dimen name="validation_message_margin_top">6dp</dimen>
<dimen name="forward_default_offset">-13dip</dimen>
@ -82,8 +81,8 @@
<!-- We need to maintain height for the tab widget on History Page
since android does not add footer/header divider height to its
calculation for wrap_content in LinearLayout.
50dp * 3 Views + 30dp padding + 4dp dividers-->
<dimen name="history_tab_widget_height">184dp</dimen>
50dp * 2 Views + 30dp padding + 4dp dividers-->
<dimen name="history_tab_widget_height">134dp</dimen>
<!-- PageActionButtons dimensions -->
<dimen name="page_action_button_width">32dp</dimen>

View File

@ -117,13 +117,13 @@
<item name="android:drawableLeft">@drawable/bookmark_folder</item>
</style>
<style name="Widget.TopBookmarksView" parent="Widget.GridView">
<style name="Widget.TopSitesGridView" parent="Widget.GridView">
<item name="android:padding">7dp</item>
<item name="android:horizontalSpacing">0dp</item>
<item name="android:verticalSpacing">7dp</item>
</style>
<style name="Widget.TopBookmarkItemView">
<style name="Widget.TopSitesGridItemView">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">fill_parent</item>
<item name="android:padding">5dip</item>
@ -134,19 +134,19 @@
<style name="Widget.BookmarksListView" parent="Widget.HomeListView"/>
<style name="Widget.BookmarkThumbnailView">
<style name="Widget.TopSitesThumbnailView">
<item name="android:padding">0dip</item>
<item name="android:scaleType">centerCrop</item>
</style>
<style name="Widget.TopBookmarkItemPin">
<style name="Widget.TopSitesGridItemPin">
<item name="android:minWidth">30dip</item>
<item name="android:minHeight">30dip</item>
<item name="android:padding">0dip</item>
</style>
<style name="Widget.TopBookmarkItemTitle">
<item name="android:textColor">@color/top_bookmark_item_title</item>
<style name="Widget.TopSitesGridItemTitle">
<item name="android:textColor">@color/top_sites_grid_item_title</item>
<item name="android:textSize">12sp</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">none</item>
@ -158,6 +158,8 @@
<item name="android:divider">#E7ECF0</item>
</style>
<style name="Widget.TopSitesListView" parent="Widget.BookmarksListView"/>
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView"/>
<style name="Widget.HomeBanner"/>
@ -201,6 +203,8 @@
<item name="android:paddingRight">10dip</item>
</style>
<style name="Widget.Home.HistoryPageTitle" parent="Widget.Home.HistoryTabIndicator"/>
<!--
TextAppearance
Note: Gecko uses light theme as default, while Android uses dark.

View File

@ -78,9 +78,9 @@
<item name="android:textViewStyle">@style/Widget.TextView</item>
<item name="android:spinnerStyle">@style/Widget.Spinner</item>
<item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
<item name="bookmarkThumbnailViewStyle">@style/Widget.BookmarkThumbnailView</item>
<item name="topBookmarkItemViewStyle">@style/Widget.TopBookmarkItemView</item>
<item name="topBookmarksViewStyle">@style/Widget.TopBookmarksView</item>
<item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
<item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
<item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
<item name="homeListViewStyle">@style/Widget.HomeListView</item>
</style>

View File

@ -222,9 +222,9 @@
<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="contextmenu_top_sites_edit">&contextmenu_top_sites_edit;</string>
<string name="contextmenu_top_sites_pin">&contextmenu_top_sites_pin;</string>
<string name="contextmenu_top_sites_unpin">&contextmenu_top_sites_unpin;</string>
<string name="pref_titlebar_mode">&pref_titlebar_mode;</string>
<string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
@ -252,6 +252,7 @@
<string name="button_yes">&button_yes;</string>
<string name="button_no">&button_no;</string>
<string name="home_top_sites_title">&home_top_sites_title;</string>
<string name="home_history_title">&home_history_title;</string>
<string name="home_last_tabs_title">&home_last_tabs_title;</string>
<string name="home_last_tabs_open">&home_last_tabs_open;</string>
@ -263,7 +264,7 @@
<string name="home_reading_list_empty">&home_reading_list_empty;</string>
<string name="home_reading_list_hint">&home_reading_list_hint;</string>
<string name="home_reading_list_hint_accessible">&home_reading_list_hint_accessible;</string>
<string name="pin_bookmark_dialog_hint">&pin_bookmark_dialog_hint;</string>
<string name="pin_site_dialog_hint">&pin_site_dialog_hint;</string>
<string name="filepicker_title">&filepicker_title;</string>
<string name="filepicker_audio_title">&filepicker_audio_title;</string>

View File

@ -24,13 +24,28 @@ import java.util.ArrayList;
* To use any of these methods in your test make sure it extends AboutHomeTest instead of BaseTest
*/
abstract class AboutHomeTest extends BaseTest {
protected enum AboutHomeTabs {MOST_VISITED, MOST_RECENT, TABS_FROM_LAST_TIME, BOOKMARKS, READING_LIST};
protected ArrayList<String> aboutHomeTabs = new ArrayList<String>() {{
add("HISTORY");
protected enum AboutHomeTabs {HISTORY, MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST};
private ArrayList<String> aboutHomeTabs = new ArrayList<String>() {{
add("TOP_SITES");
add("BOOKMARKS");
add("READING_LIST");
}};
@Override
protected void setUp() throws Exception {
super.setUp();
if (aboutHomeTabs.size() < 4) {
// Update it for tablets vs. phones.
if (mDevice.type.equals("phone")) {
aboutHomeTabs.add(0, AboutHomeTabs.HISTORY.toString());
} else {
aboutHomeTabs.add(AboutHomeTabs.HISTORY.toString());
}
}
}
/**
* FIXME: Write new versions of these methods and update their consumers to use the new about:home pages.
*/
@ -85,7 +100,6 @@ abstract class AboutHomeTest extends BaseTest {
return null;
}
/**
* FIXME: rewrite this to work with fig when rewriting the testBookmarksTab test
* This method will edit the bookmark with index = bookmarkIndex from the list of bookmarks
@ -142,69 +156,120 @@ abstract class AboutHomeTest extends BaseTest {
}
}
// A wait in order for the about:home tab to be rendered after drag/tab selection
// A wait in order for the about:home tab to be rendered after drag/tab selection
private void waitForAboutHomeTab(final int tabIndex) {
boolean correctTab = waitForCondition(new Condition() {
@Override
public boolean isSatisfied() {
ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
return (pager.getCurrentItem() == tabIndex);
ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
return (pager.getCurrentItem() == tabIndex);
}
}, MAX_WAIT_MS);
mAsserter.ok(correctTab, "Checking that the correct tab is displayed", "The " + aboutHomeTabs.get(tabIndex) + " tab is displayed");
}
private void clickAboutHomeTab(AboutHomeTabs tab) {
mSolo.clickOnText(tab.toString().replace("_", " "));
}
/**
* This method can be used to open the different tabs of about:home
* @param AboutHomeTabs enum item {MOST_VISITED, MOST_RECENT, TABS_FROM_LAST_TIME, BOOKMARKS, READING_LIST}
* Swipes to an about:home tab.
* @param int swipeVector Value and direction to swipe (go left for negative, right for positive).
*/
private void swipeAboutHome(int swipeVector) {
// Increase swipe width, which will especially impact tablets.
int swipeWidth = mDriver.getGeckoWidth() - 1;
int swipeHeight = mDriver.getGeckoHeight() / 2;
if (swipeVector >= 0) {
// Emulate swipe motion from right to left.
for (int i = 0; i < swipeVector; i++) {
mActions.drag(swipeWidth, 0, swipeHeight, swipeHeight);
mSolo.sleep(100);
}
} else {
// Emulate swipe motion from left to right.
for (int i = 0; i > swipeVector; i--) {
mActions.drag(0, swipeWidth, swipeHeight, swipeHeight);
mSolo.sleep(100);
}
}
}
/**
* This method can be used to open the different tabs of about:home.
*
* @param AboutHomeTabs enum item {MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST}
*/
protected void openAboutHomeTab(AboutHomeTabs tab) {
int halfWidth = mDriver.getGeckoWidth() / 2;
int halfHeight = mDriver.getGeckoHeight() / 2;
focusUrlBar();
ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
// Handle tablets by just clicking the visible tab title.
if (mDevice.type.equals("tablet")) {
// Just click for tablets, since all the titles are visible.
if (AboutHomeTabs.MOST_RECENT == tab || AboutHomeTabs.TABS_FROM_LAST_TIME == tab) {
mSolo.clickOnText(AboutHomeTabs.HISTORY.toString());
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
switch (tab) {
case MOST_RECENT: {
mSolo.clickOnView(tabwidget.getChildAt(0));
mAsserter.ok(waitForText(StringHelper.MOST_RECENT_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
break;
}
case TABS_FROM_LAST_TIME: {
mSolo.clickOnView(tabwidget.getChildAt(1));
mAsserter.ok(waitForText(StringHelper.TABS_FROM_LAST_TIME_LABEL), "Checking that we are in the Tabs from last time tab of about:home", "We are in the Tabs from last time tab");
break;
}
}
} else {
clickAboutHomeTab(tab);
}
return;
}
// Handle phones (non-tablets).
final int currentTabIndex = pager.getCurrentItem();
int tabOffset = aboutHomeTabs.indexOf(tab.toString()) - currentTabIndex;
switch (tab) {
case TOP_SITES : {
swipeAboutHome(tabOffset);
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
break;
}
case BOOKMARKS : {
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
swipeAboutHome(tabOffset);
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
break;
}
case MOST_RECENT: {
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.BOOKMARKS_LABEL));
mActions.drag(0, halfWidth, halfHeight, halfHeight);
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
mSolo.clickOnView(tabwidget.getChildAt(1));
mAsserter.ok(waitForText(StringHelper.MOST_RECENT_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
break;
}
case READING_LIST: {
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.BOOKMARKS_LABEL));
mActions.drag(halfWidth, 0, halfHeight, halfHeight);
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
break;
}
case MOST_VISITED: {
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.BOOKMARKS_LABEL));
mActions.drag(0, halfWidth, halfHeight, halfHeight);
// MOST_RECENT is contained in the HISTORY tab.
tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
swipeAboutHome(tabOffset);
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
mSolo.clickOnView(tabwidget.getChildAt(0));
mAsserter.ok(waitForText(StringHelper.MOST_RECENT_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
break;
}
case TABS_FROM_LAST_TIME: {
mSolo.clickOnText(StringHelper.BOOKMARKS_LABEL);
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.BOOKMARKS_LABEL));
mActions.drag(0, halfWidth, halfHeight, halfHeight);
// TABS_FROM_LAST_TIME is contained in the HISTORY tab.
tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
swipeAboutHome(tabOffset);
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
mSolo.clickOnView(tabwidget.getChildAt(2));
mSolo.clickOnView(tabwidget.getChildAt(1));
mAsserter.ok(waitForText(StringHelper.TABS_FROM_LAST_TIME_LABEL), "Checking that we are in the Tabs from last time tab of about:home", "We are in the Tabs from last time tab");
break;
}
case READING_LIST: {
swipeAboutHome(tabOffset);
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
break;
}
}
}
}

View File

@ -616,6 +616,7 @@ abstract class BaseTest extends ActivityInstrumentationTestCase2<Activity> {
public String type; // "tablet" or "phone"
public final int width;
public final int height;
public final float density;
public Device() {
// Determine device version
@ -634,6 +635,7 @@ abstract class BaseTest extends ActivityInstrumentationTestCase2<Activity> {
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
height = dm.heightPixels;
width = dm.widthPixels;
density = dm.density;
// Determine device type
type = "phone";
try {

View File

@ -90,6 +90,7 @@ class StringHelper {
// Labels for the about:home tabs
public static final String HISTORY_LABEL = "HISTORY";
public static final String TOP_SITES_LABEL = "TOP SITES";
public static final String BOOKMARKS_LABEL = "BOOKMARKS";
public static final String READING_LIST_LABEL = "READING LIST";
public static final String MOST_RECENT_LABEL = "Most recent";

View File

@ -0,0 +1,7 @@
<html>
<meta charset="utf-8">
<title>Browser Blank Page 04</title>
<body>
<p>Browser Blank Page 04</p>
</body>
</html>

View File

@ -0,0 +1,7 @@
<html>
<meta charset="utf-8">
<title>Browser Blank Page 05</title>
<body>
<p>Browser Blank Page 05</p>
</body>
</html>

View File

@ -35,6 +35,9 @@ public class testShareLink extends AboutHomeTest {
ArrayList<String> shareOptions;
blockForGeckoReady();
// FIXME: This is a temporary hack workaround for a permissions problem.
openAboutHomeTab(AboutHomeTabs.READING_LIST);
inputAndLoadUrl(url);
verifyPageTitle(urlTitle); // Waiting for page title to ensure the page is loaded
@ -59,12 +62,8 @@ public class testShareLink extends AboutHomeTest {
mSolo.clickLongOnText(urlTitle);
verifySharePopup(shareOptions,"urlbar");
// Test link Context Menu
DisplayMetrics dm = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
// The link has a 60px height, so let's try to hit the middle
float top = mDriver.getGeckoTop() + 30 * dm.density;
float top = mDriver.getGeckoTop() + 30 * mDevice.density;
float left = mDriver.getGeckoLeft() + mDriver.getGeckoWidth() / 2;
mSolo.clickLongOnScreen(left, top);
verifySharePopup("Share Link",shareOptions,"Link");
@ -75,26 +74,37 @@ public class testShareLink extends AboutHomeTest {
ListView bookmarksList = findListViewWithTag("bookmarks");
mAsserter.is(waitForListToLoad(bookmarksList), true, "list is properly loaded");
int width = mDriver.getGeckoWidth();
int height = mDriver.getGeckoHeight();
// Scroll down a bit so that the bookmarks list has more
// items on screen.
mActions.drag(width / 2, width / 2, height - 10, height / 2);
View bookmarksItem = bookmarksList.getChildAt(bookmarksList.getHeaderViewsCount());
mSolo.clickLongOnView(bookmarksItem);
verifySharePopup(shareOptions,"bookmarks");
// Test the share popup in the Most Visited tab
openAboutHomeTab(AboutHomeTabs.MOST_VISITED);
// Prepopulate top sites with history items to overflow tiles.
// We are trying to move away from using reflection and doing more black-box testing.
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_01.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_02.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_03.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_04.html"));
if (mDevice.type.equals("tablet")) {
// Tablets have more tile spaces to fill.
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_05.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_boxes.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_search.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_text_page.html"));
}
ListView mostVisitedList = findListViewWithTag("most_visited");
mAsserter.is(waitForListToLoad(mostVisitedList), true, "list is properly loaded");
// Test the share popup in Top Sites.
openAboutHomeTab(AboutHomeTabs.TOP_SITES);
View mostVisitedItem = mostVisitedList.getChildAt(mostVisitedList.getHeaderViewsCount());
// Scroll down a bit so that the top sites list has more items on screen.
int width = mDriver.getGeckoWidth();
int height = mDriver.getGeckoHeight();
mActions.drag(width / 2, width / 2, height - 10, height / 2);
ListView topSitesList = findListViewWithTag("top_sites");
mAsserter.is(waitForListToLoad(topSitesList), true, "list is properly loaded");
View mostVisitedItem = topSitesList.getChildAt(topSitesList.getHeaderViewsCount());
mSolo.clickLongOnView(mostVisitedItem);
verifySharePopup(shareOptions,"most visited");
verifySharePopup(shareOptions,"top_sites");
// Test the share popup in the Most Recent tab
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);