mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 586885 - Add search suggestions to AwesomeBar. r=mfinkle,lucasr
This commit is contained in:
parent
469c975cb5
commit
4ab9b4aa7f
@ -11,6 +11,7 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
@ -42,6 +43,7 @@ import android.widget.TabWidget;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
@ -54,6 +56,9 @@ import org.json.JSONObject;
|
||||
public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
|
||||
private static final String LOGTAG = "GeckoAwesomeBar";
|
||||
|
||||
private static final int SUGGESTION_TIMEOUT = 2000;
|
||||
private static final int SUGGESTION_MAX = 3;
|
||||
|
||||
static final String URL_KEY = "url";
|
||||
static final String CURRENT_URL_KEY = "currenturl";
|
||||
static final String TYPE_KEY = "type";
|
||||
@ -67,6 +72,11 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
|
||||
private ImageButton mGoButton;
|
||||
private ContentResolver mResolver;
|
||||
private ContextMenuSubject mContextMenuSubject;
|
||||
private SuggestClient mSuggestClient;
|
||||
private AsyncTask<String, Void, ArrayList<String>> mSuggestTask;
|
||||
|
||||
private static String sSuggestEngine;
|
||||
private static String sSuggestTemplate;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@ -91,8 +101,20 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
|
||||
openUrlAndFinish(url);
|
||||
}
|
||||
|
||||
public void onSearch(String engine) {
|
||||
openSearchAndFinish(mText.getText().toString(), engine);
|
||||
public void onSearch(String engine, String text) {
|
||||
openSearchAndFinish(text, engine);
|
||||
}
|
||||
|
||||
public void onEditSuggestion(final String text) {
|
||||
GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
|
||||
public void run() {
|
||||
mText.setText(text);
|
||||
mText.setSelection(mText.getText().length());
|
||||
mText.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -159,6 +181,24 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
|
||||
|
||||
// no composition string. It is safe to update IME flags.
|
||||
updateGoButton(text);
|
||||
|
||||
// cancel previous query
|
||||
if (mSuggestTask != null) {
|
||||
mSuggestTask.cancel(true);
|
||||
}
|
||||
|
||||
if (mSuggestClient != null) {
|
||||
mSuggestTask = new AsyncTask<String, Void, ArrayList<String>>() {
|
||||
protected ArrayList<String> doInBackground(String... query) {
|
||||
return mSuggestClient.query(query[0]);
|
||||
}
|
||||
|
||||
protected void onPostExecute(ArrayList<String> suggestions) {
|
||||
mAwesomeTabs.setSuggestions(suggestions);
|
||||
}
|
||||
};
|
||||
mSuggestTask.execute(text);
|
||||
}
|
||||
}
|
||||
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
@ -190,14 +230,46 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
|
||||
registerForContextMenu(mAwesomeTabs.findViewById(R.id.bookmarks_list));
|
||||
registerForContextMenu(mAwesomeTabs.findViewById(R.id.history_list));
|
||||
|
||||
if (sSuggestTemplate == null) {
|
||||
loadSuggestClientFromPrefs();
|
||||
} else {
|
||||
loadSuggestClient();
|
||||
}
|
||||
|
||||
GeckoAppShell.registerGeckoEventListener("SearchEngines:Data", this);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
|
||||
}
|
||||
|
||||
private void loadSuggestClientFromPrefs() {
|
||||
GeckoAppShell.getHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
SharedPreferences prefs = getSearchPreferences();
|
||||
sSuggestEngine = prefs.getString("suggestEngine", null);
|
||||
sSuggestTemplate = prefs.getString("suggestTemplate", null);
|
||||
if (sSuggestTemplate != null) {
|
||||
loadSuggestClient();
|
||||
mAwesomeTabs.setSuggestEngine(sSuggestEngine, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadSuggestClient() {
|
||||
mSuggestClient = new SuggestClient(GeckoApp.mAppContext, sSuggestTemplate, SUGGESTION_TIMEOUT, SUGGESTION_MAX);
|
||||
}
|
||||
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
try {
|
||||
if (event.equals("SearchEngines:Data")) {
|
||||
mAwesomeTabs.setSearchEngines(message.getJSONArray("searchEngines"));
|
||||
final String suggestEngine = message.optString("suggestEngine");
|
||||
final String suggestTemplate = message.optString("suggestTemplate");
|
||||
if (!TextUtils.equals(suggestTemplate, sSuggestTemplate)) {
|
||||
saveSuggestEngineData(suggestEngine, suggestTemplate);
|
||||
sSuggestEngine = suggestEngine;
|
||||
sSuggestTemplate = suggestTemplate;
|
||||
loadSuggestClient();
|
||||
}
|
||||
mAwesomeTabs.setSearchEngines(suggestEngine, message.getJSONArray("searchEngines"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// do nothing
|
||||
@ -205,6 +277,18 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void saveSuggestEngineData(final String suggestEngine, final String suggestTemplate) {
|
||||
GeckoAppShell.getHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
SharedPreferences prefs = getSearchPreferences();
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString("suggestEngine", suggestEngine);
|
||||
editor.putString("suggestTemplate", suggestTemplate);
|
||||
editor.commit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfiguration) {
|
||||
super.onConfigurationChanged(newConfiguration);
|
||||
@ -678,4 +762,8 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
|
||||
mOnKeyPreImeListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
private SharedPreferences getSearchPreferences() {
|
||||
return getSharedPreferences("search.prefs", MODE_PRIVATE);
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import android.widget.TextView;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
@ -67,9 +68,10 @@ public class AwesomeBarTabs extends TabHost {
|
||||
private boolean mInflated;
|
||||
private LayoutInflater mInflater;
|
||||
private OnUrlOpenListener mUrlOpenListener;
|
||||
private JSONArray mSearchEngines;
|
||||
private ContentResolver mContentResolver;
|
||||
private ContentObserver mContentObserver;
|
||||
private SearchEngine mSuggestEngine;
|
||||
private ArrayList<SearchEngine> mSearchEngines;
|
||||
|
||||
private BookmarksQueryTask mBookmarksQueryTask;
|
||||
private HistoryQueryTask mHistoryQueryTask;
|
||||
@ -86,16 +88,24 @@ public class AwesomeBarTabs extends TabHost {
|
||||
|
||||
public interface OnUrlOpenListener {
|
||||
public void onUrlOpen(String url);
|
||||
public void onSearch(String engine);
|
||||
public void onSearch(String engine, String text);
|
||||
public void onEditSuggestion(String suggestion);
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
private class AwesomeEntryViewHolder {
|
||||
public TextView titleView;
|
||||
public TextView urlView;
|
||||
public ImageView faviconView;
|
||||
public ImageView starView;
|
||||
}
|
||||
|
||||
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<? extends Map<String, ?>> groupData,
|
||||
int groupLayout, String[] groupFrom, int[] groupTo,
|
||||
@ -108,12 +118,12 @@ public class AwesomeBarTabs extends TabHost {
|
||||
@Override
|
||||
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
|
||||
View convertView, ViewGroup parent) {
|
||||
ViewHolder viewHolder = null;
|
||||
AwesomeEntryViewHolder viewHolder = null;
|
||||
|
||||
if (convertView == null) {
|
||||
convertView = mInflater.inflate(R.layout.awesomebar_row, null);
|
||||
|
||||
viewHolder = new ViewHolder();
|
||||
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);
|
||||
@ -121,7 +131,7 @@ public class AwesomeBarTabs extends TabHost {
|
||||
|
||||
convertView.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (ViewHolder) convertView.getTag();
|
||||
viewHolder = (AwesomeEntryViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -256,7 +266,7 @@ public class AwesomeBarTabs extends TabHost {
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
int viewType = getItemViewType(position);
|
||||
ViewHolder viewHolder = null;
|
||||
AwesomeEntryViewHolder viewHolder = null;
|
||||
|
||||
if (convertView == null) {
|
||||
if (viewType == VIEW_TYPE_ITEM)
|
||||
@ -264,7 +274,7 @@ public class AwesomeBarTabs extends TabHost {
|
||||
else
|
||||
convertView = mInflater.inflate(R.layout.awesomebar_folder_row, null);
|
||||
|
||||
viewHolder = new ViewHolder();
|
||||
viewHolder = new AwesomeEntryViewHolder();
|
||||
viewHolder.titleView = (TextView) convertView.findViewById(R.id.title);
|
||||
viewHolder.faviconView = (ImageView) convertView.findViewById(R.id.favicon);
|
||||
|
||||
@ -273,7 +283,7 @@ public class AwesomeBarTabs extends TabHost {
|
||||
|
||||
convertView.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (ViewHolder) convertView.getTag();
|
||||
viewHolder = (AwesomeEntryViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
Cursor cursor = getCursor();
|
||||
@ -612,42 +622,45 @@ public class AwesomeBarTabs extends TabHost {
|
||||
public void onClick();
|
||||
}
|
||||
|
||||
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(cursor.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);
|
||||
}
|
||||
}
|
||||
|
||||
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 = "";
|
||||
@ -658,58 +671,123 @@ public class AwesomeBarTabs extends TabHost {
|
||||
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 additional search engines if search field is empty
|
||||
// don't show search engines or suggestions if search field is empty
|
||||
if (mSearchTerm.length() == 0)
|
||||
return resultCount;
|
||||
|
||||
return resultCount + mSearchEngines.length();
|
||||
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) {
|
||||
final int resultCount = super.getCount();
|
||||
if (position < resultCount)
|
||||
return new AwesomeBarCursorItem((Cursor) super.getItem(position));
|
||||
int engineIndex = getEngineIndex(position);
|
||||
|
||||
JSONObject engine;
|
||||
String engineName = null;
|
||||
try {
|
||||
engine = mSearchEngines.getJSONObject(position - resultCount);
|
||||
engineName = engine.getString("name");
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error getting search engine JSON", e);
|
||||
if (engineIndex == -1) {
|
||||
// return awesomebar result
|
||||
position -= getSuggestEngineCount();
|
||||
return new AwesomeBarCursorItem((Cursor) super.getItem(position));
|
||||
}
|
||||
|
||||
return new AwesomeBarSearchEngineItem(engineName);
|
||||
// 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) {
|
||||
ViewHolder viewHolder = null;
|
||||
if (getItemViewType(position) == ROW_SEARCH) {
|
||||
SearchEntryViewHolder viewHolder = null;
|
||||
|
||||
if (convertView == null) {
|
||||
convertView = mInflater.inflate(R.layout.awesomebar_row, null);
|
||||
if (convertView == null) {
|
||||
convertView = mInflater.inflate(R.layout.awesomebar_suggestion_row, null);
|
||||
|
||||
viewHolder = new ViewHolder();
|
||||
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.starView = (ImageView) convertView.findViewById(R.id.bookmark_star);
|
||||
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);
|
||||
convertView.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (SearchEntryViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
bindSearchEngineView(getEngine(getEngineIndex(position)), viewHolder);
|
||||
} else {
|
||||
viewHolder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
AwesomeEntryViewHolder viewHolder = null;
|
||||
|
||||
final int resultCount = super.getCount();
|
||||
if (position < resultCount) {
|
||||
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.starView = (ImageView) convertView.findViewById(R.id.bookmark_star);
|
||||
|
||||
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);
|
||||
@ -718,45 +796,69 @@ public class AwesomeBarTabs extends TabHost {
|
||||
updateUrl(viewHolder.urlView, cursor);
|
||||
updateFavicon(viewHolder.faviconView, cursor);
|
||||
updateBookmarkStar(viewHolder.starView, cursor);
|
||||
} else {
|
||||
bindSearchEngineView(position - resultCount, viewHolder);
|
||||
}
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void bindSearchEngineView(int position, ViewHolder viewHolder) {
|
||||
String name;
|
||||
String iconURI;
|
||||
String searchText = getResources().getString(R.string.awesomebar_search_engine, mSearchTerm);
|
||||
try {
|
||||
JSONObject searchEngine = mSearchEngines.getJSONObject(position);
|
||||
name = searchEngine.getString("name");
|
||||
iconURI = searchEngine.getString("iconURI");
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error getting search engine JSON", e);
|
||||
return;
|
||||
// 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);
|
||||
}
|
||||
|
||||
viewHolder.titleView.setText(name);
|
||||
viewHolder.urlView.setText(searchText);
|
||||
Drawable drawable = getDrawableFromDataURI(iconURI);
|
||||
viewHolder.faviconView.setImageDrawable(drawable);
|
||||
viewHolder.starView.setVisibility(View.GONE);
|
||||
// hide extra suggestions that have been recycled
|
||||
for (++i; i < recycledSuggestionCount; i++) {
|
||||
suggestionView.getChildAt(i).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -767,7 +869,7 @@ public class AwesomeBarTabs extends TabHost {
|
||||
|
||||
mContext = context;
|
||||
mInflated = false;
|
||||
mSearchEngines = new JSONArray();
|
||||
mSearchEngines = new ArrayList<SearchEngine>();
|
||||
mContentResolver = context.getContentResolver();
|
||||
mContentObserver = null;
|
||||
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
@ -1043,10 +1145,98 @@ public class AwesomeBarTabs extends TabHost {
|
||||
mAllPagesCursorAdapter.filter(searchTerm);
|
||||
}
|
||||
|
||||
public void setSearchEngines(final JSONArray engines) {
|
||||
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<String> suggestions;
|
||||
|
||||
public SearchEngine(String name) {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
public SearchEngine(String name, Drawable icon) {
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.suggestions = new ArrayList<String>();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
GeckoAppShell.getMainHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
mSearchEngines = engines;
|
||||
mSuggestEngine = suggestEngine;
|
||||
mAllPagesCursorAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets suggestions associated with the current suggest engine.
|
||||
* If there is no suggest engine, this does nothing.
|
||||
*/
|
||||
public void setSuggestions(final ArrayList<String> suggestions) {
|
||||
GeckoAppShell.getMainHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
if (mSuggestEngine != null) {
|
||||
mSuggestEngine.suggestions = suggestions;
|
||||
mAllPagesCursorAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets search engines to be shown for user-entered queries.
|
||||
*/
|
||||
public void setSearchEngines(String suggestEngine, JSONArray engines) {
|
||||
final ArrayList<SearchEngine> searchEngines = new ArrayList<SearchEngine>();
|
||||
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 {
|
||||
searchEngines.add(new SearchEngine(name, icon));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error getting search engine JSON", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GeckoAppShell.getMainHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
mSearchEngines = searchEngines;
|
||||
mAllPagesCursorAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
@ -1058,7 +1248,10 @@ public class AwesomeBarTabs extends TabHost {
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
hideSoftInput(this);
|
||||
// we should only have to hide the soft keyboard once - when the user
|
||||
// initially touches the screen
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN)
|
||||
hideSoftInput(this);
|
||||
|
||||
// the android docs make no sense, but returning false will cause this and other
|
||||
// motion events to be sent to the view the user tapped on
|
||||
|
@ -98,6 +98,7 @@ FENNEC_JAVA_FILES = \
|
||||
RemoteTabs.java \
|
||||
SetupScreen.java \
|
||||
SiteIdentityPopup.java \
|
||||
SuggestClient.java \
|
||||
SurfaceBits.java \
|
||||
Tab.java \
|
||||
Tabs.java \
|
||||
@ -254,6 +255,8 @@ RES_LAYOUT = \
|
||||
res/layout/awesomebar_folder_row.xml \
|
||||
res/layout/awesomebar_header_row.xml \
|
||||
res/layout/awesomebar_row.xml \
|
||||
res/layout/awesomebar_suggestion_item.xml \
|
||||
res/layout/awesomebar_suggestion_row.xml \
|
||||
res/layout/awesomebar_search.xml \
|
||||
res/layout/awesomebar_tab_indicator.xml \
|
||||
res/layout/awesomebar_tabs.xml \
|
||||
@ -815,6 +818,7 @@ MOZ_ANDROID_DRAWABLES += \
|
||||
mobile/android/base/resources/drawable/remote_tabs_group_bg_repeat.xml \
|
||||
mobile/android/base/resources/drawable/start.png \
|
||||
mobile/android/base/resources/drawable/site_security_level.xml \
|
||||
mobile/android/base/resources/drawable/suggestion_selector.xml \
|
||||
mobile/android/base/resources/drawable/tabs_button.xml \
|
||||
mobile/android/base/resources/drawable/tabs_level.xml \
|
||||
mobile/android/base/resources/drawable/tabs_tray_bg_repeat.xml \
|
||||
|
127
mobile/android/base/SuggestClient.java
Normal file
127
mobile/android/base/SuggestClient.java
Normal file
@ -0,0 +1,127 @@
|
||||
/* 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 org.json.JSONArray;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Use network-based search suggestions.
|
||||
*/
|
||||
public class SuggestClient {
|
||||
private static final String LOGTAG = "GeckoSuggestClient";
|
||||
private static final String USER_AGENT = GeckoApp.mAppContext.getDefaultUAString();
|
||||
|
||||
private final Context mContext;
|
||||
private final int mTimeout;
|
||||
|
||||
// should contain the string "__searchTerms__", which is replaced with the query
|
||||
private final String mSuggestTemplate;
|
||||
|
||||
// the maximum number of suggestions to return
|
||||
private final int mMaxResults;
|
||||
|
||||
public SuggestClient(Context context, String suggestTemplate, int timeout, int maxResults) {
|
||||
mContext = context;
|
||||
mMaxResults = maxResults;
|
||||
mSuggestTemplate = suggestTemplate;
|
||||
mTimeout = timeout;
|
||||
}
|
||||
|
||||
public SuggestClient(Context context, String suggestTemplate, int timeout) {
|
||||
this(context, suggestTemplate, timeout, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries for a given search term and returns an ArrayList of suggestions.
|
||||
*/
|
||||
public ArrayList<String> query(String query) {
|
||||
ArrayList<String> suggestions = new ArrayList<String>();
|
||||
if (TextUtils.isEmpty(mSuggestTemplate) || TextUtils.isEmpty(query)) {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
if (!isNetworkConnected()) {
|
||||
Log.i(LOGTAG, "Not connected to network");
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
try {
|
||||
String encoded = URLEncoder.encode(query, "UTF-8");
|
||||
String suggestUri = mSuggestTemplate.replace("__searchTerms__", encoded);
|
||||
|
||||
URL url = new URL(suggestUri);
|
||||
String json = null;
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
try {
|
||||
urlConnection.setConnectTimeout(mTimeout);
|
||||
urlConnection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
|
||||
json = convertStreamToString(in);
|
||||
} finally {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
|
||||
if (json != null) {
|
||||
/*
|
||||
* Sample result:
|
||||
* ["foo",["food network","foothill college","foot locker",...]]
|
||||
*/
|
||||
JSONArray results = new JSONArray(json);
|
||||
JSONArray jsonSuggestions = results.getJSONArray(1);
|
||||
|
||||
int added = 0;
|
||||
for (int i = 0; (i < jsonSuggestions.length()) && (added < mMaxResults); i++) {
|
||||
String suggestion = jsonSuggestions.getString(i);
|
||||
if (!suggestion.equalsIgnoreCase(query)) {
|
||||
suggestions.add(suggestion);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(LOGTAG, "Suggestion query failed");
|
||||
}
|
||||
} catch (InterruptedIOException e) {
|
||||
Log.d(LOGTAG, "Suggestion query interrupted");
|
||||
} catch (Exception e) {
|
||||
Log.w(LOGTAG, "Error", e);
|
||||
}
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
private boolean isNetworkConnected() {
|
||||
NetworkInfo networkInfo = getActiveNetworkInfo();
|
||||
return networkInfo != null && networkInfo.isConnected();
|
||||
}
|
||||
|
||||
private NetworkInfo getActiveNetworkInfo() {
|
||||
ConnectivityManager connectivity = (ConnectivityManager) mContext
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivity == null)
|
||||
return null;
|
||||
return connectivity.getActiveNetworkInfo();
|
||||
}
|
||||
|
||||
private String convertStreamToString(java.io.InputStream is) {
|
||||
try {
|
||||
return new java.util.Scanner(is).useDelimiter("\\A").next();
|
||||
} catch (java.util.NoSuchElementException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/suggestion_pressed" />
|
||||
|
||||
<padding android:left="7dp"
|
||||
android:top="7dp"
|
||||
android:right="7dp"
|
||||
android:bottom="7dp" />
|
||||
|
||||
<corners android:bottomRightRadius="4dp"
|
||||
android:bottomLeftRadius="4dp"
|
||||
android:topLeftRadius="4dp"
|
||||
android:topRightRadius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_enabled="true">
|
||||
<shape>
|
||||
<solid android:color="@color/suggestion_primary" />
|
||||
|
||||
<padding android:left="7dp"
|
||||
android:top="7dp"
|
||||
android:right="7dp"
|
||||
android:bottom="7dp" />
|
||||
|
||||
<corners android:bottomRightRadius="4dp"
|
||||
android:bottomLeftRadius="4dp"
|
||||
android:topLeftRadius="4dp"
|
||||
android:topRightRadius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</selector>
|
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/suggestion_selector"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/suggestion_magnifier"
|
||||
android:src="@drawable/ic_awesomebar_search"
|
||||
android:layout_marginRight="3dip"
|
||||
android:layout_width="16dip"
|
||||
android:layout_height="16dip" />
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/suggestion_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorPrimary"/>
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dip">
|
||||
|
||||
<ImageView android:id="@+id/suggestion_icon"
|
||||
android:layout_width="32dip"
|
||||
android:layout_height="32dip"
|
||||
android:layout_marginRight="10dip"
|
||||
android:minWidth="32dip"
|
||||
android:minHeight="32dip"
|
||||
android:scaleType="fitCenter"/>
|
||||
|
||||
<org.mozilla.gecko.FlowLayout android:id="@+id/suggestion_layout"
|
||||
android:layout_toRightOf="@id/suggestion_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include layout="@layout/awesomebar_suggestion_item"
|
||||
android:id="@+id/suggestion_user_entered"/>
|
||||
|
||||
</org.mozilla.gecko.FlowLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -14,5 +14,7 @@
|
||||
<color name="identity_identified">#B7D46A</color>
|
||||
<color name="tabs_counter_color">#C7D1DB</color>
|
||||
<color name="url_bar_text_highlight">#FF9500</color>
|
||||
<color name="suggestion_primary">#dddddd</color>
|
||||
<color name="suggestion_pressed">#bbbbbb</color>
|
||||
</resources>
|
||||
|
||||
|
@ -4999,15 +4999,41 @@ var SearchEngines = {
|
||||
};
|
||||
});
|
||||
|
||||
let suggestTemplate = null;
|
||||
let suggestEngine = null;
|
||||
if (Services.prefs.getBoolPref("browser.search.suggest.enabled")) {
|
||||
let engine = this.getSuggestionEngine();
|
||||
if (engine != null) {
|
||||
suggestEngine = engine.name;
|
||||
suggestTemplate = engine.getSubmission("__searchTerms__", "application/x-suggestions+json").uri.spec;
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageToJava({
|
||||
gecko: {
|
||||
type: "SearchEngines:Data",
|
||||
searchEngines: searchEngines
|
||||
searchEngines: searchEngines,
|
||||
suggestEngine: suggestEngine,
|
||||
suggestTemplate: suggestTemplate
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getSuggestionEngine: function () {
|
||||
let engines = [ Services.search.currentEngine,
|
||||
Services.search.defaultEngine,
|
||||
Services.search.originalDefaultEngine ];
|
||||
|
||||
for (let i = 0; i < engines.length; i++) {
|
||||
let engine = engines[i];
|
||||
if (engine && engine.supportsResponseType("application/x-suggestions+json"))
|
||||
return engine;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
addEngine: function addEngine(aElement) {
|
||||
let form = aElement.form;
|
||||
let charset = aElement.ownerDocument.characterSet;
|
||||
|
Loading…
Reference in New Issue
Block a user