diff options
Diffstat (limited to 'src/com/android/browser')
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; + } + } + +} |