diff options
Diffstat (limited to 'src/com/android/browser')
-rw-r--r-- | src/com/android/browser/BrowserActivity.java | 15 | ||||
-rw-r--r-- | src/com/android/browser/BrowserPreferencesPage.java | 12 | ||||
-rw-r--r-- | src/com/android/browser/BrowserProvider.java | 77 | ||||
-rw-r--r-- | src/com/android/browser/BrowserSettings.java | 64 | ||||
-rw-r--r-- | src/com/android/browser/search/DefaultSearchEngine.java | 118 | ||||
-rw-r--r-- | src/com/android/browser/search/OpenSearchSearchEngine.java | 295 | ||||
-rw-r--r-- | src/com/android/browser/search/SearchEngine.java | 57 | ||||
-rw-r--r-- | src/com/android/browser/search/SearchEngineInfo.java | 169 | ||||
-rw-r--r-- | src/com/android/browser/search/SearchEnginePreference.java | 62 | ||||
-rw-r--r-- | src/com/android/browser/search/SearchEngines.java | 73 |
10 files changed, 854 insertions, 88 deletions
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java index 5e55789..6f47788 100644 --- a/src/com/android/browser/BrowserActivity.java +++ b/src/com/android/browser/BrowserActivity.java @@ -111,6 +111,7 @@ import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.accounts.AccountManagerCallback; +import com.android.browser.search.SearchEngine; import com.android.common.Search; import com.android.common.speech.LoggingEvents; @@ -619,17 +620,9 @@ public class BrowserActivity extends Activity } }.execute(); - Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.putExtra(SearchManager.QUERY, url); - if (appData != null) { - intent.putExtra(SearchManager.APP_DATA, appData); - } - if (extraData != null) { - intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData); - } - intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName()); - startActivity(intent); + SearchEngine searchEngine = mSettings.getSearchEngine(); + if (searchEngine == null) return false; + searchEngine.startSearch(this, url, appData, extraData); return true; } diff --git a/src/com/android/browser/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java index 6426b99..9af66f1 100644 --- a/src/com/android/browser/BrowserPreferencesPage.java +++ b/src/com/android/browser/BrowserPreferencesPage.java @@ -16,24 +16,19 @@ package com.android.browser; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.preference.EditTextPreference; -import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; -import android.util.Log; import android.webkit.GeolocationPermissions; import android.webkit.ValueCallback; import android.webkit.WebStorage; -import android.webkit.WebView; + +import java.util.Map; +import java.util.Set; public class BrowserPreferencesPage extends PreferenceActivity implements Preference.OnPreferenceChangeListener { @@ -119,6 +114,7 @@ public class BrowserPreferencesPage extends PreferenceActivity // sync the shared preferences back to BrowserSettings BrowserSettings.getInstance().syncSharedPreferences( + getApplicationContext(), getPreferenceScreen().getSharedPreferences()); } diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java index bf1f9d5..96745e5 100644 --- a/src/com/android/browser/BrowserProvider.java +++ b/src/com/android/browser/BrowserProvider.java @@ -16,10 +16,10 @@ package com.android.browser; +import com.android.browser.search.SearchEngine; + import android.app.SearchManager; -import android.app.SearchableInfo; import android.app.backup.BackupManager; -import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentUris; @@ -27,28 +27,21 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.UriMatcher; import android.content.SharedPreferences.Editor; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; +import android.content.UriMatcher; import android.database.AbstractCursor; -import android.database.ContentObserver; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; -import android.os.Handler; import android.os.Process; import android.preference.PreferenceManager; import android.provider.Browser; -import android.provider.Settings; import android.provider.Browser.BookmarkColumns; import android.speech.RecognizerResultsIntent; import android.text.TextUtils; import android.util.Log; import android.util.Patterns; -import android.util.TypedValue; - import java.io.File; import java.io.FilenameFilter; @@ -165,7 +158,7 @@ public class BrowserProvider extends ContentProvider { // optionally a trailing slash, all matched as separate groups. private static final Pattern STRIP_URL_PATTERN = Pattern.compile("^(http://)(.*?)(/$)?"); - private SearchManager mSearchManager; + private BrowserSettings mSettings; public BrowserProvider() { } @@ -366,59 +359,10 @@ public class BrowserProvider extends ContentProvider { ed.commit(); } } - mSearchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE); - mShowWebSuggestionsSettingChangeObserver - = new ShowWebSuggestionsSettingChangeObserver(); - context.getContentResolver().registerContentObserver( - Settings.System.getUriFor( - Settings.System.SHOW_WEB_SUGGESTIONS), - true, mShowWebSuggestionsSettingChangeObserver); - updateShowWebSuggestions(); + mSettings = BrowserSettings.getInstance(); return true; } - /** - * This Observer will ensure that if the user changes the system - * setting of whether to display web suggestions, we will - * change accordingly. - */ - /* package */ class ShowWebSuggestionsSettingChangeObserver - extends ContentObserver { - public ShowWebSuggestionsSettingChangeObserver() { - super(new Handler()); - } - - @Override - public void onChange(boolean selfChange) { - updateShowWebSuggestions(); - } - } - - private ShowWebSuggestionsSettingChangeObserver - mShowWebSuggestionsSettingChangeObserver; - - // If non-null, then the system is set to show web suggestions, - // and this is the SearchableInfo to use to get them. - private SearchableInfo mSearchableInfo; - - /** - * Check the system settings to see whether web suggestions are - * allowed. If so, store the SearchableInfo to grab suggestions - * while the user is typing. - */ - private void updateShowWebSuggestions() { - mSearchableInfo = null; - Context context = getContext(); - if (Settings.System.getInt(context.getContentResolver(), - Settings.System.SHOW_WEB_SUGGESTIONS, - 1 /* default on */) == 1) { - ComponentName webSearchComponent = mSearchManager.getWebSearchActivity(); - if (webSearchComponent != null) { - mSearchableInfo = mSearchManager.getSearchableInfo(webSearchComponent); - } - } - } - private void fixPicasaBookmark() { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); Cursor cursor = db.rawQuery("SELECT _id FROM bookmarks WHERE " + @@ -875,12 +819,15 @@ public class BrowserProvider extends ContentProvider { || Patterns.WEB_URL.matcher(selectionArgs[0]).matches()) { return new MySuggestionCursor(c, null, ""); } else { - // get Google suggest if there is still space in the list + // get search suggestions if there is still space in the list if (myArgs != null && myArgs.length > 1 - && mSearchableInfo != null + && mSettings.getShowSearchSuggestions() && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) { - Cursor sc = mSearchManager.getSuggestions(mSearchableInfo, selectionArgs[0]); - return new MySuggestionCursor(c, sc, selectionArgs[0]); + SearchEngine searchEngine = mSettings.getSearchEngine(); + if (searchEngine != null && searchEngine.supportsSuggestions()) { + Cursor sc = searchEngine.getSuggestions(getContext(), selectionArgs[0]); + return new MySuggestionCursor(c, sc, selectionArgs[0]); + } } return new MySuggestionCursor(c, null, selectionArgs[0]); } diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java index 51b4eaa..6263eb3 100644 --- a/src/com/android/browser/BrowserSettings.java +++ b/src/com/android/browser/BrowserSettings.java @@ -17,14 +17,22 @@ package com.android.browser; +import com.android.browser.search.SearchEngine; +import com.android.browser.search.SearchEngines; + import android.app.ActivityManager; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; +import android.database.ContentObserver; +import android.os.Handler; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.util.Log; import android.webkit.CookieManager; import android.webkit.GeolocationPermissions; import android.webkit.ValueCallback; @@ -71,6 +79,8 @@ class BrowserSettings extends Observable { private boolean openInBackground; private String defaultTextEncodingName; private String homeUrl = ""; + private SearchEngine searchEngine; + private boolean showSearchSuggestions; private boolean autoFitPage; private boolean landscapeOnly; private boolean loadsPageInOverviewMode; @@ -121,6 +131,8 @@ class BrowserSettings extends Observable { public final static String PREF_CLEAR_COOKIES = "privacy_clear_cookies"; public final static String PREF_CLEAR_HISTORY = "privacy_clear_history"; public final static String PREF_HOMEPAGE = "homepage"; + public final static String PREF_SEARCH_ENGINE = "search_engine"; + public final static String PREF_SHOW_SEARCH_SUGGESTIONS = "show_search_suggestions"; public final static String PREF_CLEAR_FORM_DATA = "privacy_clear_form_data"; public final static String PREF_CLEAR_PASSWORDS = @@ -234,7 +246,7 @@ class BrowserSettings extends Observable { * stored in this BrowserSettings object. This will update all * observers of this object. */ - public void loadFromDb(Context ctx) { + public void loadFromDb(final Context ctx) { SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(ctx); // Set the default value for the Application Caches path. @@ -266,17 +278,41 @@ class BrowserSettings extends Observable { pageCacheCapacity = 1; } - // Load the defaults from the xml + final ContentResolver cr = ctx.getContentResolver(); + cr.registerContentObserver( + Settings.System.getUriFor(Settings.System.SHOW_WEB_SUGGESTIONS), false, + new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + SharedPreferences p = + PreferenceManager.getDefaultSharedPreferences(ctx); + updateShowWebSuggestions(cr, p); + } + }); + updateShowWebSuggestions(cr, p); + + // Load the defaults from the xml // This call is TOO SLOW, need to manually keep the defaults // in sync //PreferenceManager.setDefaultValues(ctx, R.xml.browser_preferences); - syncSharedPreferences(p); + syncSharedPreferences(ctx, p); } - /* package */ void syncSharedPreferences(SharedPreferences p) { + /* package */ void syncSharedPreferences(Context ctx, SharedPreferences p) { homeUrl = p.getString(PREF_HOMEPAGE, homeUrl); + String searchEngineName = p.getString(PREF_SEARCH_ENGINE, null); + if (searchEngine == null || !searchEngine.getName().equals(searchEngineName)) { + if (searchEngine != null) { + searchEngine.close(); + } + searchEngine = SearchEngines.get(ctx, searchEngineName); + } + Log.i(TAG, "Selected search engine: " + searchEngine); + showSearchSuggestions = p.getBoolean(PREF_SHOW_SEARCH_SUGGESTIONS, true); + // Persist to system settings + saveShowWebSuggestions(ctx.getContentResolver()); loadsImagesAutomatically = p.getBoolean("load_images", loadsImagesAutomatically); @@ -365,10 +401,30 @@ class BrowserSettings extends Observable { update(); } + private void saveShowWebSuggestions(ContentResolver cr) { + int value = showSearchSuggestions ? 1 : 0; + Settings.System.putInt(cr, Settings.System.SHOW_WEB_SUGGESTIONS, value); + } + + private void updateShowWebSuggestions(ContentResolver cr, SharedPreferences p) { + showSearchSuggestions = + Settings.System.getInt(cr, + Settings.System.SHOW_WEB_SUGGESTIONS, 1) == 1; + p.edit().putBoolean(PREF_SHOW_SEARCH_SUGGESTIONS, showSearchSuggestions).commit(); + } + public String getHomePage() { return homeUrl; } + public SearchEngine getSearchEngine() { + return searchEngine; + } + + public boolean getShowSearchSuggestions() { + return showSearchSuggestions; + } + public String getJsFlags() { return jsFlags; } diff --git a/src/com/android/browser/search/DefaultSearchEngine.java b/src/com/android/browser/search/DefaultSearchEngine.java new file mode 100644 index 0000000..42d274d --- /dev/null +++ b/src/com/android/browser/search/DefaultSearchEngine.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.browser.search; + +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.Browser; +import android.text.TextUtils; +import android.util.Log; + +public class DefaultSearchEngine implements SearchEngine { + + private static final String TAG = "DefaultSearchEngine"; + + private final SearchableInfo mSearchable; + + private final CharSequence mLabel; + + private DefaultSearchEngine(Context context, SearchableInfo searchable) { + mSearchable = searchable; + mLabel = loadLabel(context, mSearchable.getSearchActivity()); + } + + public static DefaultSearchEngine create(Context context) { + SearchManager searchManager = + (SearchManager) context.getSystemService(Context.SEARCH_SERVICE); + ComponentName name = searchManager.getWebSearchActivity(); + if (name == null) return null; + SearchableInfo searchable = searchManager.getSearchableInfo(name); + if (searchable == null) return null; + return new DefaultSearchEngine(context, searchable); + } + + private CharSequence loadLabel(Context context, ComponentName activityName) { + PackageManager pm = context.getPackageManager(); + try { + ActivityInfo ai = pm.getActivityInfo(activityName, 0); + return ai.loadLabel(pm); + } catch (PackageManager.NameNotFoundException ex) { + Log.e(TAG, "Web search activity not found: " + activityName); + return null; + } + } + + public String getName() { + String packageName = mSearchable.getSearchActivity().getPackageName(); + // Use "google" as name to avoid showing Google twice (app + OpenSearch) + if ("com.google.android.googlequicksearchbox".equals(packageName)) { + return "google"; + } else if ("com.android.quicksearchbox".equals(packageName)) { + return "google"; + } else { + return packageName; + } + } + + public CharSequence getLabel() { + return mLabel; + } + + public void startSearch(Context context, String query, Bundle appData, String extraData) { + try { + Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.putExtra(SearchManager.QUERY, query); + if (appData != null) { + intent.putExtra(SearchManager.APP_DATA, appData); + } + if (extraData != null) { + intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData); + } + intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + context.startActivity(intent); + } catch (ActivityNotFoundException ex) { + Log.e(TAG, "Web search activity not found: " + mSearchable.getSearchActivity()); + } + } + + public Cursor getSuggestions(Context context, String query) { + SearchManager searchManager = + (SearchManager) context.getSystemService(Context.SEARCH_SERVICE); + return searchManager.getSuggestions(mSearchable, query); + } + + public boolean supportsSuggestions() { + return !TextUtils.isEmpty(mSearchable.getSuggestAuthority()); + } + + public void close() { + } + + @Override + public String toString() { + return "ActivitySearchEngine{" + mSearchable + "}"; + } + +} diff --git a/src/com/android/browser/search/OpenSearchSearchEngine.java b/src/com/android/browser/search/OpenSearchSearchEngine.java new file mode 100644 index 0000000..e78a93c --- /dev/null +++ b/src/com/android/browser/search/OpenSearchSearchEngine.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.browser.search; + +import com.android.browser.R; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.params.HttpParams; +import org.apache.http.util.EntityUtils; +import org.json.JSONArray; +import org.json.JSONException; + +import android.app.SearchManager; +import android.content.Context; +import android.content.Intent; +import android.database.AbstractCursor; +import android.database.Cursor; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.net.http.AndroidHttpClient; +import android.os.Bundle; +import android.provider.Browser; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; + +/** + * Provides search suggestions, if any, for a given web search provider. + */ +public class OpenSearchSearchEngine implements SearchEngine { + + private static final String TAG = "OpenSearchSearchEngine"; + + private static final String USER_AGENT = "Android/1.0"; + private static final int HTTP_TIMEOUT_MS = 1000; + + // TODO: this should be defined somewhere + private static final String HTTP_TIMEOUT = "http.connection-manager.timeout"; + + // Indices of the columns in the below arrays. + private static final int COLUMN_INDEX_ID = 0; + private static final int COLUMN_INDEX_QUERY = 1; + private static final int COLUMN_INDEX_ICON = 2; + private static final int COLUMN_INDEX_TEXT_1 = 3; + private static final int COLUMN_INDEX_TEXT_2 = 4; + + // The suggestion columns used. If you are adding a new entry to these arrays make sure to + // update the list of indices declared above. + private static final String[] COLUMNS = new String[] { + "_id", + SearchManager.SUGGEST_COLUMN_QUERY, + SearchManager.SUGGEST_COLUMN_ICON_1, + SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, + }; + + private static final String[] COLUMNS_WITHOUT_DESCRIPTION = new String[] { + "_id", + SearchManager.SUGGEST_COLUMN_QUERY, + SearchManager.SUGGEST_COLUMN_ICON_1, + SearchManager.SUGGEST_COLUMN_TEXT_1, + }; + + private final SearchEngineInfo mSearchEngineInfo; + + private final AndroidHttpClient mHttpClient; + + public OpenSearchSearchEngine(Context context, SearchEngineInfo searchEngineInfo) { + mSearchEngineInfo = searchEngineInfo; + mHttpClient = AndroidHttpClient.newInstance(USER_AGENT); + HttpParams params = mHttpClient.getParams(); + params.setLongParameter(HTTP_TIMEOUT, HTTP_TIMEOUT_MS); + } + + public String getName() { + return mSearchEngineInfo.getName(); + } + + public CharSequence getLabel() { + return mSearchEngineInfo.getLabel(); + } + + public void startSearch(Context context, String query, Bundle appData, String extraData) { + String uri = mSearchEngineInfo.getSearchUriForQuery(query); + if (uri == null) { + Log.e(TAG, "Unable to get search URI for " + mSearchEngineInfo); + } else { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); + // Make sure the intent goes to the Browser itself + intent.setPackage(context.getPackageName()); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.putExtra(SearchManager.QUERY, query); + if (appData != null) { + intent.putExtra(SearchManager.APP_DATA, appData); + } + if (extraData != null) { + intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData); + } + intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + context.startActivity(intent); + } + } + + /** + * Queries for a given search term and returns a cursor containing + * suggestions ordered by best match. + */ + public Cursor getSuggestions(Context context, String query) { + if (TextUtils.isEmpty(query)) { + return null; + } + if (!isNetworkConnected(context)) { + Log.i(TAG, "Not connected to network."); + return null; + } + + String suggestUri = mSearchEngineInfo.getSuggestUriForQuery(query); + if (TextUtils.isEmpty(suggestUri)) { + // No suggest URI available for this engine + return null; + } + + try { + String content = readUrl(suggestUri); + if (content == null) return null; + /* The data format is a JSON array with items being regular strings or JSON arrays + * themselves. We are interested in the second and third elements, both of which + * should be JSON arrays. The second element/array contains the suggestions and the + * third element contains the descriptions. Some search engines don't support + * suggestion descriptions so the third element is optional. + */ + JSONArray results = new JSONArray(content); + JSONArray suggestions = results.getJSONArray(1); + JSONArray descriptions = null; + if (results.length() > 2) { + descriptions = results.getJSONArray(2); + // Some search engines given an empty array "[]" for descriptions instead of + // not including it in the response. + if (descriptions.length() == 0) { + descriptions = null; + } + } + return new SuggestionsCursor(suggestions, descriptions); + } catch (JSONException e) { + Log.w(TAG, "Error", e); + } + return null; + } + + /** + * Executes a GET request and returns the response content. + * + * @param url Request URI. + * @param requestHeaders Request headers. + * @return The response content. This is the empty string if the response + * contained no content. + */ + public String readUrl(String url) { + try { + HttpGet method = new HttpGet(url); + HttpResponse response = mHttpClient.execute(method); + if (response.getStatusLine().getStatusCode() == 200) { + return EntityUtils.toString(response.getEntity()); + } else { + Log.i(TAG, "Suggestion request failed"); + return null; + } + } catch (IOException e) { + Log.w(TAG, "Error", e); + return null; + } + } + + public boolean supportsSuggestions() { + return mSearchEngineInfo.supportsSuggestions(); + } + + public void close() { + mHttpClient.close(); + } + + private boolean isNetworkConnected(Context context) { + NetworkInfo networkInfo = getActiveNetworkInfo(context); + return networkInfo != null && networkInfo.isConnected(); + } + + private NetworkInfo getActiveNetworkInfo(Context context) { + ConnectivityManager connectivity = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity == null) { + return null; + } + return connectivity.getActiveNetworkInfo(); + } + + private static class SuggestionsCursor extends AbstractCursor { + + private final JSONArray mSuggestions; + + private final JSONArray mDescriptions; + + public SuggestionsCursor(JSONArray suggestions, JSONArray descriptions) { + mSuggestions = suggestions; + mDescriptions = descriptions; + } + + @Override + public int getCount() { + return mSuggestions.length(); + } + + @Override + public String[] getColumnNames() { + return (mDescriptions != null ? COLUMNS : COLUMNS_WITHOUT_DESCRIPTION); + } + + @Override + public String getString(int column) { + if (mPos != -1) { + if ((column == COLUMN_INDEX_QUERY) || (column == COLUMN_INDEX_TEXT_1)) { + try { + return mSuggestions.getString(mPos); + } catch (JSONException e) { + Log.w(TAG, "Error", e); + } + } else if (column == COLUMN_INDEX_TEXT_2) { + try { + return mDescriptions.getString(mPos); + } catch (JSONException e) { + Log.w(TAG, "Error", e); + } + } else if (column == COLUMN_INDEX_ICON) { + return String.valueOf(R.drawable.magnifying_glass); + } + } + return null; + } + + @Override + public double getDouble(int column) { + throw new UnsupportedOperationException(); + } + + @Override + public float getFloat(int column) { + throw new UnsupportedOperationException(); + } + + @Override + public int getInt(int column) { + throw new UnsupportedOperationException(); + } + + @Override + public long getLong(int column) { + if (column == COLUMN_INDEX_ID) { + return mPos; // use row# as the _Id + } + throw new UnsupportedOperationException(); + } + + @Override + public short getShort(int column) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNull(int column) { + throw new UnsupportedOperationException(); + } + } + + @Override + public String toString() { + return "OpenSearchSearchEngine{" + mSearchEngineInfo + "}"; + } + +} diff --git a/src/com/android/browser/search/SearchEngine.java b/src/com/android/browser/search/SearchEngine.java new file mode 100644 index 0000000..3d24d2e --- /dev/null +++ b/src/com/android/browser/search/SearchEngine.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.browser.search; + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; + +/** + * Interface for search engines. + */ +public interface SearchEngine { + + /** + * Gets the unique name of this search engine. + */ + public String getName(); + + /** + * Gets the human-readable name of this search engine. + */ + public CharSequence getLabel(); + + /** + * Starts a search. + */ + public void startSearch(Context context, String query, Bundle appData, String extraData); + + /** + * Gets search suggestions. + */ + public Cursor getSuggestions(Context context, String query); + + /** + * Checks whether this search engine supports search suggestions. + */ + public boolean supportsSuggestions(); + + /** + * Closes this search engine. + */ + public void close(); + +} diff --git a/src/com/android/browser/search/SearchEngineInfo.java b/src/com/android/browser/search/SearchEngineInfo.java new file mode 100644 index 0000000..6f0b1d5 --- /dev/null +++ b/src/com/android/browser/search/SearchEngineInfo.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.browser.search; + +import android.content.Context; +import android.content.res.Resources; +import android.text.TextUtils; +import android.util.Log; + +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Locale; + +/** + * Loads and holds data for a given web search engine. + */ +public class SearchEngineInfo { + + private static String TAG = "SearchEngineInfo"; + + // The fields of a search engine data array, defined in the same order as they appear in the + // all_search_engines.xml file. + // If you are adding/removing to this list, remember to update NUM_FIELDS below. + private static final int FIELD_LABEL = 0; + private static final int FIELD_KEYWORD = 1; + private static final int FIELD_FAVICON_URI = 2; + private static final int FIELD_SEARCH_URI = 3; + private static final int FIELD_ENCODING = 4; + private static final int FIELD_SUGGEST_URI = 5; + private static final int NUM_FIELDS = 6; + + // The OpenSearch URI template parameters that we support. + private static final String PARAMETER_LANGUAGE = "{language}"; + private static final String PARAMETER_SEARCH_TERMS = "{searchTerms}"; + private static final String PARAMETER_INPUT_ENCODING = "{inputEncoding}"; + + private final String mName; + + // The array of strings defining this search engine. The array values are in the same order as + // the above enumeration definition. + private final String[] mSearchEngineData; + + /** + * @throws IllegalArgumentException If the name does not refer to a valid search engine + */ + public SearchEngineInfo(Context context, String name) throws IllegalArgumentException { + mName = name; + + Resources res = context.getResources(); + int id_data = res.getIdentifier(name, "array", context.getPackageName()); + mSearchEngineData = res.getStringArray(id_data); + + if (mSearchEngineData == null) { + throw new IllegalArgumentException("No data found for " + name); + } + if (mSearchEngineData.length != NUM_FIELDS) { + throw new IllegalArgumentException( + name + " has invalid number of fields - " + mSearchEngineData.length); + } + if (TextUtils.isEmpty(mSearchEngineData[FIELD_SEARCH_URI])) { + throw new IllegalArgumentException(name + " has an empty search URI"); + } + + // Add the current language/country information to the URIs. + Locale locale = context.getResources().getConfiguration().locale; + StringBuilder language = new StringBuilder(locale.getLanguage()); + if (!TextUtils.isEmpty(locale.getCountry())) { + language.append('-'); + language.append(locale.getCountry()); + } + + String language_str = language.toString(); + mSearchEngineData[FIELD_SEARCH_URI] = + mSearchEngineData[FIELD_SEARCH_URI].replace(PARAMETER_LANGUAGE, language_str); + mSearchEngineData[FIELD_SUGGEST_URI] = + mSearchEngineData[FIELD_SUGGEST_URI].replace(PARAMETER_LANGUAGE, language_str); + + // Default to UTF-8 if not specified. + String enc = mSearchEngineData[FIELD_ENCODING]; + if (TextUtils.isEmpty(enc)) { + enc = "UTF-8"; + mSearchEngineData[FIELD_ENCODING] = enc; + } + + // Add the input encoding method to the URI. + mSearchEngineData[FIELD_SEARCH_URI] = + mSearchEngineData[FIELD_SEARCH_URI].replace(PARAMETER_INPUT_ENCODING, enc); + mSearchEngineData[FIELD_SUGGEST_URI] = + mSearchEngineData[FIELD_SUGGEST_URI].replace(PARAMETER_INPUT_ENCODING, enc); + } + + public String getName() { + return mName; + } + + public String getLabel() { + return mSearchEngineData[FIELD_LABEL]; + } + + /** + * Returns the URI for launching a web search with the given query (or null if there was no + * data available for this search engine). + */ + public String getSearchUriForQuery(String query) { + return getFormattedUri(searchUri(), query); + } + + /** + * Returns the URI for retrieving web search suggestions for the given query (or null if there + * was no data available for this search engine). + */ + public String getSuggestUriForQuery(String query) { + return getFormattedUri(suggestUri(), query); + } + + public boolean supportsSuggestions() { + return !TextUtils.isEmpty(suggestUri()); + } + + public String faviconUri() { + return mSearchEngineData[FIELD_FAVICON_URI]; + } + + private String suggestUri() { + return mSearchEngineData[FIELD_SUGGEST_URI]; + } + + private String searchUri() { + return mSearchEngineData[FIELD_SEARCH_URI]; + } + + /** + * Formats a launchable uri out of the template uri by replacing the template parameters with + * actual values. + */ + private String getFormattedUri(String templateUri, String query) { + if (TextUtils.isEmpty(templateUri)) { + return null; + } + + // Encode the query terms in the requested encoding (and fallback to UTF-8 if not). + String enc = mSearchEngineData[FIELD_ENCODING]; + try { + return templateUri.replace(PARAMETER_SEARCH_TERMS, URLEncoder.encode(query, enc)); + } catch (java.io.UnsupportedEncodingException e) { + Log.e(TAG, "Exception occured when encoding query " + query + " to " + enc); + return null; + } + } + + @Override + public String toString() { + return "SearchEngineInfo{" + Arrays.toString(mSearchEngineData) + "}"; + } + +} diff --git a/src/com/android/browser/search/SearchEnginePreference.java b/src/com/android/browser/search/SearchEnginePreference.java new file mode 100644 index 0000000..18ce495 --- /dev/null +++ b/src/com/android/browser/search/SearchEnginePreference.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.browser.search; + +import com.android.browser.R; + +import android.app.SearchManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.preference.ListPreference; +import android.util.AttributeSet; +import android.util.Log; + +import java.util.ArrayList; + +class SearchEnginePreference extends ListPreference { + + private static final String TAG = "SearchEnginePreference"; + + public SearchEnginePreference(Context context, AttributeSet attrs) { + super(context, attrs); + + ArrayList<CharSequence> entryValues = new ArrayList<CharSequence>(); + ArrayList<CharSequence> entries = new ArrayList<CharSequence>(); + + SearchEngine defaultSearchEngine = SearchEngines.getDefaultSearchEngine(context); + String defaultSearchEngineName = null; + if (defaultSearchEngine != null) { + defaultSearchEngineName = defaultSearchEngine.getName(); + entryValues.add(defaultSearchEngineName); + entries.add(defaultSearchEngine.getLabel()); + } + for (SearchEngineInfo searchEngineInfo : SearchEngines.getSearchEngineInfos(context)) { + String name = searchEngineInfo.getName(); + // Skip entry with same name as default provider + if (!name.equals(defaultSearchEngineName)) { + entryValues.add(name); + entries.add(searchEngineInfo.getLabel()); + } + } + + setEntryValues(entryValues.toArray(new CharSequence[entryValues.size()])); + setEntries(entries.toArray(new CharSequence[entries.size()])); + } + +} diff --git a/src/com/android/browser/search/SearchEngines.java b/src/com/android/browser/search/SearchEngines.java new file mode 100644 index 0000000..62690e7 --- /dev/null +++ b/src/com/android/browser/search/SearchEngines.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.browser.search; + +import com.android.browser.R; + +import android.app.SearchManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +public class SearchEngines { + + private static final String TAG = "SearchEngines"; + + public static SearchEngine getDefaultSearchEngine(Context context) { + return DefaultSearchEngine.create(context); + } + + public static List<SearchEngineInfo> getSearchEngineInfos(Context context) { + ArrayList<SearchEngineInfo> searchEngineInfos = new ArrayList<SearchEngineInfo>(); + Resources res = context.getResources(); + String[] searchEngines = res.getStringArray(R.array.search_engines); + for (int i = 0; i < searchEngines.length; i++) { + String name = searchEngines[i]; + SearchEngineInfo info = new SearchEngineInfo(context, name); + searchEngineInfos.add(info); + } + return searchEngineInfos; + } + + public static SearchEngine get(Context context, String name) { + // TODO: cache + SearchEngine defaultSearchEngine = getDefaultSearchEngine(context); + if (TextUtils.isEmpty(name) + || (defaultSearchEngine != null && name.equals(defaultSearchEngine.getName()))) { + return defaultSearchEngine; + } + SearchEngineInfo searchEngineInfo = getSearchEngineInfo(context, name); + if (searchEngineInfo == null) return defaultSearchEngine; + return new OpenSearchSearchEngine(context, searchEngineInfo); + } + + private static SearchEngineInfo getSearchEngineInfo(Context context, String name) { + try { + return new SearchEngineInfo(context, name); + } catch (IllegalArgumentException exception) { + Log.e(TAG, "Cannot load search engine " + name, exception); + return null; + } + } + +} |