diff --git a/mobile/android/base/AwesomeBar.java b/mobile/android/base/AwesomeBar.java index cd020b87d56..f6dea7aa1e4 100644 --- a/mobile/android/base/AwesomeBar.java +++ b/mobile/android/base/AwesomeBar.java @@ -223,7 +223,6 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener { } }); - registerForContextMenu(mAwesomeTabs.findViewById(R.id.all_pages_list)); registerForContextMenu(mAwesomeTabs.findViewById(R.id.bookmarks_list)); registerForContextMenu(mAwesomeTabs.findViewById(R.id.history_list)); diff --git a/mobile/android/base/AwesomeBarTabs.java b/mobile/android/base/AwesomeBarTabs.java index 5cc80c9ffd2..6eb63bf1c28 100644 --- a/mobile/android/base/AwesomeBarTabs.java +++ b/mobile/android/base/AwesomeBarTabs.java @@ -28,7 +28,6 @@ import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ExpandableListView; -import android.widget.FilterQueryProvider; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; @@ -37,8 +36,6 @@ import android.widget.SimpleExpandableListAdapter; import android.widget.TabHost; import android.widget.TextView; -import java.io.ByteArrayInputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -47,8 +44,6 @@ import java.util.List; import java.util.Map; import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import org.mozilla.gecko.db.BrowserContract.Bookmarks; import org.mozilla.gecko.db.BrowserContract.Combined; @@ -58,7 +53,6 @@ import org.mozilla.gecko.db.BrowserDB.URLColumns; public class AwesomeBarTabs extends TabHost { private static final String LOGTAG = "GeckoAwesomeBarTabs"; - private static final String ALL_PAGES_TAB = "all"; private static final String BOOKMARKS_TAB = "bookmarks"; private static final String HISTORY_TAB = "history"; @@ -70,13 +64,11 @@ public class AwesomeBarTabs extends TabHost { private OnUrlOpenListener mUrlOpenListener; private ContentResolver mContentResolver; private ContentObserver mContentObserver; - private SearchEngine mSuggestEngine; - private ArrayList mSearchEngines; private BookmarksQueryTask mBookmarksQueryTask; private HistoryQueryTask mHistoryQueryTask; - private AwesomeBarCursorAdapter mAllPagesCursorAdapter; + private AllPagesTab mAllPagesTab; private BookmarksListAdapter mBookmarksAdapter; private SimpleExpandableListAdapter mHistoryAdapter; @@ -99,13 +91,6 @@ public class AwesomeBarTabs extends TabHost { public ImageView bookmarkIconView; } - private class SearchEntryViewHolder { - public FlowLayout suggestionView; - public ImageView iconView; - public LinearLayout userEnteredView; - public TextView userEnteredTextView; - } - private class HistoryListAdapter extends SimpleExpandableListAdapter { public HistoryListAdapter(Context context, List> groupData, int groupLayout, String[] groupFrom, int[] groupTo, @@ -611,250 +596,6 @@ public class AwesomeBarTabs extends TabHost { } } - private interface AwesomeBarItem { - public void onClick(); - } - - private class AwesomeBarCursorAdapter extends SimpleCursorAdapter { - private String mSearchTerm; - - private static final int ROW_SEARCH = 0; - private static final int ROW_STANDARD = 1; - - private class AwesomeBarCursorItem implements AwesomeBarItem { - private Cursor mCursor; - - public AwesomeBarCursorItem(Cursor cursor) { - mCursor = cursor; - } - - public void onClick() { - String url = mCursor.getString(mCursor.getColumnIndexOrThrow(URLColumns.URL)); - if (mUrlOpenListener != null) { - int display = mCursor.getInt(mCursor.getColumnIndexOrThrow(Combined.DISPLAY)); - if (display == Combined.DISPLAY_READER) { - url = getReaderForUrl(url); - } - - mUrlOpenListener.onUrlOpen(url); - } - } - } - - private class AwesomeBarSearchEngineItem implements AwesomeBarItem { - private String mSearchEngine; - - public AwesomeBarSearchEngineItem(String searchEngine) { - mSearchEngine = searchEngine; - } - - public void onClick() { - if (mUrlOpenListener != null) - mUrlOpenListener.onSearch(mSearchEngine, mSearchTerm); - } - } - - public AwesomeBarCursorAdapter(Context context) { - super(context, -1, null, new String[] {}, new int[] {}); - mSearchTerm = ""; - } - - public void filter(String searchTerm) { - mSearchTerm = searchTerm; - getFilter().filter(searchTerm); - } - - private int getSuggestEngineCount() { - return (mSearchTerm.length() == 0 || mSuggestEngine == null) ? 0 : 1; - } - - // Add the search engines to the number of reported results. - @Override - public int getCount() { - final int resultCount = super.getCount(); - - // don't show search engines or suggestions if search field is empty - if (mSearchTerm.length() == 0) - return resultCount; - - return resultCount + mSearchEngines.size() + getSuggestEngineCount(); - } - - // If an item is part of the cursor result set, return that entry. - // Otherwise, return the search engine data. - @Override - public Object getItem(int position) { - int engineIndex = getEngineIndex(position); - - if (engineIndex == -1) { - // return awesomebar result - position -= getSuggestEngineCount(); - return new AwesomeBarCursorItem((Cursor) super.getItem(position)); - } - - // return search engine - return new AwesomeBarSearchEngineItem(getEngine(engineIndex).name); - } - - private SearchEngine getEngine(int index) { - final int suggestEngineCount = getSuggestEngineCount(); - if (index < suggestEngineCount) - return mSuggestEngine; - return mSearchEngines.get(index - suggestEngineCount); - } - - private int getEngineIndex(int position) { - final int resultCount = super.getCount(); - final int suggestEngineCount = getSuggestEngineCount(); - - // return suggest engine index - if (position < suggestEngineCount) - return 0; - - // not an engine - if (position - suggestEngineCount < resultCount) - return -1; - - // return search engine index - return position - resultCount; - } - - @Override - public int getItemViewType(int position) { - return getEngineIndex(position) == -1 ? ROW_STANDARD : ROW_SEARCH; - } - - @Override - public int getViewTypeCount() { - // view can be either a standard awesomebar row or a search engine row - return 2; - } - - @Override - public boolean isEnabled(int position) { - // If the suggestion row only contains one item (the user-entered - // query), allow the entire row to be clickable; clicking the row - // has the same effect as clicking the single suggestion. If the - // row contains multiple items, clicking the row will do nothing. - int index = getEngineIndex(position); - if (index != -1) { - return getEngine(index).suggestions.isEmpty(); - } - return true; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (getItemViewType(position) == ROW_SEARCH) { - SearchEntryViewHolder viewHolder = null; - - if (convertView == null) { - convertView = mInflater.inflate(R.layout.awesomebar_suggestion_row, null); - - viewHolder = new SearchEntryViewHolder(); - viewHolder.suggestionView = (FlowLayout) convertView.findViewById(R.id.suggestion_layout); - viewHolder.iconView = (ImageView) convertView.findViewById(R.id.suggestion_icon); - viewHolder.userEnteredView = (LinearLayout) convertView.findViewById(R.id.suggestion_user_entered); - viewHolder.userEnteredTextView = (TextView) convertView.findViewById(R.id.suggestion_text); - - convertView.setTag(viewHolder); - } else { - viewHolder = (SearchEntryViewHolder) convertView.getTag(); - } - - bindSearchEngineView(getEngine(getEngineIndex(position)), viewHolder); - } else { - AwesomeEntryViewHolder viewHolder = null; - - if (convertView == null) { - convertView = mInflater.inflate(R.layout.awesomebar_row, null); - - viewHolder = new AwesomeEntryViewHolder(); - viewHolder.titleView = (TextView) convertView.findViewById(R.id.title); - viewHolder.urlView = (TextView) convertView.findViewById(R.id.url); - viewHolder.faviconView = (ImageView) convertView.findViewById(R.id.favicon); - viewHolder.bookmarkIconView = (ImageView) convertView.findViewById(R.id.bookmark_icon); - - convertView.setTag(viewHolder); - } else { - viewHolder = (AwesomeEntryViewHolder) convertView.getTag(); - } - - position -= getSuggestEngineCount(); - Cursor cursor = getCursor(); - if (!cursor.moveToPosition(position)) - throw new IllegalStateException("Couldn't move cursor to position " + position); - - updateTitle(viewHolder.titleView, cursor); - updateUrl(viewHolder.urlView, cursor); - updateFavicon(viewHolder.faviconView, cursor); - updateBookmarkIcon(viewHolder.bookmarkIconView, cursor); - } - - return convertView; - } - - private void bindSearchEngineView(final SearchEngine engine, SearchEntryViewHolder viewHolder) { - // when a suggestion is clicked, do a search - OnClickListener clickListener = new OnClickListener() { - public void onClick(View v) { - if (mUrlOpenListener != null) { - String suggestion = ((TextView) v.findViewById(R.id.suggestion_text)).getText().toString(); - mUrlOpenListener.onSearch(engine.name, suggestion); - } - } - }; - - // when a suggestion is long-clicked, copy the suggestion into the URL EditText - OnLongClickListener longClickListener = new OnLongClickListener() { - public boolean onLongClick(View v) { - if (mUrlOpenListener != null) { - String suggestion = ((TextView) v.findViewById(R.id.suggestion_text)).getText().toString(); - mUrlOpenListener.onEditSuggestion(suggestion); - return true; - } - return false; - } - }; - - // set the search engine icon (e.g., Google) for the row - FlowLayout suggestionView = viewHolder.suggestionView; - viewHolder.iconView.setImageDrawable(engine.icon); - - // user-entered search term is first suggestion - viewHolder.userEnteredTextView.setText(mSearchTerm); - viewHolder.userEnteredView.setOnClickListener(clickListener); - - // add additional suggestions given by this engine - int recycledSuggestionCount = suggestionView.getChildCount(); - int suggestionCount = engine.suggestions.size(); - int i = 0; - for (i = 0; i < suggestionCount; i++) { - String suggestion = engine.suggestions.get(i); - View suggestionItem = null; - - // reuse suggestion views from recycled view, if possible - if (i+1 < recycledSuggestionCount) { - suggestionItem = suggestionView.getChildAt(i+1); - suggestionItem.setVisibility(View.VISIBLE); - } else { - suggestionItem = mInflater.inflate(R.layout.awesomebar_suggestion_item, null); - ((ImageView) suggestionItem.findViewById(R.id.suggestion_magnifier)).setVisibility(View.GONE); - suggestionView.addView(suggestionItem); - } - ((TextView) suggestionItem.findViewById(R.id.suggestion_text)).setText(suggestion); - - suggestionItem.setOnClickListener(clickListener); - suggestionItem.setOnLongClickListener(longClickListener); - } - - // hide extra suggestions that have been recycled - for (++i; i < recycledSuggestionCount; i++) { - suggestionView.getChildAt(i).setVisibility(View.GONE); - } - } - }; - public AwesomeBarTabs(Context context, AttributeSet attrs) { super(context, attrs); @@ -862,7 +603,6 @@ public class AwesomeBarTabs extends TabHost { mContext = context; mInflated = false; - mSearchEngines = new ArrayList(); mContentResolver = context.getContentResolver(); mContentObserver = null; mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -920,7 +660,23 @@ public class AwesomeBarTabs extends TabHost { filter(""); } + private TabSpec addAwesomeTab(String id, int titleId, TabHost.TabContentFactory factory) { + TabSpec tab = getTabSpec(id, titleId); + tab.setContent(factory); + addTab(tab); + + return tab; + } + private TabSpec addAwesomeTab(String id, int titleId, int contentId) { + TabSpec tab = getTabSpec(id, titleId); + tab.setContent(contentId); + addTab(tab); + + return tab; + } + + private TabSpec getTabSpec(String id, int titleId) { TabSpec tab = newTabSpec(id); View indicatorView = mInflater.inflate(R.layout.awesomebar_tab_indicator, null); @@ -934,9 +690,6 @@ public class AwesomeBarTabs extends TabHost { title.setText(titleId); tab.setIndicator(indicatorView); - tab.setContent(contentId); - - addTab(tab); return tab; } @@ -944,36 +697,10 @@ public class AwesomeBarTabs extends TabHost { private void addAllPagesTab() { Log.d(LOGTAG, "Creating All Pages tab"); - addAwesomeTab(ALL_PAGES_TAB, - R.string.awesomebar_all_pages_title, - R.id.all_pages_list); - - // Load the list using a custom adapter so we can create the bitmaps - mAllPagesCursorAdapter = new AwesomeBarCursorAdapter(mContext); - - mAllPagesCursorAdapter.setFilterQueryProvider(new FilterQueryProvider() { - public Cursor runQuery(CharSequence constraint) { - long start = SystemClock.uptimeMillis(); - - Cursor c = BrowserDB.filter(mContentResolver, constraint, MAX_RESULTS); - c.getCount(); // ensure the query runs at least once - - long end = SystemClock.uptimeMillis(); - Log.i(LOGTAG, "Got cursor in " + (end - start) + "ms"); - - return c; - } - }); - - final ListView allPagesList = (ListView) findViewById(R.id.all_pages_list); - - allPagesList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - public void onItemClick(AdapterView parent, View view, int position, long id) { - ((AwesomeBarItem) allPagesList.getItemAtPosition(position)).onClick(); - } - }); - - allPagesList.setAdapter(mAllPagesCursorAdapter); + mAllPagesTab = new AllPagesTab(mContext); + addAwesomeTab(mAllPagesTab.getTag(), + mAllPagesTab.getTitleStringId(), + mAllPagesTab.getFactory()); } private void addBookmarksTab() { @@ -1112,12 +839,11 @@ public class AwesomeBarTabs extends TabHost { public void setOnUrlOpenListener(OnUrlOpenListener listener) { mUrlOpenListener = listener; + mAllPagesTab.setUrlListener(listener); } public void destroy() { - Cursor allPagesCursor = mAllPagesCursorAdapter.getCursor(); - if (allPagesCursor != null) - allPagesCursor.close(); + mAllPagesTab.destroy(); if (mBookmarksAdapter != null) { Cursor bookmarksCursor = mBookmarksAdapter.getCursor(); @@ -1134,7 +860,7 @@ public class AwesomeBarTabs extends TabHost { setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); // Ensure the 'All Pages' tab is selected - setCurrentTabByTag(ALL_PAGES_TAB); + setCurrentTabByTag(mAllPagesTab.getTag()); // Restore normal focus behavior on tab host setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); @@ -1144,35 +870,9 @@ public class AwesomeBarTabs extends TabHost { getTabWidget().setVisibility(tabsVisibility); // Perform the actual search - mAllPagesCursorAdapter.filter(searchTerm); + mAllPagesTab.filter(searchTerm); } - private Drawable getDrawableFromDataURI(String dataURI) { - String base64 = dataURI.substring(dataURI.indexOf(',') + 1); - Drawable drawable = null; - try { - byte[] bytes = GeckoAppShell.decodeBase64(base64, GeckoAppShell.BASE64_DEFAULT); - ByteArrayInputStream stream = new ByteArrayInputStream(bytes); - drawable = Drawable.createFromStream(stream, "src"); - stream.close(); - } catch (IllegalArgumentException e) { - Log.i(LOGTAG, "exception while decoding drawable: " + base64, e); - } catch (IOException e) { } - return drawable; - } - - private class SearchEngine { - public String name; - public Drawable icon; - public ArrayList suggestions; - - public SearchEngine(String name, Drawable icon) { - this.name = name; - this.icon = icon; - this.suggestions = new ArrayList(); - } - }; - /** * Sets suggestions associated with the current suggest engine. * If there is no suggest engine, this does nothing. @@ -1180,10 +880,7 @@ public class AwesomeBarTabs extends TabHost { public void setSuggestions(final ArrayList suggestions) { GeckoAppShell.getMainHandler().post(new Runnable() { public void run() { - if (mSuggestEngine != null) { - mSuggestEngine.suggestions = suggestions; - mAllPagesCursorAdapter.notifyDataSetChanged(); - } + mAllPagesTab.setSuggestions(suggestions); } }); } @@ -1191,32 +888,10 @@ public class AwesomeBarTabs extends TabHost { /** * Sets search engines to be shown for user-entered queries. */ - public void setSearchEngines(String suggestEngineName, JSONArray engines) { - final ArrayList searchEngines = new ArrayList(); - SearchEngine suggestEngine = null; - for (int i = 0; i < engines.length(); i++) { - try { - JSONObject engineJSON = engines.getJSONObject(i); - String name = engineJSON.getString("name"); - String iconURI = engineJSON.getString("iconURI"); - Drawable icon = getDrawableFromDataURI(iconURI); - if (name.equals(suggestEngineName)) { - suggestEngine = new SearchEngine(name, icon); - } else { - searchEngines.add(new SearchEngine(name, icon)); - } - } catch (JSONException e) { - Log.e(LOGTAG, "Error getting search engine JSON", e); - return; - } - } - - final SearchEngine suggestEngineArg = suggestEngine; + public void setSearchEngines(final String suggestEngineName, final JSONArray engines) { GeckoAppShell.getMainHandler().post(new Runnable() { public void run() { - mSuggestEngine = suggestEngineArg; - mSearchEngines = searchEngines; - mAllPagesCursorAdapter.notifyDataSetChanged(); + mAllPagesTab.setSearchEngines(suggestEngineName, engines); } }); } diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index f1898a18bd8..2805cc844e5 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -26,6 +26,7 @@ FENNEC_JAVA_FILES = \ AlertNotification.java \ AwesomeBar.java \ awesomebar/AwesomeBarTab.java \ + awesomebar/AllPagesTab.java \ AwesomeBarTabs.java \ BrowserApp.java \ BrowserToolbar.java \ diff --git a/mobile/android/base/awesomebar/AllPagesTab.java b/mobile/android/base/awesomebar/AllPagesTab.java new file mode 100644 index 00000000000..5f1317fb6db --- /dev/null +++ b/mobile/android/base/awesomebar/AllPagesTab.java @@ -0,0 +1,500 @@ +/* -*- 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; + +import android.app.Activity; +import android.content.Context; +import android.widget.ListView; +import android.widget.ImageView; +import android.widget.TextView; +import android.view.View; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.MenuItem; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.app.Activity; +import android.widget.AdapterView; +import android.database.Cursor; +import android.widget.AdapterView; +import android.util.Log; +import android.text.TextUtils; +import android.widget.Toast; +import android.widget.SimpleCursorAdapter; +import android.widget.LinearLayout; +import android.widget.TabHost.TabContentFactory; +import android.view.ViewGroup; +import android.graphics.drawable.Drawable; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.content.Intent; +import android.widget.FilterQueryProvider; +import android.os.SystemClock; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONException; + +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.db.BrowserDB.URLColumns; +import org.mozilla.gecko.db.BrowserContract.Combined; + +public class AllPagesTab extends AwesomeBarTab { + public static final String LOGTAG = "ALL_PAGES"; + private static final String TAG = "allPages"; + private SearchEngine mSuggestEngine; + private ArrayList mSearchEngines; + private ListView mView = null; + private AwesomeBarCursorAdapter mCursorAdapter = null; + + private class SearchEntryViewHolder { + public FlowLayout suggestionView; + public ImageView iconView; + public LinearLayout userEnteredView; + public TextView userEnteredTextView; + } + + public AllPagesTab(Context context) { + super(context); + mSearchEngines = new ArrayList(); + } + + public TabContentFactory getFactory() { + return new TabContentFactory() { + public View createTabContent(String tag) { + final ListView list = getListView(); + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View view, int position, long id) { + handleItemClick(parent, view, position, id); + } + }); + return list; + } + }; + } + + public int getTitleStringId() { + return R.string.awesomebar_all_pages_title; + } + + public String getTag() { + return TAG; + } + + public ListView getListView() { + if (mView == null) { + mView = new ListView(mContext, null, R.style.AwesomeBarList); + ((Activity)mContext).registerForContextMenu(mView); + mView.setTag(TAG); + AwesomeBarCursorAdapter adapter = getCursorAdapter(); + mView.setAdapter(adapter); + } + return mView; + } + + public void destroy() { + AwesomeBarCursorAdapter adapter = getCursorAdapter(); + if (adapter == null) { + return; + } + + Cursor cursor = adapter.getCursor(); + if (cursor != null) + cursor.close(); + } + + public void filter(String searchTerm) { + AwesomeBarCursorAdapter adapter = getCursorAdapter(); + adapter.filter(searchTerm); + } + + protected AwesomeBarCursorAdapter getCursorAdapter() { + if (mCursorAdapter == null) { + // Load the list using a custom adapter so we can create the bitmaps + mCursorAdapter = new AwesomeBarCursorAdapter(mContext); + + mCursorAdapter.setFilterQueryProvider(new FilterQueryProvider() { + public Cursor runQuery(CharSequence constraint) { + long start = SystemClock.uptimeMillis(); + + Cursor c = BrowserDB.filter(getContentResolver(), constraint, MAX_RESULTS); + c.getCount(); + + long end = SystemClock.uptimeMillis(); + Log.i(LOGTAG, "Got cursor in " + (end - start) + "ms"); + + return c; + } + }); + } + return mCursorAdapter; + } + + private interface AwesomeBarItem { + public void onClick(); + } + + private class AwesomeBarCursorAdapter extends SimpleCursorAdapter { + private String mSearchTerm; + + private static final int ROW_SEARCH = 0; + private static final int ROW_STANDARD = 1; + + private class AwesomeBarCursorItem implements AwesomeBarItem { + private Cursor mCursor; + + public AwesomeBarCursorItem(Cursor cursor) { + mCursor = cursor; + } + + public void onClick() { + AwesomeBarTabs.OnUrlOpenListener listener = getUrlListener(); + if (listener == null) + return; + + String url = mCursor.getString(mCursor.getColumnIndexOrThrow(URLColumns.URL)); + + int display = mCursor.getInt(mCursor.getColumnIndexOrThrow(Combined.DISPLAY)); + if (display == Combined.DISPLAY_READER) { + url = getReaderForUrl(url); + } + listener.onUrlOpen(url); + } + } + + private class AwesomeBarSearchEngineItem implements AwesomeBarItem { + private String mSearchEngine; + + public AwesomeBarSearchEngineItem(String searchEngine) { + mSearchEngine = searchEngine; + } + + public void onClick() { + AwesomeBarTabs.OnUrlOpenListener listener = getUrlListener(); + if (listener != null) + listener.onSearch(mSearchEngine, mSearchTerm); + } + } + + public AwesomeBarCursorAdapter(Context context) { + super(context, -1, null, new String[] {}, new int[] {}); + mSearchTerm = ""; + } + + public void filter(String searchTerm) { + mSearchTerm = searchTerm; + getFilter().filter(searchTerm); + } + + private int getSuggestEngineCount() { + return (mSearchTerm.length() == 0 || mSuggestEngine == null) ? 0 : 1; + } + + // Add the search engines to the number of reported results. + @Override + public int getCount() { + final int resultCount = super.getCount(); + + // don't show search engines or suggestions if search field is empty + if (mSearchTerm.length() == 0) + return resultCount; + + return resultCount + mSearchEngines.size() + getSuggestEngineCount(); + } + + // If an item is part of the cursor result set, return that entry. + // Otherwise, return the search engine data. + @Override + public Object getItem(int position) { + int engineIndex = getEngineIndex(position); + + if (engineIndex == -1) { + // return awesomebar result + position -= getSuggestEngineCount(); + return new AwesomeBarCursorItem((Cursor) super.getItem(position)); + } + + // return search engine + return new AwesomeBarSearchEngineItem(getEngine(engineIndex).name); + } + + private SearchEngine getEngine(int index) { + final int suggestEngineCount = getSuggestEngineCount(); + if (index < suggestEngineCount) + return mSuggestEngine; + return mSearchEngines.get(index - suggestEngineCount); + } + + private int getEngineIndex(int position) { + final int resultCount = super.getCount(); + final int suggestEngineCount = getSuggestEngineCount(); + + // return suggest engine index + if (position < suggestEngineCount) + return 0; + + // not an engine + if (position - suggestEngineCount < resultCount) + return -1; + + // return search engine index + return position - resultCount; + } + + @Override + public int getItemViewType(int position) { + return getEngineIndex(position) == -1 ? ROW_STANDARD : ROW_SEARCH; + } + + @Override + public int getViewTypeCount() { + // view can be either a standard awesomebar row or a search engine row + return 2; + } + + @Override + public boolean isEnabled(int position) { + // If the suggestion row only contains one item (the user-entered + // query), allow the entire row to be clickable; clicking the row + // has the same effect as clicking the single suggestion. If the + // row contains multiple items, clicking the row will do nothing. + int index = getEngineIndex(position); + if (index != -1) { + return getEngine(index).suggestions.isEmpty(); + } + return true; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (getItemViewType(position) == ROW_SEARCH) { + SearchEntryViewHolder viewHolder = null; + + if (convertView == null) { + convertView = getInflater().inflate(R.layout.awesomebar_suggestion_row, null); + + viewHolder = new SearchEntryViewHolder(); + viewHolder.suggestionView = (FlowLayout) convertView.findViewById(R.id.suggestion_layout); + viewHolder.iconView = (ImageView) convertView.findViewById(R.id.suggestion_icon); + viewHolder.userEnteredView = (LinearLayout) convertView.findViewById(R.id.suggestion_user_entered); + viewHolder.userEnteredTextView = (TextView) convertView.findViewById(R.id.suggestion_text); + + convertView.setTag(viewHolder); + } else { + viewHolder = (SearchEntryViewHolder) convertView.getTag(); + } + + bindSearchEngineView(getEngine(getEngineIndex(position)), viewHolder); + } else { + AwesomeEntryViewHolder viewHolder = null; + + if (convertView == null) { + convertView = getInflater().inflate(R.layout.awesomebar_row, null); + + viewHolder = new AwesomeEntryViewHolder(); + viewHolder.titleView = (TextView) convertView.findViewById(R.id.title); + viewHolder.urlView = (TextView) convertView.findViewById(R.id.url); + viewHolder.faviconView = (ImageView) convertView.findViewById(R.id.favicon); + viewHolder.bookmarkIconView = (ImageView) convertView.findViewById(R.id.bookmark_icon); + + convertView.setTag(viewHolder); + } else { + viewHolder = (AwesomeEntryViewHolder) convertView.getTag(); + } + + position -= getSuggestEngineCount(); + Cursor cursor = getCursor(); + if (!cursor.moveToPosition(position)) + throw new IllegalStateException("Couldn't move cursor to position " + position); + + updateTitle(viewHolder.titleView, cursor); + updateUrl(viewHolder.urlView, cursor); + updateFavicon(viewHolder.faviconView, cursor); + updateBookmarkIcon(viewHolder.bookmarkIconView, cursor); + } + + return convertView; + } + + private void bindSearchEngineView(final SearchEngine engine, SearchEntryViewHolder viewHolder) { + // when a suggestion is clicked, do a search + OnClickListener clickListener = new OnClickListener() { + public void onClick(View v) { + AwesomeBarTabs.OnUrlOpenListener listener = getUrlListener(); + if (listener != null) { + String suggestion = ((TextView) v.findViewById(R.id.suggestion_text)).getText().toString(); + listener.onSearch(engine.name, suggestion); + } + } + }; + + // when a suggestion is long-clicked, copy the suggestion into the URL EditText + OnLongClickListener longClickListener = new OnLongClickListener() { + public boolean onLongClick(View v) { + AwesomeBarTabs.OnUrlOpenListener listener = getUrlListener(); + if (listener != null) { + String suggestion = ((TextView) v.findViewById(R.id.suggestion_text)).getText().toString(); + listener.onEditSuggestion(suggestion); + return true; + } + return false; + } + }; + + // set the search engine icon (e.g., Google) for the row + FlowLayout suggestionView = viewHolder.suggestionView; + viewHolder.iconView.setImageDrawable(engine.icon); + + // user-entered search term is first suggestion + viewHolder.userEnteredTextView.setText(mSearchTerm); + viewHolder.userEnteredView.setOnClickListener(clickListener); + + // add additional suggestions given by this engine + int recycledSuggestionCount = suggestionView.getChildCount(); + int suggestionCount = engine.suggestions.size(); + int i = 0; + for (i = 0; i < suggestionCount; i++) { + String suggestion = engine.suggestions.get(i); + View suggestionItem = null; + + // reuse suggestion views from recycled view, if possible + if (i+1 < recycledSuggestionCount) { + suggestionItem = suggestionView.getChildAt(i+1); + suggestionItem.setVisibility(View.VISIBLE); + } else { + suggestionItem = getInflater().inflate(R.layout.awesomebar_suggestion_item, null); + ((ImageView) suggestionItem.findViewById(R.id.suggestion_magnifier)).setVisibility(View.GONE); + suggestionView.addView(suggestionItem); + } + ((TextView) suggestionItem.findViewById(R.id.suggestion_text)).setText(suggestion); + + suggestionItem.setOnClickListener(clickListener); + suggestionItem.setOnLongClickListener(longClickListener); + } + + // hide extra suggestions that have been recycled + for (++i; i < recycledSuggestionCount; i++) { + suggestionView.getChildAt(i).setVisibility(View.GONE); + } + } + }; + + private class SearchEngine { + public String name; + public Drawable icon; + public ArrayList suggestions; + + public SearchEngine(String name) { + this(name, null); + } + + public SearchEngine(String name, Drawable icon) { + this.name = name; + this.icon = icon; + this.suggestions = new ArrayList(); + } + }; + + /** + * Sets the suggest engine, which will show suggestions for user-entered queries. + * If the suggest engine has already been set, it will be replaced, and its + * suggestions will be copied to the new suggest engine. + */ + public void setSuggestEngine(String name, Drawable icon) { + // We currently save the suggest engine in shared preferences, so this + // method is called immediately when the AwesomeBar is created. It's + // called again in setSuggestions(), when the list of search engines is + // received from Gecko (in case the suggestion engine has changed). + final SearchEngine suggestEngine = new SearchEngine(name, icon); + if (mSuggestEngine != null) + suggestEngine.suggestions = mSuggestEngine.suggestions; + + mSuggestEngine = suggestEngine; + } + + /** + * Sets suggestions associated with the current suggest engine. + * If there is no suggest engine, this does nothing. + */ + public void setSuggestions(final ArrayList suggestions) { + if (mSuggestEngine != null) { + mSuggestEngine.suggestions = suggestions; + getCursorAdapter().notifyDataSetChanged(); + } + } + + /** + * Sets search engines to be shown for user-entered queries. + */ + public void setSearchEngines(String suggestEngine, JSONArray engines) { + mSearchEngines = new ArrayList(); + for (int i = 0; i < engines.length(); i++) { + try { + JSONObject engineJSON = engines.getJSONObject(i); + String name = engineJSON.getString("name"); + String iconURI = engineJSON.getString("iconURI"); + Drawable icon = getDrawableFromDataURI(iconURI); + if (name.equals(suggestEngine)) { + setSuggestEngine(name, icon); + } else { + mSearchEngines.add(new SearchEngine(name, icon)); + } + } catch (JSONException e) { + Log.e(LOGTAG, "Error getting search engine JSON", e); + return; + } + } + AwesomeBarCursorAdapter adapter = getCursorAdapter(); + if (adapter != null) + adapter.notifyDataSetChanged(); + } + + private Drawable getDrawableFromDataURI(String dataURI) { + String base64 = dataURI.substring(dataURI.indexOf(',') + 1); + Drawable drawable = null; + try { + byte[] bytes = GeckoAppShell.decodeBase64(base64, GeckoAppShell.BASE64_DEFAULT); + ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + drawable = Drawable.createFromStream(stream, "src"); + stream.close(); + } catch (IllegalArgumentException e) { + Log.i(LOGTAG, "exception while decoding drawable: " + base64, e); + } catch (IOException e) { } + return drawable; + } + + public void handleItemClick(AdapterView parent, View view, int position, long id) { + ListView listview = getListView(); + if (listview == null) + return; + + AwesomeBarItem item = (AwesomeBarItem)listview.getItemAtPosition(position); + item.onClick(); + } + + protected void updateBookmarkIcon(ImageView bookmarkIconView, Cursor cursor) { + int bookmarkIdIndex = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID); + long id = cursor.getLong(bookmarkIdIndex); + + int displayIndex = cursor.getColumnIndexOrThrow(Combined.DISPLAY); + int display = cursor.getInt(displayIndex); + + // The bookmark id will be 0 (null in database) when the url + // is not a bookmark. + int visibility = (id == 0 ? View.GONE : View.VISIBLE); + bookmarkIconView.setVisibility(visibility); + + if (display == Combined.DISPLAY_READER) { + bookmarkIconView.setImageResource(R.drawable.ic_awesomebar_reader); + } else { + bookmarkIconView.setImageResource(R.drawable.ic_awesomebar_star); + } + } +} diff --git a/mobile/android/base/awesomebar/AwesomeBarTab.java b/mobile/android/base/awesomebar/AwesomeBarTab.java index cd148392e99..9123cb97c0c 100644 --- a/mobile/android/base/awesomebar/AwesomeBarTab.java +++ b/mobile/android/base/awesomebar/AwesomeBarTab.java @@ -5,18 +5,32 @@ package org.mozilla.gecko; +import android.content.ContentResolver; import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.text.TextUtils; +import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import android.widget.ImageView; import android.widget.TabHost.TabContentFactory; +import org.mozilla.gecko.db.BrowserContract.Combined; +import org.mozilla.gecko.db.BrowserDB.URLColumns; + abstract public class AwesomeBarTab { abstract public String getTag(); abstract public int getTitleStringId(); abstract public void destroy(); abstract public TabContentFactory getFactory(); + private AwesomeBarTabs.OnUrlOpenListener mListener; + private LayoutInflater mInflater = null; + private ContentResolver mContentResolver = null; + private Resources mResources; // FIXME: This value should probably come from a prefs key public static final int MAX_RESULTS = 100; protected Context mContext = null; @@ -31,4 +45,70 @@ abstract public class AwesomeBarTab { public ImageView faviconView; public ImageView bookmarkIconView; } + + protected LayoutInflater getInflater() { + if (mInflater == null) { + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + return mInflater; + } + + protected AwesomeBarTabs.OnUrlOpenListener getUrlListener() { + return mListener; + } + + protected void setUrlListener(AwesomeBarTabs.OnUrlOpenListener listener) { + mListener = listener; + } + + protected ContentResolver getContentResolver() { + if (mContentResolver == null) { + mContentResolver = mContext.getContentResolver(); + } + return mContentResolver; + } + + protected Resources getResources() { + if (mResources == null) { + mResources = mContext.getResources(); + } + return mResources; + } + + protected String getReaderForUrl(String url) { + // FIXME: still need to define the final way to open items from + // reading list. For now, we're using an about:reader page. + return "about:reader?url=" + url; + } + + protected void updateFavicon(ImageView faviconView, Cursor cursor) { + byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON)); + if (b == null) { + faviconView.setImageDrawable(null); + } else { + Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length); + faviconView.setImageBitmap(bitmap); + } + } + + protected void updateTitle(TextView titleView, Cursor cursor) { + int titleIndex = cursor.getColumnIndexOrThrow(URLColumns.TITLE); + String title = cursor.getString(titleIndex); + + // Use the URL instead of an empty title for consistency with the normal URL + // bar view - this is the equivalent of getDisplayTitle() in Tab.java + if (TextUtils.isEmpty(title)) { + int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL); + title = cursor.getString(urlIndex); + } + + titleView.setText(title); + } + + protected void updateUrl(TextView urlView, Cursor cursor) { + int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL); + String url = cursor.getString(urlIndex); + + urlView.setText(url); + } } diff --git a/mobile/android/base/resources/layout/awesomebar_tabs.xml b/mobile/android/base/resources/layout/awesomebar_tabs.xml index 0525b5ff7f3..cdead1e14c4 100644 --- a/mobile/android/base/resources/layout/awesomebar_tabs.xml +++ b/mobile/android/base/resources/layout/awesomebar_tabs.xml @@ -19,9 +19,6 @@ android:layout_width="fill_parent" android:layout_height="fill_parent"> - -