summaryrefslogtreecommitdiffstats
path: root/src/com/android/browser
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/browser')
-rw-r--r--src/com/android/browser/BrowserActivity.java215
-rw-r--r--src/com/android/browser/BrowserBookmarksPage.java2
-rw-r--r--src/com/android/browser/BrowserDownloadAdapter.java221
-rw-r--r--src/com/android/browser/BrowserDownloadPage.java535
-rw-r--r--src/com/android/browser/BrowserPreferencesPage.java12
-rw-r--r--src/com/android/browser/BrowserProvider.java78
-rw-r--r--src/com/android/browser/BrowserSettings.java63
-rw-r--r--src/com/android/browser/FindDialog.java161
-rw-r--r--src/com/android/browser/OpenDownloadReceiver.java42
-rw-r--r--src/com/android/browser/SelectDialog.java85
-rw-r--r--src/com/android/browser/SystemAllowGeolocationOrigins.java2
-rw-r--r--src/com/android/browser/Tab.java115
-rw-r--r--src/com/android/browser/TabControl.java9
-rw-r--r--src/com/android/browser/WebDialog.java79
-rw-r--r--src/com/android/browser/search/DefaultSearchEngine.java123
-rw-r--r--src/com/android/browser/search/OpenSearchSearchEngine.java298
-rw-r--r--src/com/android/browser/search/SearchEngine.java64
-rw-r--r--src/com/android/browser/search/SearchEngineInfo.java169
-rw-r--r--src/com/android/browser/search/SearchEnginePreference.java62
-rw-r--r--src/com/android/browser/search/SearchEngines.java73
20 files changed, 1340 insertions, 1068 deletions
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index dda7820..6271e24 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -18,6 +18,7 @@ package com.android.browser;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.DownloadManager;
import android.app.ProgressDialog;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
@@ -111,6 +112,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;
@@ -144,13 +146,6 @@ public class BrowserActivity extends Activity
private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
- // These are single-character shortcuts for searching popular sources.
- private static final int SHORTCUT_INVALID = 0;
- private static final int SHORTCUT_GOOGLE_SEARCH = 1;
- private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2;
- private static final int SHORTCUT_DICTIONARY_SEARCH = 3;
- private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4;
-
private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
@Override
public Void doInBackground(File... files) {
@@ -190,6 +185,9 @@ public class BrowserActivity extends Activity
mResolver = getContentResolver();
+ // Keep a settings instance handy.
+ mSettings = BrowserSettings.getInstance();
+
// If this was a web search request, pass it on to the default web
// search provider and finish this activity.
if (handleWebSearchIntent(getIntent())) {
@@ -224,8 +222,6 @@ public class BrowserActivity extends Activity
// Open the icon database and retain all the bookmark urls for favicons
retainIconsOnStartup();
- // Keep a settings instance handy.
- mSettings = BrowserSettings.getInstance();
mSettings.setTabControl(mTabControl);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
@@ -358,6 +354,15 @@ public class BrowserActivity extends Activity
attachTabToContentView(mTabControl.getCurrentTab());
}
+ // Delete old thumbnails to save space
+ File dir = mTabControl.getThumbnailDir();
+ if (dir.exists()) {
+ for (String child : dir.list()) {
+ File f = new File(dir, child);
+ f.delete();
+ }
+ }
+
// Read JavaScript flags if it exists.
String jsFlags = mSettings.getJsFlags();
if (jsFlags.trim().length() != 0) {
@@ -548,21 +553,6 @@ public class BrowserActivity extends Activity
}
}
- private int parseUrlShortcut(String url) {
- if (url == null) return SHORTCUT_INVALID;
-
- // FIXME: quick search, need to be customized by setting
- if (url.length() > 2 && url.charAt(1) == ' ') {
- switch (url.charAt(0)) {
- case 'g': return SHORTCUT_GOOGLE_SEARCH;
- case 'w': return SHORTCUT_WIKIPEDIA_SEARCH;
- case 'd': return SHORTCUT_DICTIONARY_SEARCH;
- case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH;
- }
- }
- return SHORTCUT_INVALID;
- }
-
/**
* Launches the default web search activity with the query parameters if the given intent's data
* are identified as plain search terms and not URLs/shortcuts.
@@ -601,11 +591,10 @@ public class BrowserActivity extends Activity
// But currently, we get the user-typed URL from search box as well.
String url = fixUrl(inUrl).trim();
- // URLs and site specific search shortcuts are handled by the regular flow of control, so
+ // URLs are handled by the regular flow of control, so
// return early.
if (Patterns.WEB_URL.matcher(url).matches()
- || ACCEPTED_URI_SCHEMA.matcher(url).matches()
- || parseUrlShortcut(url) != SHORTCUT_INVALID) {
+ || ACCEPTED_URI_SCHEMA.matcher(url).matches()) {
return false;
}
@@ -619,23 +608,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());
-
- // can't be sure there is an activity for the Intent
- try {
- startActivity(intent);
- } catch (ActivityNotFoundException ex) {
- return false;
- }
+ SearchEngine searchEngine = mSettings.getSearchEngine();
+ if (searchEngine == null) return false;
+ searchEngine.startSearch(this, url, appData, extraData);
return true;
}
@@ -840,6 +815,13 @@ public class BrowserActivity extends Activity
if (mainView == null) {
return;
}
+ // Do not need to check for null, since the current tab will have
+ // at least a main WebView, or we would have returned above.
+ if (dialogIsUp()) {
+ // Do not show the fake title bar, which would cover up the
+ // find or select dialog.
+ return;
+ }
WindowManager manager
= (WindowManager) getSystemService(Context.WINDOW_SERVICE);
@@ -1198,6 +1180,12 @@ public class BrowserActivity extends Activity
if (appSearchData == null) {
appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
}
+
+ SearchEngine searchEngine = mSettings.getSearchEngine();
+ if (searchEngine != null && !searchEngine.supportsVoiceSearch()) {
+ appSearchData.putBoolean(SearchManager.DISABLE_VOICE_SEARCH, true);
+ }
+
super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
}
@@ -1277,6 +1265,21 @@ public class BrowserActivity extends Activity
getTopWindow().requestFocus();
}
+ private WebView showDialog(WebDialog dialog) {
+ Tab tab = mTabControl.getCurrentTab();
+ if (tab.getSubWebView() == null) {
+ // If the find or select is being performed on the main webview,
+ // remove the embedded title bar.
+ WebView mainView = tab.getWebView();
+ if (mainView != null) {
+ mainView.setEmbeddedTitleBar(null);
+ }
+ }
+ hideFakeTitleBar();
+ mMenuState = EMPTY_MENU;
+ return tab.showDialog(dialog);
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (!mCanChord) {
@@ -1370,18 +1373,20 @@ public class BrowserActivity extends Activity
break;
case R.id.find_menu_id:
- if (null == mFindDialog) {
- mFindDialog = new FindDialog(this);
- }
- mFindDialog.setWebView(getTopWindow());
- mFindDialog.show();
- getTopWindow().setFindIsUp(true);
- mMenuState = EMPTY_MENU;
+ showFindDialog();
break;
case R.id.select_text_id:
- getTopWindow().emulateShiftHeld();
+ if (true) {
+ Tab currentTab = mTabControl.getCurrentTab();
+ if (currentTab != null) {
+ currentTab.getWebView().setUpSelect();
+ }
+ } else {
+ showSelectDialog();
+ }
break;
+
case R.id.page_info_menu_id:
showPageInfo(mTabControl.getCurrentTab(), false);
break;
@@ -1420,7 +1425,7 @@ public class BrowserActivity extends Activity
break;
case R.id.view_downloads_menu_id:
- viewDownloads(null);
+ viewDownloads();
break;
case R.id.window_one_menu_id:
@@ -1456,8 +1461,59 @@ public class BrowserActivity extends Activity
return true;
}
- public void closeFind() {
+ private boolean dialogIsUp() {
+ return null != mFindDialog && mFindDialog.isVisible() ||
+ null != mSelectDialog && mSelectDialog.isVisible();
+ }
+
+ private boolean closeDialog(WebDialog dialog) {
+ if (null == dialog || !dialog.isVisible()) return false;
+ Tab currentTab = mTabControl.getCurrentTab();
+ currentTab.closeDialog(dialog);
+ dialog.dismiss();
+ return true;
+ }
+
+ /*
+ * Remove the find dialog or select dialog.
+ */
+ public void closeDialogs() {
+ if (!(closeDialog(mFindDialog) || closeDialog(mSelectDialog))) return;
+ // If the Find was being performed in the main WebView, replace the
+ // embedded title bar.
+ Tab currentTab = mTabControl.getCurrentTab();
+ if (currentTab.getSubWebView() == null) {
+ WebView mainView = currentTab.getWebView();
+ if (mainView != null) {
+ mainView.setEmbeddedTitleBar(mTitleBar);
+ }
+ }
mMenuState = R.id.MAIN_MENU;
+ if (mInLoad) {
+ // The title bar was hidden, because otherwise it would cover up the
+ // find or select dialog. Now that the dialog has been removed,
+ // show the fake title bar once again.
+ showFakeTitleBar();
+ }
+ }
+
+ public void showFindDialog() {
+ if (null == mFindDialog) {
+ mFindDialog = new FindDialog(this);
+ }
+ showDialog(mFindDialog).setFindIsUp(true);
+ }
+
+ public void setFindDialogText(String text) {
+ mFindDialog.setText(text);
+ }
+
+ public void showSelectDialog() {
+ if (null == mSelectDialog) {
+ mSelectDialog = new SelectDialog(this);
+ }
+ showDialog(mSelectDialog).setUpSelect();
+ mSelectDialog.hideSoftInput();
}
@Override
@@ -2472,7 +2528,7 @@ public class BrowserActivity extends Activity
onProgressChanged(view, INITIAL_PROGRESS);
mDidStopLoad = false;
if (!mIsNetworkUp) createAndShowNetworkDialog();
-
+ closeDialogs();
if (mSettings.isTracing()) {
String host;
try {
@@ -3492,12 +3548,9 @@ public class BrowserActivity extends Activity
* menu to see the download window. It shows the download window on top of
* the current window.
*/
- private void viewDownloads(Uri downloadRecord) {
- Intent intent = new Intent(this,
- BrowserDownloadPage.class);
- intent.setData(downloadRecord);
- startActivityForResult(intent, BrowserActivity.DOWNLOAD_PAGE);
-
+ private void viewDownloads() {
+ Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+ startActivity(intent);
}
/**
@@ -3636,31 +3689,15 @@ public class BrowserActivity extends Activity
}
return inUrl;
}
- if (hasSpace) {
- // FIXME: Is this the correct place to add to searches?
- // what if someone else calls this function?
- int shortcut = parseUrlShortcut(inUrl);
- if (shortcut != SHORTCUT_INVALID) {
- Browser.addSearchUrl(mResolver, inUrl);
- String query = inUrl.substring(2);
- switch (shortcut) {
- case SHORTCUT_GOOGLE_SEARCH:
- return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
- case SHORTCUT_WIKIPEDIA_SEARCH:
- return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
- case SHORTCUT_DICTIONARY_SEARCH:
- return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
- case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
- // FIXME: we need location in this case
- return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
- }
- }
- } else {
+ if (!hasSpace) {
if (Patterns.WEB_URL.matcher(inUrl).matches()) {
return URLUtil.guessUrl(inUrl);
}
}
+ // FIXME: Is this the correct place to add to searches?
+ // what if someone else calls this function?
+
Browser.addSearchUrl(mResolver, inUrl);
return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
}
@@ -3670,11 +3707,15 @@ public class BrowserActivity extends Activity
// Nothing to do.
return;
}
+ Tab t = mTabControl.getCurrentTab();
+ if (t == null) {
+ // There is no current tab so we cannot toggle the error console
+ return;
+ }
mShouldShowErrorConsole = flag;
- ErrorConsoleView errorConsole = mTabControl.getCurrentTab()
- .getErrorConsole(true);
+ ErrorConsoleView errorConsole = t.getErrorConsole(true);
if (flag) {
// Setting the show state of the console will cause it's the layout to be inflated.
@@ -3780,6 +3821,7 @@ public class BrowserActivity extends Activity
private Menu mMenu;
private FindDialog mFindDialog;
+ private SelectDialog mSelectDialog;
// Used to prevent chording to result in firing two shortcuts immediately
// one after another. Fixes bug 1211714.
boolean mCanChord;
@@ -3864,12 +3906,6 @@ public class BrowserActivity extends Activity
Gravity.CENTER);
// Google search
final static String QuickSearch_G = "http://www.google.com/m?q=%s";
- // Wikipedia search
- final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
- // Dictionary search
- final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
- // Google Mobile Local search
- final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
final static String QUERY_PLACE_HOLDER = "%s";
@@ -3915,7 +3951,6 @@ public class BrowserActivity extends Activity
// activity requestCode
final static int COMBO_PAGE = 1;
- final static int DOWNLOAD_PAGE = 2;
final static int PREFERENCES_PAGE = 3;
final static int FILE_SELECTED = 4;
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index 7560c78..dd01009 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -312,7 +312,7 @@ public class BrowserBookmarksPage extends Activity implements
} else {
ed.putInt(PREF_BOOKMARK_VIEW_MODE, mViewMode.ordinal());
}
- ed.commit();
+ ed.apply();
if (mBookmarksAdapter != null) {
mBookmarksAdapter.switchViewMode(viewMode);
diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java
deleted file mode 100644
index 0f8f721..0000000
--- a/src/com/android/browser/BrowserDownloadAdapter.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.drm.mobile1.DrmRawContent;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.provider.Downloads;
-import android.text.format.Formatter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import java.text.DateFormat;
-import java.util.Date;
-import java.util.List;
-
-/**
- * This class is used to represent the data for the download list box. The only
- * real work done by this class is to construct a custom view for the line
- * items.
- */
-public class BrowserDownloadAdapter extends DateSortedExpandableListAdapter {
-
- private int mTitleColumnId;
- private int mDescColumnId;
- private int mStatusColumnId;
- private int mTotalBytesColumnId;
- private int mCurrentBytesColumnId;
- private int mMimetypeColumnId;
- private int mDateColumnId;
-
- public BrowserDownloadAdapter(Context context, Cursor c, int index) {
- super(context, c, index);
- mTitleColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE);
- mDescColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESCRIPTION);
- mStatusColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS);
- mTotalBytesColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TOTAL_BYTES);
- mCurrentBytesColumnId =
- c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_CURRENT_BYTES);
- mMimetypeColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE);
- mDateColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_LAST_MODIFICATION);
- }
-
- @Override
- public View getChildView(int groupPosition, int childPosition,
- boolean isLastChild, View convertView, ViewGroup parent) {
- Context context = getContext();
- // The layout file uses a RelativeLayout, whereas the GroupViews use
- // TextView.
- if (null == convertView || !(convertView instanceof RelativeLayout)) {
- convertView = LayoutInflater.from(context).inflate(
- R.layout.browser_download_item, null);
- }
-
- // Bail early if the Cursor is closed.
- if (!moveCursorToChildPosition(groupPosition, childPosition)) {
- return convertView;
- }
-
- Resources r = context.getResources();
-
- // Retrieve the icon for this download
- String mimeType = getString(mMimetypeColumnId);
- ImageView iv = (ImageView) convertView.findViewById(R.id.download_icon);
- if (DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(mimeType)) {
- iv.setImageResource(R.drawable.ic_launcher_drm_file);
- } else if (mimeType == null) {
- iv.setVisibility(View.INVISIBLE);
- } else {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.fromParts("file", "", null), mimeType);
- PackageManager pm = context.getPackageManager();
- List<ResolveInfo> list = pm.queryIntentActivities(intent,
- PackageManager.MATCH_DEFAULT_ONLY);
- if (list.size() > 0) {
- Drawable icon = list.get(0).activityInfo.loadIcon(pm);
- iv.setImageDrawable(icon);
- iv.setVisibility(View.VISIBLE);
- } else {
- iv.setVisibility(View.INVISIBLE);
- }
- }
-
- TextView tv = (TextView) convertView.findViewById(R.id.download_title);
- String title = getString(mTitleColumnId);
- if (title == null) {
- title = r.getString(R.string.download_unknown_filename);
- }
- tv.setText(title);
-
- tv = (TextView) convertView.findViewById(R.id.domain);
- tv.setText(getString(mDescColumnId));
-
- long totalBytes = getLong(mTotalBytesColumnId);
-
- int status = getInt(mStatusColumnId);
- if (Downloads.Impl.isStatusCompleted(status)) { // Download stopped
- View v = convertView.findViewById(R.id.progress_text);
- v.setVisibility(View.GONE);
-
- v = convertView.findViewById(R.id.download_progress);
- v.setVisibility(View.GONE);
-
- tv = (TextView) convertView.findViewById(R.id.complete_text);
- tv.setVisibility(View.VISIBLE);
- if (Downloads.Impl.isStatusError(status)) {
- tv.setText(getErrorText(status));
- } else {
- tv.setText(r.getString(R.string.download_success,
- Formatter.formatFileSize(context, totalBytes)));
- }
-
- long time = getLong(mDateColumnId);
- Date d = new Date(time);
- DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
- tv = (TextView) convertView.findViewById(R.id.complete_date);
- tv.setVisibility(View.VISIBLE);
- tv.setText(df.format(d));
-
- } else { // Download is still running
- tv = (TextView) convertView.findViewById(R.id.progress_text);
- tv.setVisibility(View.VISIBLE);
-
- View progress = convertView.findViewById(R.id.download_progress);
- progress.setVisibility(View.VISIBLE);
-
- View v = convertView.findViewById(R.id.complete_date);
- v.setVisibility(View.GONE);
-
- v = convertView.findViewById(R.id.complete_text);
- v.setVisibility(View.GONE);
-
- if (status == Downloads.Impl.STATUS_PENDING) {
- tv.setText(r.getText(R.string.download_pending));
- } else if (status == Downloads.Impl.STATUS_PENDING_PAUSED) {
- tv.setText(r.getText(R.string.download_pending_network));
- } else {
- ProgressBar pb = (ProgressBar) progress;
-
- StringBuilder sb = new StringBuilder();
- if (status == Downloads.Impl.STATUS_RUNNING) {
- sb.append(r.getText(R.string.download_running));
- } else {
- sb.append(r.getText(R.string.download_running_paused));
- }
- if (totalBytes > 0) {
- long currentBytes = getLong(mCurrentBytesColumnId);
- int progressAmount = (int)(currentBytes * 100 / totalBytes);
- sb.append(' ');
- sb.append(progressAmount);
- sb.append("% (");
- sb.append(Formatter.formatFileSize(context, currentBytes));
- sb.append("/");
- sb.append(Formatter.formatFileSize(context, totalBytes));
- sb.append(")");
- pb.setIndeterminate(false);
- pb.setProgress(progressAmount);
- } else {
- pb.setIndeterminate(true);
- }
- tv.setText(sb.toString());
- }
- }
- return convertView;
- }
-
- /**
- * Provide the resource id for the error string.
- * @param status status of the download item
- * @return resource id for the error string.
- */
- public static int getErrorText(int status) {
- switch (status) {
- case Downloads.Impl.STATUS_NOT_ACCEPTABLE:
- return R.string.download_not_acceptable;
-
- case Downloads.Impl.STATUS_LENGTH_REQUIRED:
- return R.string.download_length_required;
-
- case Downloads.Impl.STATUS_PRECONDITION_FAILED:
- return R.string.download_precondition_failed;
-
- case Downloads.Impl.STATUS_CANCELED:
- return R.string.download_canceled;
-
- case Downloads.Impl.STATUS_FILE_ERROR:
- return R.string.download_file_error;
-
- case Downloads.Impl.STATUS_BAD_REQUEST:
- case Downloads.Impl.STATUS_UNKNOWN_ERROR:
- default:
- return R.string.download_error;
- }
- }
-}
diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java
deleted file mode 100644
index 18faf8b..0000000
--- a/src/com/android/browser/BrowserDownloadPage.java
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-
-import android.app.AlertDialog;
-import android.app.ExpandableListActivity;
-import android.content.ActivityNotFoundException;
-import android.content.ContentValues;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.ContentUris;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Downloads;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.AdapterView;
-import android.widget.ExpandableListView;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * View showing the user's current browser downloads
- */
-public class BrowserDownloadPage extends ExpandableListActivity {
- private ExpandableListView mListView;
- private Cursor mDownloadCursor;
- private BrowserDownloadAdapter mDownloadAdapter;
- private int mStatusColumnId;
- private int mIdColumnId;
- private int mTitleColumnId;
- private long mContextMenuPosition;
- // Used to update the ContextMenu if an item is being downloaded and the
- // user opens the ContextMenu.
- private ContentObserver mContentObserver;
- // Only meaningful while a ContentObserver is registered. The ContextMenu
- // will be reopened on this View.
- private View mSelectedView;
-
- private final static String LOGTAG = "BrowserDownloadPage";
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.browser_downloads_page);
-
- setTitle(getText(R.string.download_title));
-
- mListView = (ExpandableListView) findViewById(android.R.id.list);
- mListView.setEmptyView(findViewById(R.id.empty));
- mDownloadCursor = managedQuery(Downloads.Impl.CONTENT_URI,
- new String [] {Downloads.Impl._ID, Downloads.Impl.COLUMN_TITLE,
- Downloads.Impl.COLUMN_STATUS, Downloads.Impl.COLUMN_TOTAL_BYTES,
- Downloads.Impl.COLUMN_CURRENT_BYTES,
- Downloads.Impl.COLUMN_DESCRIPTION,
- Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
- Downloads.Impl.COLUMN_LAST_MODIFICATION,
- Downloads.Impl.COLUMN_VISIBILITY,
- Downloads.Impl._DATA,
- Downloads.Impl.COLUMN_MIME_TYPE},
- null, Downloads.Impl.COLUMN_LAST_MODIFICATION + " DESC");
-
- // only attach everything to the listbox if we can access
- // the download database. Otherwise, just show it empty
- if (mDownloadCursor != null) {
- mStatusColumnId =
- mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS);
- mIdColumnId =
- mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl._ID);
- mTitleColumnId =
- mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE);
-
- // Create a list "controller" for the data
- mDownloadAdapter = new BrowserDownloadAdapter(this,
- mDownloadCursor, mDownloadCursor.getColumnIndexOrThrow(
- Downloads.Impl.COLUMN_LAST_MODIFICATION));
-
- setListAdapter(mDownloadAdapter);
- mListView.setOnCreateContextMenuListener(this);
-
- Intent intent = getIntent();
- final int groupToShow = intent == null || intent.getData() == null
- ? 0 : checkStatus(ContentUris.parseId(intent.getData()));
- if (mDownloadAdapter.getGroupCount() > groupToShow) {
- mListView.post(new Runnable() {
- public void run() {
- if (mDownloadAdapter.getGroupCount() > groupToShow) {
- mListView.expandGroup(groupToShow);
- }
- }
- });
- }
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- if (mDownloadCursor != null) {
- String where = null;
- for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
- mDownloadCursor.moveToNext()) {
- if (!Downloads.Impl.isStatusCompleted(
- mDownloadCursor.getInt(mStatusColumnId))) {
- // Only want to check files that have completed.
- continue;
- }
- int filenameColumnId = mDownloadCursor.getColumnIndexOrThrow(
- Downloads.Impl._DATA);
- String filename = mDownloadCursor.getString(filenameColumnId);
- if (filename != null) {
- File file = new File(filename);
- if (!file.exists()) {
- long id = mDownloadCursor.getLong(mIdColumnId);
- if (where == null) {
- where = Downloads.Impl._ID + " = '" + id + "'";
- } else {
- where += " OR " + Downloads.Impl._ID + " = '" + id
- + "'";
- }
- }
- }
- }
- if (where != null) {
- getContentResolver().delete(Downloads.Impl.CONTENT_URI, where,
- null);
- }
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if (mDownloadCursor != null) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.downloadhistory, menu);
- }
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- boolean showCancel = getCancelableCount() > 0;
- menu.findItem(R.id.download_menu_cancel_all).setEnabled(showCancel);
- return super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.download_menu_cancel_all:
- promptCancelAll();
- return true;
- }
- return false;
- }
-
- /**
- * Remove the file from the list of downloads.
- * @param id Unique ID of the download to remove.
- */
- private void clearFromDownloads(long id) {
- getContentResolver().delete(ContentUris.withAppendedId(
- Downloads.Impl.CONTENT_URI, id), null, null);
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- if (!mDownloadAdapter.moveCursorToPackedChildPosition(
- mContextMenuPosition)) {
- return false;
- }
- switch (item.getItemId()) {
- case R.id.download_menu_open:
- hideCompletedDownload();
- openOrDeleteCurrentDownload(false);
- return true;
-
- case R.id.download_menu_delete:
- new AlertDialog.Builder(this)
- .setTitle(R.string.download_delete_file)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(mDownloadCursor.getString(mTitleColumnId))
- .setNegativeButton(R.string.cancel, null)
- .setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- openOrDeleteCurrentDownload(true);
- }
- })
- .show();
- break;
-
- case R.id.download_menu_clear:
- case R.id.download_menu_cancel:
- clearFromDownloads(mDownloadCursor.getLong(mIdColumnId));
- return true;
- }
- return false;
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- if (mContentObserver != null) {
- getContentResolver().unregisterContentObserver(mContentObserver);
- // Note that we do not need to undo this in onResume, because the
- // ContextMenu does not get reinvoked when the Activity resumes.
- }
- }
-
- /*
- * ContentObserver to update the ContextMenu if it is open when the
- * corresponding download completes.
- */
- private class ChangeObserver extends ContentObserver {
- private final Uri mTrack;
- public ChangeObserver(Uri track) {
- super(new Handler());
- mTrack = track;
- }
-
- @Override
- public boolean deliverSelfNotifications() {
- return false;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- Cursor cursor = null;
- try {
- cursor = getContentResolver().query(mTrack,
- new String[] { Downloads.Impl.COLUMN_STATUS }, null, null,
- null);
- if (cursor.moveToFirst() && Downloads.Impl.isStatusSuccess(
- cursor.getInt(0))) {
- // Do this right away, so we get no more updates.
- getContentResolver().unregisterContentObserver(
- mContentObserver);
- // Post a runnable in case this ContentObserver gets notified
- // before the one that updates the ListView.
- mListView.post(new Runnable() {
- public void run() {
- // Close the context menu, reopen with up to date data.
- closeContextMenu();
- openContextMenu(mSelectedView);
- }
- });
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "onChange", e);
- } finally {
- if (cursor != null) cursor.close();
- }
- }
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- if (mDownloadCursor != null) {
- ExpandableListView.ExpandableListContextMenuInfo info
- = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
- long packedPosition = info.packedPosition;
- // Only show a context menu for the child views
- if (!mDownloadAdapter.moveCursorToPackedChildPosition(
- packedPosition)) {
- return;
- }
- mContextMenuPosition = packedPosition;
- menu.setHeaderTitle(mDownloadCursor.getString(mTitleColumnId));
-
- MenuInflater inflater = getMenuInflater();
- int status = mDownloadCursor.getInt(mStatusColumnId);
- if (Downloads.Impl.isStatusSuccess(status)) {
- inflater.inflate(R.menu.downloadhistorycontextfinished, menu);
- } else if (Downloads.Impl.isStatusError(status)) {
- inflater.inflate(R.menu.downloadhistorycontextfailed, menu);
- } else {
- // In this case, the download is in progress. Set a
- // ContentObserver so that we can know when it completes,
- // and if it does, we can then update the context menu
- Uri track = ContentUris.withAppendedId(
- Downloads.Impl.CONTENT_URI,
- mDownloadCursor.getLong(mIdColumnId));
- if (mContentObserver != null) {
- getContentResolver().unregisterContentObserver(
- mContentObserver);
- }
- mContentObserver = new ChangeObserver(track);
- mSelectedView = v;
- getContentResolver().registerContentObserver(track, false,
- mContentObserver);
- inflater.inflate(R.menu.downloadhistorycontextrunning, menu);
- }
- }
- super.onCreateContextMenu(menu, v, menuInfo);
- }
-
- /**
- * This function is called to check the status of the download and if it
- * has an error show an error dialog.
- * @param id Row id of the download to check
- * @return Group which contains the download
- */
- private int checkStatus(final long id) {
- int groupToShow = mDownloadAdapter.groupFromChildId(id);
- if (-1 == groupToShow) return 0;
- int status = mDownloadCursor.getInt(mStatusColumnId);
- if (!Downloads.Impl.isStatusError(status)) {
- return groupToShow;
- }
- if (status == Downloads.Impl.STATUS_FILE_ERROR) {
- String title = mDownloadCursor.getString(mTitleColumnId);
- if (title == null || title.length() == 0) {
- title = getString(R.string.download_unknown_filename);
- }
- String msg = getString(R.string.download_file_error_dlg_msg, title);
- new AlertDialog.Builder(this)
- .setTitle(R.string.download_file_error_dlg_title)
- .setIcon(android.R.drawable.ic_popup_disk_full)
- .setMessage(msg)
- .setPositiveButton(R.string.ok, null)
- .setNegativeButton(R.string.retry,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- resumeDownload(id);
- }
- })
- .show();
- } else {
- new AlertDialog.Builder(this)
- .setTitle(R.string.download_failed_generic_dlg_title)
- .setIcon(R.drawable.ssl_icon)
- .setMessage(BrowserDownloadAdapter.getErrorText(status))
- .setPositiveButton(R.string.ok, null)
- .show();
- }
- return groupToShow;
- }
-
- /**
- * Resume a given download
- * @param id Row id of the download to resume
- */
- private void resumeDownload(final long id) {
- // the relevant functionality doesn't exist in the download manager
- }
-
- /**
- * Return the number of items in the list that can be canceled.
- * @return count
- */
- private int getCancelableCount() {
- // Count the number of items that will be canceled.
- int count = 0;
- if (mDownloadCursor != null) {
- for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
- mDownloadCursor.moveToNext()) {
- int status = mDownloadCursor.getInt(mStatusColumnId);
- if (!Downloads.Impl.isStatusCompleted(status)) {
- count++;
- }
- }
- }
-
- return count;
- }
-
- /**
- * Prompt the user if they would like to clear the download history
- */
- private void promptCancelAll() {
- int count = getCancelableCount();
-
- // If there is nothing to do, just return
- if (count == 0) {
- return;
- }
-
- // Don't show the dialog if there is only one download
- if (count == 1) {
- cancelAllDownloads();
- return;
- }
- String msg =
- getString(R.string.download_cancel_dlg_msg, count);
- new AlertDialog.Builder(this)
- .setTitle(R.string.download_cancel_dlg_title)
- .setIcon(R.drawable.ssl_icon)
- .setMessage(msg)
- .setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- cancelAllDownloads();
- }
- })
- .setNegativeButton(R.string.cancel, null)
- .show();
- }
-
- /**
- * Cancel all downloads. As canceled downloads are not
- * listed, we removed them from the db. Removing a download
- * record, cancels the download.
- */
- private void cancelAllDownloads() {
- if (mDownloadCursor.moveToFirst()) {
- StringBuilder where = new StringBuilder();
- boolean firstTime = true;
- while (!mDownloadCursor.isAfterLast()) {
- int status = mDownloadCursor.getInt(mStatusColumnId);
- if (!Downloads.Impl.isStatusCompleted(status)) {
- if (firstTime) {
- firstTime = false;
- } else {
- where.append(" OR ");
- }
- where.append("( ");
- where.append(Downloads.Impl._ID);
- where.append(" = '");
- where.append(mDownloadCursor.getLong(mIdColumnId));
- where.append("' )");
- }
- mDownloadCursor.moveToNext();
- }
- if (!firstTime) {
- getContentResolver().delete(Downloads.Impl.CONTENT_URI,
- where.toString(), null);
- }
- }
- }
-
- private int getClearableCount() {
- int count = 0;
- if (mDownloadCursor.moveToFirst()) {
- while (!mDownloadCursor.isAfterLast()) {
- int status = mDownloadCursor.getInt(mStatusColumnId);
- if (Downloads.Impl.isStatusCompleted(status)) {
- count++;
- }
- mDownloadCursor.moveToNext();
- }
- }
- return count;
- }
-
- /**
- * Open or delete content where the download db cursor currently is. Sends
- * an Intent to perform the action.
- * @param delete If true, delete the content. Otherwise open it.
- */
- private void openOrDeleteCurrentDownload(boolean delete) {
- int packageColumnId = mDownloadCursor.getColumnIndexOrThrow(
- Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
- String packageName = mDownloadCursor.getString(packageColumnId);
- Intent intent = new Intent(delete ? Intent.ACTION_DELETE
- : Downloads.Impl.ACTION_NOTIFICATION_CLICKED);
- Uri contentUri = ContentUris.withAppendedId(
- Downloads.Impl.CONTENT_URI,
- mDownloadCursor.getLong(mIdColumnId));
- intent.setData(contentUri);
- intent.setPackage(packageName);
- sendBroadcast(intent);
- }
-
- @Override
- public boolean onChildClick(ExpandableListView parent, View v,
- int groupPosition, int childPosition, long id) {
- // Open the selected item
- mDownloadAdapter.moveCursorToChildPosition(groupPosition,
- childPosition);
-
- hideCompletedDownload();
-
- int status = mDownloadCursor.getInt(mStatusColumnId);
- if (Downloads.Impl.isStatusSuccess(status)) {
- // Open it if it downloaded successfully
- openOrDeleteCurrentDownload(false);
- } else {
- // Check to see if there is an error.
- checkStatus(id);
- }
- return true;
- }
-
- /**
- * hides the notification for the download pointed by mDownloadCursor
- * if the download has completed.
- */
- private void hideCompletedDownload() {
- int status = mDownloadCursor.getInt(mStatusColumnId);
-
- int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(
- Downloads.Impl.COLUMN_VISIBILITY);
- int visibility = mDownloadCursor.getInt(visibilityColumn);
-
- if (Downloads.Impl.isStatusCompleted(status) &&
- visibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
- ContentValues values = new ContentValues();
- values.put(Downloads.Impl.COLUMN_VISIBILITY, Downloads.Impl.VISIBILITY_VISIBLE);
- getContentResolver().update(
- ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI,
- mDownloadCursor.getLong(mIdColumnId)), values, null, null);
- }
- }
-}
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 a6ceb8b..72ec819 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;
@@ -166,7 +159,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() {
}
@@ -385,62 +378,13 @@ public class BrowserProvider extends ContentProvider {
fixPicasaBookmark();
Editor ed = p.edit();
ed.putBoolean("fix_picasa", false);
- ed.commit();
+ ed.apply();
}
}
- 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 " +
@@ -897,12 +841,14 @@ 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
&& 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 3d5ca03..3791eb0 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,7 @@ class BrowserSettings extends Observable {
private boolean openInBackground;
private String defaultTextEncodingName;
private String homeUrl = "";
+ private SearchEngine searchEngine;
private boolean autoFitPage;
private boolean landscapeOnly;
private boolean loadsPageInOverviewMode;
@@ -121,6 +130,7 @@ 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_CLEAR_FORM_DATA =
"privacy_clear_form_data";
public final static String PREF_CLEAR_PASSWORDS =
@@ -137,12 +147,20 @@ class BrowserSettings extends Observable {
"privacy_clear_geolocation_access";
private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (Macintosh; " +
- "U; Intel Mac OS X 10_5_7; en-us) AppleWebKit/530.17 (KHTML, " +
- "like Gecko) Version/4.0 Safari/530.17";
+ "U; Intel Mac OS X 10_6_3; en-us) AppleWebKit/533.16 (KHTML, " +
+ "like Gecko) Version/5.0 Safari/533.16";
private static final String IPHONE_USERAGENT = "Mozilla/5.0 (iPhone; U; " +
- "CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 " +
- "(KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16";
+ "CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 " +
+ "(KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7";
+
+ private static final String IPAD_USERAGENT = "Mozilla/5.0 (iPad; U; " +
+ "CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 " +
+ "(KHTML, like Gecko) Version/4.0.4 Mobile/7B367 Safari/531.21.10";
+
+ private static final String FROYO_USERAGENT = "Mozilla/5.0 (Linux; U; " +
+ "Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 " +
+ "(KHTML, like Gecko) Version/4.0 Mobile Safari/533.1";
// Value to truncate strings when adding them to a TextView within
// a ListView
@@ -182,6 +200,10 @@ class BrowserSettings extends Observable {
s.setUserAgentString(DESKTOP_USERAGENT);
} else if (b.userAgent == 2) {
s.setUserAgentString(IPHONE_USERAGENT);
+ } else if (b.userAgent == 3) {
+ s.setUserAgentString(IPAD_USERAGENT);
+ } else if (b.userAgent == 4) {
+ s.setUserAgentString(FROYO_USERAGENT);
}
s.setUseWideViewPort(b.useWideViewPort);
s.setLoadsImagesAutomatically(b.loadsImagesAutomatically);
@@ -234,7 +256,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 +288,34 @@ class BrowserSettings extends Observable {
pageCacheCapacity = 1;
}
- // Load the defaults from the xml
+ // 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,
+ SearchEngine.GOOGLE);
+ if (searchEngine == null || !searchEngine.getName().equals(searchEngineName)) {
+ if (searchEngine != null) {
+ if (searchEngine.supportsVoiceSearch()) {
+ // One or more tabs could have been in voice search mode.
+ // Clear it, since the new SearchEngine may not support
+ // it, or may handle it differently.
+ for (int i = 0; i < mTabControl.getTabCount(); i++) {
+ mTabControl.getTab(i).revertVoiceSearchMode();
+ }
+ }
+ searchEngine.close();
+ }
+ searchEngine = SearchEngines.get(ctx, searchEngineName);
+ }
+ Log.i(TAG, "Selected search engine: " + searchEngine);
loadsImagesAutomatically = p.getBoolean("load_images",
loadsImagesAutomatically);
@@ -369,6 +408,10 @@ class BrowserSettings extends Observable {
return homeUrl;
}
+ public SearchEngine getSearchEngine() {
+ return searchEngine;
+ }
+
public String getJsFlags() {
return jsFlags;
}
@@ -381,7 +424,7 @@ class BrowserSettings extends Observable {
Editor ed = PreferenceManager.
getDefaultSharedPreferences(context).edit();
ed.putString(PREF_HOMEPAGE, url);
- ed.commit();
+ ed.apply();
homeUrl = url;
}
@@ -561,7 +604,7 @@ class BrowserSettings extends Observable {
reset();
SharedPreferences p =
PreferenceManager.getDefaultSharedPreferences(ctx);
- p.edit().clear().commit();
+ p.edit().clear().apply();
PreferenceManager.setDefaultValues(ctx, R.xml.browser_preferences,
true);
// reset homeUrl
diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java
index 45c8016..726138e 100644
--- a/src/com/android/browser/FindDialog.java
+++ b/src/com/android/browser/FindDialog.java
@@ -16,27 +16,25 @@
package com.android.browser;
-import android.app.Dialog;
import android.content.Context;
-import android.os.Bundle;
import android.text.Editable;
+import android.text.Selection;
import android.text.Spannable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
+import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebView;
import android.widget.EditText;
+import android.widget.LinearLayout;
import android.widget.TextView;
-/* package */ class FindDialog extends Dialog implements TextWatcher {
- private WebView mWebView;
+/* package */ class FindDialog extends WebDialog implements TextWatcher {
private TextView mMatches;
- private BrowserActivity mBrowserActivity;
// Views with which the user can interact.
private EditText mEditText;
@@ -44,39 +42,30 @@ import android.widget.TextView;
private View mPrevButton;
private View mMatchesView;
+ // When the dialog is opened up with old text, enter needs to be pressed
+ // (or the text needs to be changed) before WebView.findAll can be called.
+ // Once it has been called, enter should move to the next match.
+ private boolean mMatchesFound;
+ private int mNumberOfMatches;
+
private View.OnClickListener mFindListener = new View.OnClickListener() {
public void onClick(View v) {
findNext();
}
};
- private View.OnClickListener mFindCancelListener =
- new View.OnClickListener() {
- public void onClick(View v) {
- dismiss();
- }
- };
-
- private View.OnClickListener mFindPreviousListener =
+ private View.OnClickListener mFindPreviousListener =
new View.OnClickListener() {
public void onClick(View v) {
if (mWebView == null) {
throw new AssertionError("No WebView for FindDialog::onClick");
}
mWebView.findNext(false);
+ updateMatchesString();
hideSoftInput();
}
};
- /*
- * Remove the soft keyboard from the screen.
- */
- private void hideSoftInput() {
- InputMethodManager imm = (InputMethodManager)
- mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
- }
-
private void disableButtons() {
mPrevButton.setEnabled(false);
mNextButton.setEnabled(false);
@@ -84,28 +73,13 @@ import android.widget.TextView;
mNextButton.setFocusable(false);
}
- /* package */ void setWebView(WebView webview) {
- mWebView = webview;
- }
-
/* package */ FindDialog(BrowserActivity context) {
- super(context, R.style.FindDialogTheme);
- mBrowserActivity = context;
- setCanceledOnTouchOutside(true);
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ super(context);
- Window theWindow = getWindow();
- theWindow.setGravity(Gravity.BOTTOM|Gravity.FILL_HORIZONTAL);
-
- setContentView(R.layout.browser_find);
-
- theWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
+ LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.browser_find, this);
+ addCancel();
mEditText = (EditText) findViewById(R.id.edit);
View button = findViewById(R.id.next);
@@ -116,29 +90,59 @@ import android.widget.TextView;
button.setOnClickListener(mFindPreviousListener);
mPrevButton = button;
- button = findViewById(R.id.done);
- button.setOnClickListener(mFindCancelListener);
-
mMatches = (TextView) findViewById(R.id.matches);
mMatchesView = findViewById(R.id.matches_view);
disableButtons();
- theWindow.setSoftInputMode(
- WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+
}
-
+
+ /**
+ * Called by BrowserActivity.closeDialog. Start the animation to hide
+ * the dialog, inform the WebView that the dialog is being dismissed,
+ * and hide the soft keyboard.
+ */
public void dismiss() {
super.dismiss();
- mBrowserActivity.closeFind();
mWebView.notifyFindDialogDismissed();
+ hideSoftInput();
+ }
+
+ @Override
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ KeyEvent.DispatcherState state = getKeyDispatcherState();
+ if (state != null) {
+ int action = event.getAction();
+ if (KeyEvent.ACTION_DOWN == action
+ && event.getRepeatCount() == 0) {
+ state.startTracking(event, this);
+ return true;
+ } else if (KeyEvent.ACTION_UP == action
+ && !event.isCanceled() && state.isTracking(event)) {
+ mBrowserActivity.closeDialogs();
+ return true;
+ }
+ }
+ }
+ return super.dispatchKeyEventPreIme(event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
- && event.getAction() == KeyEvent.ACTION_UP
- && mEditText.hasFocus()) {
- findNext();
- return true;
+ int keyCode = event.getKeyCode();
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER
+ && mEditText.hasFocus()) {
+ if (mMatchesFound) {
+ findNext();
+ } else {
+ findAll();
+ // Set the selection to the end.
+ Spannable span = (Spannable) mEditText.getText();
+ Selection.setSelection(span, span.length());
+ }
+ return true;
+ }
}
return super.dispatchKeyEvent(event);
}
@@ -148,18 +152,26 @@ import android.widget.TextView;
throw new AssertionError("No WebView for FindDialog::findNext");
}
mWebView.findNext(true);
+ updateMatchesString();
hideSoftInput();
}
public void show() {
super.show();
+ // In case the matches view is showing from a previous search
+ mMatchesView.setVisibility(View.INVISIBLE);
+ mMatchesFound = false;
+ // This text is only here to ensure that mMatches has a height.
+ mMatches.setText("0");
mEditText.requestFocus();
- mEditText.setText("");
Spannable span = (Spannable) mEditText.getText();
- span.setSpan(this, 0, span.length(),
- Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- setMatchesFound(0);
+ int length = span.length();
+ Selection.setSelection(span, 0, length);
+ span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
disableButtons();
+ InputMethodManager imm = (InputMethodManager)
+ mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mEditText, 0);
}
// TextWatcher methods
@@ -173,9 +185,13 @@ import android.widget.TextView;
int start,
int before,
int count) {
+ findAll();
+ }
+
+ private void findAll() {
if (mWebView == null) {
throw new AssertionError(
- "No WebView for FindDialog::onTextChanged");
+ "No WebView for FindDialog::findAll");
}
CharSequence find = mEditText.getText();
if (0 == find.length()) {
@@ -184,14 +200,18 @@ import android.widget.TextView;
mMatchesView.setVisibility(View.INVISIBLE);
} else {
mMatchesView.setVisibility(View.VISIBLE);
- mWebView.setFindDialogHeight(
- getWindow().getDecorView().getHeight());
int found = mWebView.findAll(find.toString());
+ mMatchesFound = true;
setMatchesFound(found);
if (found < 2) {
disableButtons();
if (found == 0) {
- setMatchesFound(0);
+ // Cannot use getQuantityString, which ignores the "zero"
+ // quantity.
+ // FIXME: is this fix is beyond the scope
+ // of adding touch selection to gingerbread?
+ // mMatches.setText(mBrowserActivity.getResources().getString(
+ // R.string.no_matches));
}
} else {
mPrevButton.setFocusable(true);
@@ -203,8 +223,21 @@ import android.widget.TextView;
}
private void setMatchesFound(int found) {
+ mNumberOfMatches = found;
+ updateMatchesString();
+ }
+
+ public void setText(String text) {
+ mEditText.setText(text);
+ findAll();
+ }
+
+ private void updateMatchesString() {
+ // Note: updateMatchesString is only called by methods that have already
+ // checked mWebView for null.
String template = mBrowserActivity.getResources().
- getQuantityString(R.plurals.matches_found, found, found);
+ getQuantityString(R.plurals.matches_found, mNumberOfMatches,
+ mWebView.findIndex() + 1, mNumberOfMatches);
mMatches.setText(template);
}
diff --git a/src/com/android/browser/OpenDownloadReceiver.java b/src/com/android/browser/OpenDownloadReceiver.java
index 814aa9c..99e5f41 100644
--- a/src/com/android/browser/OpenDownloadReceiver.java
+++ b/src/com/android/browser/OpenDownloadReceiver.java
@@ -16,23 +16,24 @@
package com.android.browser;
+import android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.net.Uri;
import android.provider.Downloads;
-import android.provider.MediaStore;
import android.widget.Toast;
import java.io.File;
/**
- * This {@link BroadcastReceiver} handles {@link Intent}s to open and delete
- * files downloaded by the Browser.
+ * This {@link BroadcastReceiver} handles clicks to notifications that
+ * downloads from the browser are in progress/complete. Clicking on an
+ * in-progress or failed download will open the download manager. Clicking on
+ * a complete, successful download will open the file.
*/
public class OpenDownloadReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
@@ -69,44 +70,15 @@ public class OpenDownloadReceiver extends BroadcastReceiver {
}
} else {
// Open the downloads page
- Intent pageView = new Intent(context,
- BrowserDownloadPage.class);
- pageView.setData(data);
+ Intent pageView = new Intent(
+ DownloadManager.ACTION_VIEW_DOWNLOADS);
pageView.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(pageView);
}
- } else if (Intent.ACTION_DELETE.equals(action)) {
- if (deleteFile(cr, filename, mimetype)) {
- cr.delete(data, null, null);
- }
}
}
} finally {
if (cursor != null) cursor.close();
}
}
-
- /**
- * Remove the file from the SD card
- * @param cr ContentResolver used to delete the file.
- * @param filename Name of the file to delete.
- * @param mimetype Mimetype of the file to delete.
- * @return boolean True on success, false on failure.
- */
- private boolean deleteFile(ContentResolver cr, String filename,
- String mimetype) {
- Uri uri;
- if (mimetype.startsWith("image")) {
- uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- } else if (mimetype.startsWith("audio")) {
- uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
- } else if (mimetype.startsWith("video")) {
- uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
- } else {
- uri = null;
- }
- return (uri != null && cr.delete(uri, MediaStore.MediaColumns.DATA
- + " = " + DatabaseUtils.sqlEscapeString(filename), null) > 0)
- || new File(filename).delete();
- }
}
diff --git a/src/com/android/browser/SelectDialog.java b/src/com/android/browser/SelectDialog.java
new file mode 100644
index 0000000..461127a
--- /dev/null
+++ b/src/com/android/browser/SelectDialog.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+import android.provider.Browser;
+import android.view.LayoutInflater;
+import android.view.View;
+
+/* package */ class SelectDialog extends WebDialog {
+ private View mCopyButton;
+ private View mSelectAllButton;
+ private View mShareButton;
+ private View mFindButton;
+
+ SelectDialog(BrowserActivity context) {
+ super(context);
+ LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.browser_select, this);
+ addCancel();
+
+ mCopyButton = findViewById(R.id.copy);
+ mCopyButton.setOnClickListener(mCopyListener);
+ mSelectAllButton = findViewById(R.id.select_all);
+ mSelectAllButton.setOnClickListener(mSelectAllListener);
+ mShareButton = findViewById(R.id.share);
+ mShareButton.setOnClickListener(mShareListener);
+ mFindButton = findViewById(R.id.find);
+ mFindButton.setOnClickListener(mFindListener);
+ }
+
+ private View.OnClickListener mCopyListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ mWebView.copySelection();
+ mBrowserActivity.closeDialogs();
+ }
+ };
+
+ private View.OnClickListener mSelectAllListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ mWebView.selectAll();
+ }
+ };
+
+ private View.OnClickListener mShareListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ String selection = mWebView.getSelection();
+ Browser.sendString(mBrowserActivity, selection);
+ mBrowserActivity.closeDialogs();
+ }
+ };
+
+ private View.OnClickListener mFindListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ String selection = mWebView.getSelection();
+ mBrowserActivity.closeDialogs();
+ mBrowserActivity.showFindDialog();
+ mBrowserActivity.setFindDialogText(selection);
+ }
+ };
+
+ /**
+ * Called by BrowserActivity.closeDialog. Start the animation to hide
+ * the dialog, and inform the WebView that the dialog is being dismissed.
+ */
+ @Override
+ public void dismiss() {
+ super.dismiss();
+ mWebView.notifySelectDialogDismissed();
+ }
+
+}
diff --git a/src/com/android/browser/SystemAllowGeolocationOrigins.java b/src/com/android/browser/SystemAllowGeolocationOrigins.java
index 3f5a84e..b53611f 100644
--- a/src/com/android/browser/SystemAllowGeolocationOrigins.java
+++ b/src/com/android/browser/SystemAllowGeolocationOrigins.java
@@ -103,7 +103,7 @@ class SystemAllowGeolocationOrigins {
// Save the new value as the last read value
preferences.edit()
.putString(LAST_READ_ALLOW_GEOLOCATION_ORIGINS, newSetting)
- .commit();
+ .apply();
Set<String> oldOrigins = parseAllowGeolocationOrigins(lastReadSetting);
Set<String> newOrigins = parseAllowGeolocationOrigins(newSetting);
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index b31abc2..7019c8a 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -87,7 +87,7 @@ class Tab {
// The Geolocation permissions prompt
private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
// Main WebView wrapper
- private View mContainer;
+ private LinearLayout mContainer;
// Main WebView
private WebView mMainView;
// Subwindow container
@@ -151,7 +151,6 @@ class Tab {
static final String CURRTAB = "currentTab";
static final String CURRURL = "currentUrl";
static final String CURRTITLE = "currentTitle";
- static final String CURRPICTURE = "currentPicture";
static final String CLOSEONEXIT = "closeonexit";
static final String PARENTTAB = "parentTab";
static final String APPID = "appid";
@@ -165,14 +164,25 @@ class Tab {
*/
private VoiceSearchData mVoiceSearchData;
/**
+ * Remove voice search mode from this tab.
+ */
+ public void revertVoiceSearchMode() {
+ if (mVoiceSearchData != null) {
+ mVoiceSearchData = null;
+ if (mInForeground) {
+ mActivity.revertVoiceTitleBar();
+ }
+ }
+ }
+ /**
* Return whether the tab is in voice search mode.
*/
public boolean isInVoiceSearchMode() {
return mVoiceSearchData != null;
}
/**
- * Return true if the voice search Intent came with a String identifying
- * that Google provided the Intent.
+ * Return true if the Tab is in voice search mode and the voice search
+ * Intent came with a String identifying that Google provided the Intent.
*/
public boolean voiceSearchSourceIsGoogle() {
return mVoiceSearchData != null && mVoiceSearchData.mSourceIsGoogle;
@@ -457,10 +467,7 @@ class Tab {
i.putExtra(LoggingEvents.EXTRA_FLUSH, true);
mActivity.sendBroadcast(i);
}
- mVoiceSearchData = null;
- if (mInForeground) {
- mActivity.revertVoiceTitleBar();
- }
+ revertVoiceSearchMode();
}
// We've started to load a new page. If there was a pending message
@@ -529,6 +536,18 @@ class Tab {
// return true if want to hijack the url to let another app to handle it
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (voiceSearchSourceIsGoogle()) {
+ // This method is called when the user clicks on a link.
+ // VoiceSearchMode is turned off when the user leaves the
+ // Google results page, so at this point the user must be on
+ // that page. If the user clicked a link on that page, assume
+ // that the voice search was effective, and broadcast an Intent
+ // so a receiver can take note of that fact.
+ Intent logIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT);
+ logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
+ LoggingEvents.VoiceSearch.RESULT_CLICKED);
+ mActivity.sendBroadcast(logIntent);
+ }
if (mInForeground) {
return mActivity.shouldOverrideUrlLoading(view, url);
} else {
@@ -1029,6 +1048,16 @@ class Tab {
}
@Override
+ public void onSelectionDone(WebView view) {
+ if (mInForeground) mActivity.closeDialogs();
+ }
+
+ @Override
+ public void onSelectionStart(WebView view) {
+ if (false && mInForeground) mActivity.showSelectDialog();
+ }
+
+ @Override
public void onShowCustomView(View view,
WebChromeClient.CustomViewCallback callback) {
if (mInForeground) mActivity.onShowCustomView(view, callback);
@@ -1208,9 +1237,18 @@ class Tab {
private static class SubWindowClient extends WebViewClient {
// The main WebViewClient.
private final WebViewClient mClient;
+ private final BrowserActivity mBrowserActivity;
- SubWindowClient(WebViewClient client) {
+ SubWindowClient(WebViewClient client, BrowserActivity activity) {
mClient = client;
+ mBrowserActivity = activity;
+ }
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ // Unlike the others, do not call mClient's version, which would
+ // change the progress bar. However, we do want to remove the
+ // find or select dialog.
+ mBrowserActivity.closeDialogs();
}
@Override
public void doUpdateVisitedHistory(WebView view, String url,
@@ -1300,7 +1338,7 @@ class Tab {
// The tab consists of a container view, which contains the main
// WebView, as well as any other UI elements associated with the tab.
- mContainer = mInflateService.inflate(R.layout.tab, null);
+ mContainer = (LinearLayout) mInflateService.inflate(R.layout.tab, null);
mDownloadListener = new DownloadListener() {
public void onDownloadStart(String url, String userAgent,
@@ -1411,6 +1449,7 @@ class Tab {
*/
boolean createSubWindow() {
if (mSubView == null) {
+ mActivity.closeDialogs();
mSubViewContainer = mInflateService.inflate(
R.layout.browser_subwindow, null);
mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview);
@@ -1419,7 +1458,8 @@ class Tab {
mSubView.setMapTrackballToArrowKeys(false);
// Enable the built-in zoom
mSubView.getSettings().setBuiltInZoomControls(true);
- mSubView.setWebViewClient(new SubWindowClient(mWebViewClient));
+ mSubView.setWebViewClient(new SubWindowClient(mWebViewClient,
+ mActivity));
mSubView.setWebChromeClient(new SubWindowChromeClient(
mWebChromeClient));
// Set a different DownloadListener for the mSubView, since it will
@@ -1433,7 +1473,7 @@ class Tab {
if (mSubView.copyBackForwardList().getSize() == 0) {
// This subwindow was opened for the sole purpose of
// downloading a file. Remove it.
- dismissSubWindow();
+ mActivity.dismissSubWindow(Tab.this);
}
}
});
@@ -1457,6 +1497,7 @@ class Tab {
*/
void dismissSubWindow() {
if (mSubView != null) {
+ mActivity.closeDialogs();
BrowserSettings.getInstance().deleteObserver(
mSubView.getSettings());
mSubView.destroy();
@@ -1481,6 +1522,7 @@ class Tab {
void removeSubWindow(ViewGroup content) {
if (mSubView != null) {
content.removeView(mSubViewContainer);
+ mActivity.closeDialogs();
}
}
@@ -1539,6 +1581,7 @@ class Tab {
(FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
wrapper.removeView(mMainView);
content.removeView(mContainer);
+ mActivity.closeDialogs();
removeSubWindow(content);
}
@@ -1871,17 +1914,6 @@ class Tab {
mSavedState = new Bundle();
final WebBackForwardList list = mMainView.saveState(mSavedState);
- if (list != null) {
- final File f = new File(mActivity.getTabControl().getThumbnailDir(),
- mMainView.hashCode() + "_pic.save");
- if (mMainView.savePicture(mSavedState, f)) {
- mSavedState.putString(CURRPICTURE, f.getPath());
- } else {
- // if savePicture returned false, we can't trust the contents,
- // and it may be large, so we delete it right away
- f.delete();
- }
- }
// Store some extra info for displaying the tab in the picker.
final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
@@ -1927,11 +1959,38 @@ class Tab {
if (list == null) {
return false;
}
- if (b.containsKey(CURRPICTURE)) {
- final File f = new File(b.getString(CURRPICTURE));
- mMainView.restorePicture(b, f);
- f.delete();
- }
return true;
}
+
+ /*
+ * Opens the find and select text dialogs. Called by BrowserActivity.
+ */
+ WebView showDialog(WebDialog dialog) {
+ LinearLayout container;
+ WebView view;
+ if (mSubView != null) {
+ view = mSubView;
+ container = (LinearLayout) mSubViewContainer.findViewById(
+ R.id.inner_container);
+ } else {
+ view = mMainView;
+ container = mContainer;
+ }
+ dialog.show();
+ container.addView(dialog, 0, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ dialog.setWebView(view);
+ return view;
+ }
+
+ /*
+ * Close the find or select dialog. Called by BrowserActivity.closeDialog.
+ */
+ void closeDialog(WebDialog dialog) {
+ // The dialog may be attached to the subwindow. Ensure that the
+ // correct parent has it removed.
+ LinearLayout parent = (LinearLayout) dialog.getParent();
+ if (parent != null) parent.removeView(dialog);
+ }
}
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 7cd2ccb..afd4ea8 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -229,15 +229,6 @@ class TabControl {
}
}
- // This tab may have been pushed in to the background and then closed.
- // If the saved state contains a picture file, delete the file.
- Bundle savedState = t.getSavedState();
- if (savedState != null) {
- if (savedState.containsKey(Tab.CURRPICTURE)) {
- new File(savedState.getString(Tab.CURRPICTURE)).delete();
- }
- }
-
// Remove it from the queue of viewed tabs.
mTabQueue.remove(t);
return true;
diff --git a/src/com/android/browser/WebDialog.java b/src/com/android/browser/WebDialog.java
new file mode 100644
index 0000000..9995e8f
--- /dev/null
+++ b/src/com/android/browser/WebDialog.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.inputmethod.InputMethodManager;
+import android.webkit.WebView;
+import android.widget.LinearLayout;
+
+/* package */ class WebDialog extends LinearLayout {
+ protected WebView mWebView;
+ protected BrowserActivity mBrowserActivity;
+ private boolean mIsVisible;
+
+ /* package */ WebDialog(BrowserActivity context) {
+ super(context);
+ mBrowserActivity = context;
+ }
+
+ /* dialogs that have cancel buttons can optionally share code by including a
+ * view with an id of 'done'.
+ */
+ protected void addCancel() {
+ View button = findViewById(R.id.done);
+ if (button != null) button.setOnClickListener(mCancelListener);
+ }
+
+ private View.OnClickListener mCancelListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ mBrowserActivity.closeDialogs();
+ }
+ };
+
+ protected void dismiss() {
+ startAnimation(AnimationUtils.loadAnimation(mBrowserActivity,
+ R.anim.dialog_exit));
+ mIsVisible = false;
+ }
+
+ /*
+ * Remove the soft keyboard from the screen.
+ */
+ protected void hideSoftInput() {
+ InputMethodManager imm = (InputMethodManager)
+ mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
+ }
+
+ protected boolean isVisible() {
+ return mIsVisible;
+ }
+
+ /* package */ void setWebView(WebView webview) {
+ mWebView = webview;
+ }
+
+ protected void show() {
+ startAnimation(AnimationUtils.loadAnimation(mBrowserActivity,
+ R.anim.dialog_enter));
+ mIsVisible = true;
+ }
+
+}
diff --git a/src/com/android/browser/search/DefaultSearchEngine.java b/src/com/android/browser/search/DefaultSearchEngine.java
new file mode 100644
index 0000000..f282b0b
--- /dev/null
+++ b/src/com/android/browser/search/DefaultSearchEngine.java
@@ -0,0 +1,123 @@
+/*
+ * 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 SearchEngine.GOOGLE;
+ } else if ("com.android.quicksearchbox".equals(packageName)) {
+ return SearchEngine.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.setComponent(mSearchable.getSearchActivity());
+ 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() {
+ }
+
+ public boolean supportsVoiceSearch() {
+ return getName().equals(SearchEngine.GOOGLE);
+ }
+
+ @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..a19e50d
--- /dev/null
+++ b/src/com/android/browser/search/OpenSearchSearchEngine.java
@@ -0,0 +1,298 @@
+/*
+ * 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.
+ * @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();
+ }
+
+ public boolean supportsVoiceSearch() {
+ return getName().equals(SearchEngine.GOOGLE);
+ }
+
+ 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..b7e1859
--- /dev/null
+++ b/src/com/android/browser/search/SearchEngine.java
@@ -0,0 +1,64 @@
+/*
+ * 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 {
+
+ // Used if the search engine is Google
+ static final String GOOGLE = "google";
+
+ /**
+ * 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();
+
+ /**
+ * Checks whether this search engine supports voice search.
+ */
+ public boolean supportsVoiceSearch();
+}
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;
+ }
+ }
+
+}