summaryrefslogtreecommitdiffstats
path: root/src/com/android/browser
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/browser')
-rw-r--r--src/com/android/browser/ActiveTabsPage.java71
-rw-r--r--src/com/android/browser/AddBookmarkPage.java752
-rw-r--r--src/com/android/browser/AutoFillProfileDatabase.java154
-rw-r--r--src/com/android/browser/AutoFillSettingsFragment.java168
-rw-r--r--src/com/android/browser/BaseUi.java895
-rw-r--r--src/com/android/browser/BookmarkUtils.java184
-rw-r--r--src/com/android/browser/Bookmarks.java211
-rw-r--r--src/com/android/browser/BookmarksLoader.java72
-rw-r--r--src/com/android/browser/BreadCrumbView.java299
-rw-r--r--src/com/android/browser/Browser.java2
-rw-r--r--src/com/android/browser/BrowserActivity.java3928
-rw-r--r--src/com/android/browser/BrowserBackupAgent.java7
-rw-r--r--src/com/android/browser/BrowserBookmarksAdapter.java579
-rw-r--r--src/com/android/browser/BrowserBookmarksPage.java1180
-rw-r--r--src/com/android/browser/BrowserHistoryPage.java442
-rw-r--r--src/com/android/browser/BrowserHomepagePreference.java56
-rw-r--r--src/com/android/browser/BrowserPreferencesPage.java190
-rw-r--r--src/com/android/browser/BrowserProvider.java193
-rw-r--r--src/com/android/browser/BrowserSettings.java350
-rw-r--r--src/com/android/browser/CombinedBookmarkHistoryActivity.java192
-rw-r--r--src/com/android/browser/CombinedBookmarkHistoryView.java240
-rw-r--r--src/com/android/browser/Controller.java2453
-rw-r--r--src/com/android/browser/DateSortedExpandableListAdapter.java117
-rw-r--r--src/com/android/browser/Dots.java83
-rw-r--r--src/com/android/browser/DownloadHandler.java218
-rw-r--r--src/com/android/browser/DownloadTouchIcon.java100
-rw-r--r--src/com/android/browser/FetchUrlMimeType.java97
-rw-r--r--src/com/android/browser/FindDialog.java247
-rw-r--r--src/com/android/browser/HistoryItem.java13
-rw-r--r--src/com/android/browser/HttpAuthenticationDialog.java153
-rw-r--r--src/com/android/browser/IntentHandler.java371
-rw-r--r--src/com/android/browser/MeshTracker.java198
-rw-r--r--src/com/android/browser/NetworkStateHandler.java140
-rw-r--r--src/com/android/browser/OpenDownloadReceiver.java106
-rw-r--r--src/com/android/browser/PageDialogsHandler.java465
-rw-r--r--src/com/android/browser/PageProgressView.java117
-rw-r--r--src/com/android/browser/Performance.java133
-rw-r--r--src/com/android/browser/ScrollWebView.java125
-rw-r--r--src/com/android/browser/SelectDialog.java85
-rw-r--r--src/com/android/browser/ShortcutActivity.java67
-rw-r--r--src/com/android/browser/SuggestionsAdapter.java623
-rw-r--r--src/com/android/browser/Tab.java610
-rw-r--r--src/com/android/browser/TabBar.java472
-rw-r--r--src/com/android/browser/TabControl.java167
-rw-r--r--src/com/android/browser/TabScrollView.java214
-rw-r--r--src/com/android/browser/TitleBar.java165
-rw-r--r--src/com/android/browser/TitleBarBase.java78
-rw-r--r--src/com/android/browser/TitleBarXLarge.java258
-rw-r--r--src/com/android/browser/UI.java131
-rw-r--r--src/com/android/browser/UiController.java78
-rw-r--r--src/com/android/browser/UploadHandler.java214
-rw-r--r--src/com/android/browser/UrlHandler.java238
-rw-r--r--src/com/android/browser/UrlInputView.java190
-rw-r--r--src/com/android/browser/UrlUtils.java148
-rw-r--r--src/com/android/browser/WallpaperHandler.java127
-rw-r--r--src/com/android/browser/WebDialog.java79
-rw-r--r--src/com/android/browser/WebViewController.java108
-rw-r--r--src/com/android/browser/WebViewFactory.java30
-rw-r--r--src/com/android/browser/WebsiteSettingsActivity.java140
-rw-r--r--src/com/android/browser/preferences/AdvancedPreferencesFragment.java91
-rw-r--r--src/com/android/browser/preferences/DebugPreferencesFragment.java32
-rw-r--r--src/com/android/browser/preferences/PageContentPreferencesFragment.java151
-rw-r--r--src/com/android/browser/preferences/PersonalPreferencesFragment.java371
-rw-r--r--src/com/android/browser/preferences/PrivacyPreferencesFragment.java55
-rw-r--r--src/com/android/browser/preferences/SecurityPreferencesFragment.java32
-rw-r--r--src/com/android/browser/provider/BrowserProvider2.java1186
-rw-r--r--src/com/android/browser/provider/SQLiteContentProvider.java276
-rw-r--r--src/com/android/browser/search/.DefaultSearchEngine.java.swpbin0 -> 16384 bytes
-rw-r--r--src/com/android/browser/widget/BookmarkListWidgetProvider.java110
-rw-r--r--src/com/android/browser/widget/BookmarkListWidgetService.java372
-rw-r--r--src/com/android/browser/widget/BookmarkWidgetProvider.java49
-rw-r--r--src/com/android/browser/widget/BookmarkWidgetService.java385
72 files changed, 14836 insertions, 7797 deletions
diff --git a/src/com/android/browser/ActiveTabsPage.java b/src/com/android/browser/ActiveTabsPage.java
index 2de778712..fb5ed3b4f 100644
--- a/src/com/android/browser/ActiveTabsPage.java
+++ b/src/com/android/browser/ActiveTabsPage.java
@@ -20,7 +20,7 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.util.AttributeSet;
-import android.view.KeyEvent;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -32,16 +32,19 @@ import android.widget.ListView;
import android.widget.TextView;
public class ActiveTabsPage extends LinearLayout {
- private final BrowserActivity mBrowserActivity;
- private final LayoutInflater mFactory;
- private final TabControl mControl;
- private final TabsListAdapter mAdapter;
- private final ListView mListView;
- public ActiveTabsPage(BrowserActivity context, TabControl control) {
+ private static final String LOGTAG = "TabPicker";
+
+ private final LayoutInflater mFactory;
+ private final UiController mUiController;
+ private final TabControl mControl;
+ private final TabsListAdapter mAdapter;
+ private final ListView mListView;
+
+ public ActiveTabsPage(Context context, UiController control) {
super(context);
- mBrowserActivity = context;
- mControl = control;
+ mUiController = control;
+ mControl = control.getTabControl();
mFactory = LayoutInflater.from(context);
mFactory.inflate(R.layout.active_tabs, this);
mListView = (ListView) findViewById(R.id.list);
@@ -51,21 +54,24 @@ public class ActiveTabsPage extends LinearLayout {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (mControl.canCreateNewTab()) {
- position--;
+ position -= 2;
}
boolean needToAttach = false;
- if (position == -1) {
+ if (position == -2) {
// Create a new tab
- mBrowserActivity.openTabToHomePage();
+ mUiController.openTabToHomePage();
+ } else if (position == -1) {
+ // Create a new incognito tab
+ mUiController.openIncognitoTab();
} else {
// Open the corresponding tab
// If the tab is the current one, switchToTab will
// do nothing and return, so we need to make sure
// it gets attached back to its mContentView in
// removeActiveTabPage
- needToAttach = !mBrowserActivity.switchToTab(position);
+ needToAttach = !mUiController.switchToTab(position);
}
- mBrowserActivity.removeActiveTabPage(needToAttach);
+ mUiController.removeActiveTabsPage(needToAttach);
}
});
}
@@ -98,7 +104,7 @@ public class ActiveTabsPage extends LinearLayout {
public int getCount() {
int count = mControl.getTabCount();
if (mControl.canCreateNewTab()) {
- count++;
+ count += 2;
}
// XXX: This is a workaround to be more like a real adapter. Most
// adapters call notifyDataSetChanged() whenever the internal data
@@ -128,23 +134,28 @@ public class ActiveTabsPage extends LinearLayout {
}
public int getItemViewType(int position) {
if (mControl.canCreateNewTab()) {
- position--;
+ position -= 2;
}
// Do not recycle the "add new tab" item.
- return position == -1 ? IGNORE_ITEM_VIEW_TYPE : 1;
+ return position < 0 ? IGNORE_ITEM_VIEW_TYPE : 1;
}
public View getView(int position, View convertView, ViewGroup parent) {
final int tabCount = mControl.getTabCount();
if (mControl.canCreateNewTab()) {
- position--;
+ position -= 2;
}
if (convertView == null) {
- convertView = mFactory.inflate(position == -1 ?
- R.layout.tab_view_add_tab : R.layout.tab_view, null);
+ if (position == -2) {
+ convertView = mFactory.inflate(R.layout.tab_view_add_tab, null);
+ } else if (position == -1) {
+ convertView = mFactory.inflate(R.layout.tab_view_add_incognito_tab, null);
+ } else {
+ convertView = mFactory.inflate(R.layout.tab_view, null);
+ }
}
- if (position != -1) {
+ if (position >= 0) {
TextView title =
(TextView) convertView.findViewById(R.id.title);
TextView url = (TextView) convertView.findViewById(R.id.url);
@@ -152,7 +163,19 @@ public class ActiveTabsPage extends LinearLayout {
(ImageView) convertView.findViewById(R.id.favicon);
View close = convertView.findViewById(R.id.close);
Tab tab = mControl.getTab(position);
+ if (tab.getWebView() == null) {
+ // This means that populatePickerData will have to use the
+ // saved state.
+ Log.w(LOGTAG, "Tab " + position + " has a null WebView and "
+ + (tab.getSavedState() == null ? "null" : "non-null")
+ + " saved state ");
+ }
tab.populatePickerData();
+ if (tab.getTitle() == null || tab.getTitle().length() == 0) {
+ Log.w(LOGTAG, "Tab " + position + " has no title. "
+ + "Check above in the Logs to see whether it has a "
+ + "null WebView or null WebHistoryItem");
+ }
title.setText(tab.getTitle());
url.setText(tab.getUrl());
Bitmap icon = tab.getFavicon();
@@ -164,11 +187,11 @@ public class ActiveTabsPage extends LinearLayout {
final int closePosition = position;
close.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
- mBrowserActivity.closeTab(
+ mUiController.closeTab(
mControl.getTab(closePosition));
if (tabCount == 1) {
- mBrowserActivity.openTabToHomePage();
- mBrowserActivity.removeActiveTabPage(false);
+ mUiController.openTabToHomePage();
+ mUiController.removeActiveTabsPage(false);
} else {
mNotified = true;
notifyDataSetChanged();
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index 594f985a7..de256a838 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -16,106 +16,616 @@
package com.android.browser;
+import com.android.browser.provider.BrowserProvider2;
+
import android.app.Activity;
+import android.app.LoaderManager;
import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
import android.net.ParseException;
+import android.net.Uri;
import android.net.WebAddress;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.provider.Browser;
+import android.preference.PreferenceManager;
+import android.provider.BrowserContract;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.Date;
+import java.util.Stack;
+
+public class AddBookmarkPage extends Activity
+ implements View.OnClickListener, TextView.OnEditorActionListener,
+ AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor>,
+ BreadCrumbView.Controller, PopupMenu.OnMenuItemClickListener {
+
+ public static final long DEFAULT_FOLDER_ID = -1;
+ public static final String TOUCH_ICON_URL = "touch_icon_url";
+ // Place on an edited bookmark to remove the saved thumbnail
+ public static final String REMOVE_THUMBNAIL = "remove_thumbnail";
+ public static final String USER_AGENT = "user_agent";
-public class AddBookmarkPage extends Activity {
+ /* package */ static final String EXTRA_EDIT_BOOKMARK = "bookmark";
+ /* package */ static final String EXTRA_IS_FOLDER = "is_folder";
+
+ private static final int MAX_CRUMBS_SHOWN = 2;
private final String LOGTAG = "Bookmarks";
+ // Set to true to see the crash on the code I would like to run.
+ private final boolean DEBUG_CRASH = false;
+
+ // IDs for the CursorLoaders that are used.
+ private final int LOADER_ID_FOLDER_CONTENTS = 0;
+ private final int LOADER_ID_ALL_FOLDERS = 1;
private EditText mTitle;
private EditText mAddress;
private TextView mButton;
private View mCancelButton;
private boolean mEditingExisting;
+ private boolean mEditingFolder;
private Bundle mMap;
private String mTouchIconUrl;
- private Bitmap mThumbnail;
private String mOriginalUrl;
+ private TextView mFolder;
+ private View mDefaultView;
+ private View mFolderSelector;
+ private EditText mFolderNamer;
+ private View mAddNewFolder;
+ private View mAddSeparator;
+ private long mCurrentFolder = 0;
+ private FolderAdapter mAdapter;
+ private BreadCrumbView mCrumbs;
+ private TextView mFakeTitle;
+ private View mCrumbHolder;
+ private ListView mListView;
+ private boolean mSaveToHomeScreen;
+ private long mRootFolder;
+
+ private static class Folder {
+ String Name;
+ long Id;
+ Folder(String name, long id) {
+ Name = name;
+ Id = id;
+ }
+ }
// Message IDs
private static final int SAVE_BOOKMARK = 100;
+ private static final int TOUCH_ICON_DOWNLOADED = 101;
private Handler mHandler;
- private View.OnClickListener mSaveBookmark = new View.OnClickListener() {
- public void onClick(View v) {
- if (save()) {
+ private InputMethodManager getInputMethodManager() {
+ return (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ }
+
+ @Override
+ public void onTop(int level, Object data) {
+ if (null == data) return;
+ Folder folderData = (Folder) data;
+ long folder = folderData.Id;
+ Uri uri = BrowserContract.Bookmarks.buildFolderUri(folder);
+ LoaderManager manager = getLoaderManager();
+ CursorLoader loader = (CursorLoader) ((Loader) manager.getLoader(
+ LOADER_ID_FOLDER_CONTENTS));
+ loader.setUri(uri);
+ loader.forceLoad();
+ updateVisible();
+ if (mFolderNamer.getVisibility() == View.VISIBLE) {
+ completeOrCancelFolderNaming(true);
+ }
+ }
+
+ /**
+ * Update the views shown to only show the two deepest levels of crumbs.
+ * Note that this method depends on internal knowledge of BreadCrumbView.
+ */
+ private void updateVisible() {
+ if (MAX_CRUMBS_SHOWN > 0) {
+ int invisibleCrumbs = mCrumbs.size() - MAX_CRUMBS_SHOWN;
+ // This class always uses a back button, which is the first child.
+ int childIndex = 1;
+ if (invisibleCrumbs > 0) {
+ int crumbIndex = 0;
+ while (crumbIndex < invisibleCrumbs) {
+ // Set the crumb to GONE.
+ mCrumbs.getChildAt(childIndex).setVisibility(View.GONE);
+ childIndex++;
+ // Each crumb is followed by a separator (except the last
+ // one). Also make it GONE
+ mCrumbs.getChildAt(childIndex).setVisibility(View.GONE);
+ childIndex++;
+ // Move to the next crumb.
+ crumbIndex++;
+ }
+ }
+ // Make sure the last two are visible.
+ int childCount = mCrumbs.getChildCount();
+ while (childIndex < childCount) {
+ mCrumbs.getChildAt(childIndex).setVisibility(View.VISIBLE);
+ childIndex++;
+ }
+ }
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (v == mFolderNamer) {
+ if (v.getText().length() > 0) {
+ if (actionId == EditorInfo.IME_NULL) {
+ // Only want to do this once.
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ completeOrCancelFolderNaming(false);
+ }
+ }
+ }
+ // Steal the key press; otherwise a newline will be added
+ return true;
+ }
+ return false;
+ }
+
+ private void switchToDefaultView(boolean changedFolder) {
+ mFolderSelector.setVisibility(View.GONE);
+ mDefaultView.setVisibility(View.VISIBLE);
+ mCrumbHolder.setVisibility(View.GONE);
+ mFakeTitle.setVisibility(View.VISIBLE);
+ if (changedFolder) {
+ Object data = mCrumbs.getTopData();
+ if (data != null) {
+ Folder folder = (Folder) data;
+ mCurrentFolder = folder.Id;
+ int resource = mCurrentFolder == mRootFolder ?
+ R.drawable.ic_menu_bookmarks :
+ com.android.internal.R.drawable.ic_menu_archive;
+ Drawable drawable = getResources().getDrawable(resource);
+ updateFolderLabel(folder.Name, drawable);
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mButton) {
+ if (mFolderSelector.getVisibility() == View.VISIBLE) {
+ // We are showing the folder selector.
+ if (mFolderNamer.getVisibility() == View.VISIBLE) {
+ completeOrCancelFolderNaming(false);
+ } else {
+ // User has selected a folder. Go back to the opening page
+ mSaveToHomeScreen = false;
+ switchToDefaultView(true);
+ }
+ } else if (save()) {
+ finish();
+ }
+ } else if (v == mCancelButton) {
+ if (mFolderNamer.getVisibility() == View.VISIBLE) {
+ completeOrCancelFolderNaming(true);
+ } else if (mFolderSelector.getVisibility() == View.VISIBLE) {
+ switchToDefaultView(false);
+ } else {
finish();
}
+ } else if (v == mFolder) {
+ PopupMenu popup = new PopupMenu(this, mFolder);
+ popup.getMenuInflater().inflate(R.menu.folder_choice,
+ popup.getMenu());
+ if (mEditingFolder) {
+ popup.getMenu().removeItem(R.id.home_screen);
+ }
+ popup.setOnMenuItemClickListener(this);
+ popup.show();
+ } else if (v == mAddNewFolder) {
+ mFolderNamer.setVisibility(View.VISIBLE);
+ mFolderNamer.setText(R.string.new_folder);
+ mFolderNamer.requestFocus();
+ updateList();
+ mAddNewFolder.setVisibility(View.GONE);
+ mAddSeparator.setVisibility(View.GONE);
+ getInputMethodManager().showSoftInput(mFolderNamer,
+ InputMethodManager.SHOW_IMPLICIT);
}
- };
+ }
- private View.OnClickListener mCancel = new View.OnClickListener() {
- public void onClick(View v) {
- finish();
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.bookmarks:
+ mCurrentFolder = mRootFolder;
+ updateFolderLabel(item.getTitle(), item.getIcon());
+ mSaveToHomeScreen = false;
+ break;
+ case R.id.home_screen:
+ // Create a short cut to the home screen
+ mSaveToHomeScreen = true;
+ updateFolderLabel(item.getTitle(), item.getIcon());
+ break;
+ case R.id.other:
+ switchToFolderSelector();
+ break;
+ default:
+ return false;
}
- };
+ return true;
+ }
+
+ // Refresh the ListView to hide or show the empty view, as necessary.
+ // Should be called after mFolderNamer is shown or hidden.
+ private void updateList() {
+ if (mAdapter.getCount() == 0) {
+ // XXX: Is there a better way to refresh the ListView?
+ mListView.setAdapter(mAdapter);
+ }
+ }
+
+ private void completeOrCancelFolderNaming(boolean cancel) {
+ if (!cancel && !TextUtils.isEmpty(mFolderNamer.getText())) {
+ String name = mFolderNamer.getText().toString();
+ long id = addFolderToCurrent(mFolderNamer.getText().toString());
+ descendInto(name, id);
+ }
+ mFolderNamer.setVisibility(View.GONE);
+ mAddNewFolder.setVisibility(View.VISIBLE);
+ mAddSeparator.setVisibility(View.VISIBLE);
+ getInputMethodManager().hideSoftInputFromWindow(
+ mFolderNamer.getWindowToken(), 0);
+ updateList();
+ }
+
+ private long addFolderToCurrent(String name) {
+ // Add the folder to the database
+ ContentValues values = new ContentValues();
+ values.put(BrowserContract.Bookmarks.TITLE,
+ name);
+ values.put(BrowserContract.Bookmarks.IS_FOLDER, 1);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ String accountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
+ String accountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
+ if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+ values.put(BrowserContract.Bookmarks.ACCOUNT_TYPE, accountType);
+ values.put(BrowserContract.Bookmarks.ACCOUNT_NAME, accountName);
+ }
+ long currentFolder;
+ Object data = mCrumbs.getTopData();
+ if (data != null) {
+ currentFolder = ((Folder) data).Id;
+ } else {
+ currentFolder = mRootFolder;
+ }
+ values.put(BrowserContract.Bookmarks.PARENT, currentFolder);
+ Uri uri = getContentResolver().insert(
+ BrowserContract.Bookmarks.CONTENT_URI, values);
+ if (uri != null) {
+ return ContentUris.parseId(uri);
+ } else {
+ return -1;
+ }
+ }
+
+ private void switchToFolderSelector() {
+ mDefaultView.setVisibility(View.GONE);
+ mFolderSelector.setVisibility(View.VISIBLE);
+ mCrumbHolder.setVisibility(View.VISIBLE);
+ mFakeTitle.setVisibility(View.GONE);
+ mAddNewFolder.setVisibility(View.VISIBLE);
+ mAddSeparator.setVisibility(View.VISIBLE);
+ }
+
+ private void descendInto(String foldername, long id) {
+ if (id != DEFAULT_FOLDER_ID) {
+ mCrumbs.pushView(foldername, new Folder(foldername, id));
+ mCrumbs.notifyController();
+ }
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ String[] projection;
+ switch (id) {
+ case LOADER_ID_ALL_FOLDERS:
+ projection = new String[] {
+ BrowserContract.Bookmarks._ID,
+ BrowserContract.Bookmarks.PARENT,
+ BrowserContract.Bookmarks.TITLE,
+ BrowserContract.Bookmarks.IS_FOLDER
+ };
+ return new CursorLoader(this,
+ BrowserContract.Bookmarks.CONTENT_URI,
+ projection,
+ BrowserContract.Bookmarks.IS_FOLDER + " != 0",
+ null,
+ null);
+ case LOADER_ID_FOLDER_CONTENTS:
+ projection = new String[] {
+ BrowserContract.Bookmarks._ID,
+ BrowserContract.Bookmarks.TITLE,
+ BrowserContract.Bookmarks.IS_FOLDER
+ };
+ return new CursorLoader(this,
+ BrowserContract.Bookmarks.buildFolderUri(
+ mCurrentFolder),
+ projection,
+ BrowserContract.Bookmarks.IS_FOLDER + " != 0",
+ null,
+ null);
+ default:
+ throw new AssertionError("Asking for nonexistant loader!");
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ switch (loader.getId()) {
+ case LOADER_ID_FOLDER_CONTENTS:
+ mAdapter.changeCursor(cursor);
+ break;
+ case LOADER_ID_ALL_FOLDERS:
+ long parent = mCurrentFolder;
+ int idIndex = cursor.getColumnIndexOrThrow(
+ BrowserContract.Bookmarks._ID);
+ int titleIndex = cursor.getColumnIndexOrThrow(
+ BrowserContract.Bookmarks.TITLE);
+ int parentIndex = cursor.getColumnIndexOrThrow(
+ BrowserContract.Bookmarks.PARENT);
+ Stack folderStack = new Stack();
+ while ((parent != BrowserProvider2.FIXED_ID_ROOT) &&
+ (parent != 0)) {
+ // First, find the folder corresponding to the current
+ // folder
+ if (!cursor.moveToFirst()) {
+ throw new AssertionError("No folders in the database!");
+ }
+ long folder;
+ do {
+ folder = cursor.getLong(idIndex);
+ } while (folder != parent && cursor.moveToNext());
+ if (cursor.isAfterLast()) {
+ throw new AssertionError("Folder(id=" + parent
+ + ") holding this bookmark does not exist!");
+ }
+ String name = cursor.getString(titleIndex);
+ if (parent == mCurrentFolder) {
+ Drawable draw = getResources().getDrawable(
+ com.android.internal.R.drawable.ic_menu_archive);
+ updateFolderLabel(name, draw);
+ }
+ folderStack.push(new Folder(name, parent));
+ parent = cursor.getLong(parentIndex);
+ }
+ while (!folderStack.isEmpty()) {
+ Folder thisFolder = (Folder) folderStack.pop();
+ mCrumbs.pushView(thisFolder.Name, thisFolder);
+ }
+ getLoaderManager().stopLoader(LOADER_ID_ALL_FOLDERS);
+ updateVisible();
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Update the name and image to show where the bookmark will be added
+ * @param name Name of the location to save (folder name, bookmarks, or home
+ * screen.
+ * @param drawable Image to show corresponding to the save location.
+ */
+ void updateFolderLabel(CharSequence name, Drawable drawable) {
+ mFolder.setText(name);
+ mFolder.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null,
+ null);
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ TextView tv = (TextView) view.findViewById(android.R.id.text1);
+ // Switch to the folder that was clicked on.
+ descendInto(tv.getText().toString(), id);
+ }
+
+ /**
+ * Shows a list of names of folders.
+ */
+ private class FolderAdapter extends CursorAdapter {
+ public FolderAdapter(Context context) {
+ super(context, null);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ ((TextView) view.findViewById(android.R.id.text1)).setText(
+ cursor.getString(cursor.getColumnIndexOrThrow(
+ BrowserContract.Bookmarks.TITLE)));
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View view = LayoutInflater.from(context).inflate(
+ R.layout.folder_list_item, null);
+ view.setBackgroundDrawable(context.getResources().
+ getDrawable(android.R.drawable.list_selector_background));
+ return view;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ // Do not show the empty view if the user is creating a new folder.
+ return super.isEmpty() && mFolderNamer.getVisibility() == View.GONE;
+ }
+ }
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- requestWindowFeature(Window.FEATURE_LEFT_ICON);
+ if (DEBUG_CRASH) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ }
+
+ mMap = getIntent().getExtras();
+
setContentView(R.layout.browser_add_bookmark);
- setTitle(R.string.save_to_bookmarks);
- getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_list_bookmark);
-
+
+ Window window = getWindow();
+ if (!DEBUG_CRASH) {
+ setTitle("");
+ }
+
String title = null;
String url = null;
- mMap = getIntent().getExtras();
+
+ mFakeTitle = (TextView) findViewById(R.id.fake_title);
+
if (mMap != null) {
- Bundle b = mMap.getBundle("bookmark");
+ Bundle b = mMap.getBundle(EXTRA_EDIT_BOOKMARK);
if (b != null) {
+ mEditingFolder = mMap.getBoolean(EXTRA_IS_FOLDER, false);
mMap = b;
mEditingExisting = true;
- setTitle(R.string.edit_bookmark);
+ mFakeTitle.setText(R.string.edit_bookmark);
+ if (mEditingFolder) {
+ findViewById(R.id.row_address).setVisibility(View.GONE);
+ }
+ } else {
+ int gravity = mMap.getInt("gravity", -1);
+ if (gravity != -1) {
+ WindowManager.LayoutParams l = window.getAttributes();
+ l.gravity = gravity;
+ window.setAttributes(l);
+ }
}
- title = mMap.getString("title");
- url = mOriginalUrl = mMap.getString("url");
- mTouchIconUrl = mMap.getString("touch_icon_url");
- mThumbnail = (Bitmap) mMap.getParcelable("thumbnail");
+ title = mMap.getString(BrowserContract.Bookmarks.TITLE);
+ url = mOriginalUrl = mMap.getString(BrowserContract.Bookmarks.URL);
+ mTouchIconUrl = mMap.getString(TOUCH_ICON_URL);
+ mCurrentFolder = mMap.getLong(BrowserContract.Bookmarks.PARENT, DEFAULT_FOLDER_ID);
+ }
+ mRootFolder = getBookmarksBarId(this);
+ if (mCurrentFolder == DEFAULT_FOLDER_ID) {
+ mCurrentFolder = mRootFolder;
}
mTitle = (EditText) findViewById(R.id.title);
mTitle.setText(title);
+
mAddress = (EditText) findViewById(R.id.address);
mAddress.setText(url);
- View.OnClickListener accept = mSaveBookmark;
mButton = (TextView) findViewById(R.id.OK);
- mButton.setOnClickListener(accept);
+ mButton.setOnClickListener(this);
mCancelButton = findViewById(R.id.cancel);
- mCancelButton.setOnClickListener(mCancel);
-
- if (!getWindow().getDecorView().isInTouchMode()) {
+ mCancelButton.setOnClickListener(this);
+
+ mFolder = (TextView) findViewById(R.id.folder);
+ mFolder.setOnClickListener(this);
+
+ mDefaultView = findViewById(R.id.default_view);
+ mFolderSelector = findViewById(R.id.folder_selector);
+
+ mFolderNamer = (EditText) findViewById(R.id.folder_namer);
+ mFolderNamer.setOnEditorActionListener(this);
+
+ mAddNewFolder = findViewById(R.id.add_new_folder);
+ mAddNewFolder.setOnClickListener(this);
+ mAddSeparator = findViewById(R.id.add_divider);
+
+ mCrumbs = (BreadCrumbView) findViewById(R.id.crumbs);
+ mCrumbs.setUseBackButton(true);
+ mCrumbs.setController(this);
+ String name = getString(R.string.bookmarks);
+ mCrumbs.pushView(name, false,
+ new Folder(name, BrowserProvider2.FIXED_ID_ROOT));
+ mCrumbHolder = findViewById(R.id.crumb_holder);
+
+ mAdapter = new FolderAdapter(this);
+ mListView = (ListView) findViewById(R.id.list);
+ View empty = findViewById(R.id.empty);
+ mListView.setEmptyView(empty);
+ mListView.setAdapter(mAdapter);
+ mListView.setOnItemClickListener(this);
+ LoaderManager manager = getLoaderManager();
+ if (mCurrentFolder != BrowserProvider2.FIXED_ID_ROOT) {
+ // Find all the folders
+ manager.initLoader(LOADER_ID_ALL_FOLDERS, null, this);
+ }
+ // Find the contents of the current folder
+ manager.initLoader(LOADER_ID_FOLDER_CONTENTS, null, this);
+
+
+ if (!window.getDecorView().isInTouchMode()) {
mButton.requestFocus();
}
}
+ // FIXME: Use a CursorLoader
+ private long getBookmarksBarId(Context context) {
+ SharedPreferences prefs
+ = PreferenceManager.getDefaultSharedPreferences(context);
+ String accountName =
+ prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
+ String accountType =
+ prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
+ if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+ return BrowserProvider2.FIXED_ID_ROOT;
+ }
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(
+ BrowserContract.Bookmarks.CONTENT_URI,
+ new String[] { BrowserContract.Bookmarks._ID },
+ BrowserContract.ChromeSyncColumns.SERVER_UNIQUE + "=? AND "
+ + BrowserContract.Bookmarks.ACCOUNT_NAME + "=? AND "
+ + BrowserContract.Bookmarks.ACCOUNT_TYPE + "=?",
+ new String[] {
+ BrowserContract.ChromeSyncColumns
+ .FOLDER_NAME_BOOKMARKS_BAR,
+ accountName,
+ accountType },
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return BrowserProvider2.FIXED_ID_ROOT;
+ }
+
/**
* Runnable to save a bookmark, so it can be performed in its own thread.
*/
private class SaveBookmarkRunnable implements Runnable {
+ // FIXME: This should be an async task.
private Message mMessage;
private Context mContext;
public SaveBookmarkRunnable(Context ctx, Message msg) {
@@ -125,18 +635,18 @@ public class AddBookmarkPage extends Activity {
public void run() {
// Unbundle bookmark data.
Bundle bundle = mMessage.getData();
- String title = bundle.getString("title");
- String url = bundle.getString("url");
- boolean invalidateThumbnail = bundle.getBoolean(
- "invalidateThumbnail");
+ String title = bundle.getString(BrowserContract.Bookmarks.TITLE);
+ String url = bundle.getString(BrowserContract.Bookmarks.URL);
+ boolean invalidateThumbnail = bundle.getBoolean(REMOVE_THUMBNAIL);
Bitmap thumbnail = invalidateThumbnail ? null
- : (Bitmap) bundle.getParcelable("thumbnail");
- String touchIconUrl = bundle.getString("touchIconUrl");
+ : (Bitmap) bundle.getParcelable(BrowserContract.Bookmarks.THUMBNAIL);
+ String touchIconUrl = bundle.getString(TOUCH_ICON_URL);
// Save to the bookmarks DB.
try {
final ContentResolver cr = getContentResolver();
- Bookmarks.addBookmark(null, cr, url, title, thumbnail, true);
+ Bookmarks.addBookmark(AddBookmarkPage.this, false, url,
+ title, thumbnail, true, mCurrentFolder);
if (touchIconUrl != null) {
new DownloadTouchIcon(mContext, cr, url).execute(mTouchIconUrl);
}
@@ -148,6 +658,28 @@ public class AddBookmarkPage extends Activity {
}
}
+ private static class UpdateBookmarkTask extends AsyncTask<ContentValues, Void, Void> {
+ Context mContext;
+ Long mId;
+
+ public UpdateBookmarkTask(Context context, long id) {
+ mContext = context;
+ mId = id;
+ }
+
+ @Override
+ protected Void doInBackground(ContentValues... params) {
+ if (params.length != 1) {
+ throw new IllegalArgumentException("No ContentValues provided!");
+ }
+ Uri uri = ContentUris.withAppendedId(BookmarkUtils.getBookmarksUri(mContext), mId);
+ mContext.getContentResolver().update(
+ uri,
+ params[0], null, null);
+ return null;
+ }
+ }
+
private void createHandler() {
if (mHandler == null) {
mHandler = new Handler() {
@@ -163,6 +695,15 @@ public class AddBookmarkPage extends Activity {
Toast.LENGTH_LONG).show();
}
break;
+ case TOUCH_ICON_DOWNLOADED:
+ Bundle b = msg.getData();
+ sendBroadcast(BookmarkUtils.createAddToHomeIntent(
+ AddBookmarkPage.this,
+ b.getString(BrowserContract.Bookmarks.URL),
+ b.getString(BrowserContract.Bookmarks.TITLE),
+ (Bitmap) b.getParcelable(BrowserContract.Bookmarks.TOUCH_ICON),
+ (Bitmap) b.getParcelable(BrowserContract.Bookmarks.FAVICON)));
+ break;
}
}
};
@@ -176,12 +717,13 @@ public class AddBookmarkPage extends Activity {
createHandler();
String title = mTitle.getText().toString().trim();
- String unfilteredUrl =
- BrowserActivity.fixUrl(mAddress.getText().toString());
+ String unfilteredUrl;
+ unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString());
+
boolean emptyTitle = title.length() == 0;
boolean emptyUrl = unfilteredUrl.trim().length() == 0;
Resources r = getResources();
- if (emptyTitle || emptyUrl) {
+ if (emptyTitle || (emptyUrl && !mEditingFolder)) {
if (emptyTitle) {
mTitle.setError(r.getText(R.string.bookmark_needs_title));
}
@@ -189,59 +731,105 @@ public class AddBookmarkPage extends Activity {
mAddress.setError(r.getText(R.string.bookmark_needs_url));
}
return false;
+
}
String url = unfilteredUrl.trim();
- try {
- // We allow bookmarks with a javascript: scheme, but these will in most cases
- // fail URI parsing, so don't try it if that's the kind of bookmark we have.
-
- if (!url.toLowerCase().startsWith("javascript:")) {
- URI uriObj = new URI(url);
- String scheme = uriObj.getScheme();
- if (!Bookmarks.urlHasAcceptableScheme(url)) {
- // If the scheme was non-null, let the user know that we
- // can't save their bookmark. If it was null, we'll assume
- // they meant http when we parse it in the WebAddress class.
- if (scheme != null) {
- mAddress.setError(r.getText(R.string.bookmark_cannot_save_url));
- return false;
- }
- WebAddress address;
- try {
- address = new WebAddress(unfilteredUrl);
- } catch (ParseException e) {
- throw new URISyntaxException("", "");
- }
- if (address.mHost.length() == 0) {
- throw new URISyntaxException("", "");
+ if (!mEditingFolder) {
+ try {
+ // We allow bookmarks with a javascript: scheme, but these will in most cases
+ // fail URI parsing, so don't try it if that's the kind of bookmark we have.
+
+ if (!url.toLowerCase().startsWith("javascript:")) {
+ URI uriObj = new URI(url);
+ String scheme = uriObj.getScheme();
+ if (!Bookmarks.urlHasAcceptableScheme(url)) {
+ // If the scheme was non-null, let the user know that we
+ // can't save their bookmark. If it was null, we'll assume
+ // they meant http when we parse it in the WebAddress class.
+ if (scheme != null) {
+ mAddress.setError(r.getText(R.string.bookmark_cannot_save_url));
+ return false;
+ }
+ WebAddress address;
+ try {
+ address = new WebAddress(unfilteredUrl);
+ } catch (ParseException e) {
+ throw new URISyntaxException("", "");
+ }
+ if (address.getHost().length() == 0) {
+ throw new URISyntaxException("", "");
+ }
+ url = address.toString();
}
- url = address.toString();
}
+ } catch (URISyntaxException e) {
+ mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
+ return false;
}
- } catch (URISyntaxException e) {
- mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
- return false;
}
+ if (mSaveToHomeScreen) {
+ mEditingExisting = false;
+ }
+
+ boolean urlUnmodified = url.equals(mOriginalUrl);
+
if (mEditingExisting) {
- mMap.putString("title", title);
- mMap.putString("url", url);
- mMap.putBoolean("invalidateThumbnail", !url.equals(mOriginalUrl));
- setResult(RESULT_OK, (new Intent()).setAction(
- getIntent().toString()).putExtras(mMap));
+ Long id = mMap.getLong(BrowserContract.Bookmarks._ID);
+ ContentValues values = new ContentValues();
+ values.put(BrowserContract.Bookmarks.TITLE, title);
+ values.put(BrowserContract.Bookmarks.PARENT, mCurrentFolder);
+ if (!mEditingFolder) {
+ values.put(BrowserContract.Bookmarks.URL, url);
+ if (!urlUnmodified) {
+ values.putNull(BrowserContract.Bookmarks.THUMBNAIL);
+ }
+ }
+ if (values.size() > 0) {
+ new UpdateBookmarkTask(getApplicationContext(), id).execute(values);
+ }
+ setResult(RESULT_OK);
} else {
- // Post a message to write to the DB.
+ Bitmap thumbnail;
+ Bitmap favicon;
+ if (urlUnmodified) {
+ thumbnail = (Bitmap) mMap.getParcelable(
+ BrowserContract.Bookmarks.THUMBNAIL);
+ favicon = (Bitmap) mMap.getParcelable(
+ BrowserContract.Bookmarks.FAVICON);
+ } else {
+ thumbnail = null;
+ favicon = null;
+ }
+
Bundle bundle = new Bundle();
- bundle.putString("title", title);
- bundle.putString("url", url);
- bundle.putParcelable("thumbnail", mThumbnail);
- bundle.putBoolean("invalidateThumbnail", !url.equals(mOriginalUrl));
- bundle.putString("touchIconUrl", mTouchIconUrl);
- Message msg = Message.obtain(mHandler, SAVE_BOOKMARK);
- msg.setData(bundle);
- // Start a new thread so as to not slow down the UI
- Thread t = new Thread(new SaveBookmarkRunnable(getApplicationContext(), msg));
- t.start();
+ bundle.putString(BrowserContract.Bookmarks.TITLE, title);
+ bundle.putString(BrowserContract.Bookmarks.URL, url);
+ bundle.putParcelable(BrowserContract.Bookmarks.FAVICON, favicon);
+
+ if (mSaveToHomeScreen) {
+ if (mTouchIconUrl != null && urlUnmodified) {
+ Message msg = Message.obtain(mHandler,
+ TOUCH_ICON_DOWNLOADED);
+ msg.setData(bundle);
+ DownloadTouchIcon icon = new DownloadTouchIcon(this, msg,
+ mMap.getString(USER_AGENT));
+ icon.execute(mTouchIconUrl);
+ } else {
+ sendBroadcast(BookmarkUtils.createAddToHomeIntent(this, url,
+ title, null /*touchIcon*/, favicon));
+ }
+ } else {
+ bundle.putParcelable(BrowserContract.Bookmarks.THUMBNAIL, thumbnail);
+ bundle.putBoolean(REMOVE_THUMBNAIL, !urlUnmodified);
+ bundle.putString(TOUCH_ICON_URL, mTouchIconUrl);
+ // Post a message to write to the DB.
+ Message msg = Message.obtain(mHandler, SAVE_BOOKMARK);
+ msg.setData(bundle);
+ // Start a new thread so as to not slow down the UI
+ Thread t = new Thread(new SaveBookmarkRunnable(getApplicationContext(), msg));
+ t.start();
+ }
setResult(RESULT_OK);
LogTag.logBookmarkAdded(url, "bookmarkview");
}
diff --git a/src/com/android/browser/AutoFillProfileDatabase.java b/src/com/android/browser/AutoFillProfileDatabase.java
new file mode 100644
index 000000000..3345e9258
--- /dev/null
+++ b/src/com/android/browser/AutoFillProfileDatabase.java
@@ -0,0 +1,154 @@
+/*
+ * 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.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+import android.util.Log;
+import android.webkit.WebSettings.AutoFillProfile;
+
+public class AutoFillProfileDatabase {
+
+ static final String LOGTAG = "AutoFillProfileDatabase";
+
+ static final String DATABASE_NAME = "autofill.db";
+ static final int DATABASE_VERSION = 2;
+ static final String PROFILES_TABLE_NAME = "profiles";
+ private AutoFillProfileDatabaseHelper mOpenHelper;
+ private static AutoFillProfileDatabase sInstance;
+
+ public static final class Profiles implements BaseColumns {
+ private Profiles() { }
+
+ static final String FULL_NAME = "fullname";
+ static final String EMAIL_ADDRESS = "email";
+ static final String COMPANY_NAME = "companyname";
+ static final String ADDRESS_LINE_1 = "addressline1";
+ static final String ADDRESS_LINE_2 = "addressline2";
+ static final String CITY = "city";
+ static final String STATE = "state";
+ static final String ZIP_CODE = "zipcode";
+ static final String COUNTRY = "country";
+ static final String PHONE_NUMBER = "phone";
+ }
+
+ private static class AutoFillProfileDatabaseHelper extends SQLiteOpenHelper {
+ AutoFillProfileDatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + PROFILES_TABLE_NAME + " ("
+ + Profiles._ID + " INTEGER PRIMARY KEY,"
+ + Profiles.FULL_NAME + " TEXT,"
+ + Profiles.EMAIL_ADDRESS + " TEXT,"
+ + Profiles.COMPANY_NAME + " TEXT,"
+ + Profiles.ADDRESS_LINE_1 + " TEXT,"
+ + Profiles.ADDRESS_LINE_2 + " TEXT,"
+ + Profiles.CITY + " TEXT,"
+ + Profiles.STATE + " TEXT,"
+ + Profiles.ZIP_CODE + " TEXT,"
+ + Profiles.COUNTRY + " TEXT,"
+ + Profiles.PHONE_NUMBER + " TEXT"
+ + " );");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(LOGTAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS " + PROFILES_TABLE_NAME);
+ onCreate(db);
+ }
+ }
+
+ private AutoFillProfileDatabase(Context context) {
+ mOpenHelper = new AutoFillProfileDatabaseHelper(context);
+ }
+
+ public static AutoFillProfileDatabase getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new AutoFillProfileDatabase(context);
+ }
+ return sInstance;
+ }
+
+ private SQLiteDatabase getDatabase(boolean writable) {
+ return writable ? mOpenHelper.getWritableDatabase() : mOpenHelper.getReadableDatabase();
+ }
+
+ public void addOrUpdateProfile(final int id, AutoFillProfile profile) {
+ final String sql = "INSERT OR REPLACE INTO " + PROFILES_TABLE_NAME + " ("
+ + Profiles._ID + ","
+ + Profiles.FULL_NAME + ","
+ + Profiles.EMAIL_ADDRESS + ","
+ + Profiles.COMPANY_NAME + ","
+ + Profiles.ADDRESS_LINE_1 + ","
+ + Profiles.ADDRESS_LINE_2 + ","
+ + Profiles.CITY + ","
+ + Profiles.STATE + ","
+ + Profiles.ZIP_CODE + ","
+ + Profiles.COUNTRY + ","
+ + Profiles.PHONE_NUMBER
+ + ") VALUES (?,?,?,?,?,?,?,?,?,?,?);";
+ final Object[] params = { id,
+ profile.getFullName(),
+ profile.getEmailAddress(),
+ profile.getCompanyName(),
+ profile.getAddressLine1(),
+ profile.getAddressLine2(),
+ profile.getCity(),
+ profile.getState(),
+ profile.getZipCode(),
+ profile.getCountry(),
+ profile.getPhoneNumber() };
+ getDatabase(true).execSQL(sql, params);
+ }
+
+ public Cursor getProfile(int id) {
+ final String[] cols = {
+ Profiles.FULL_NAME,
+ Profiles.EMAIL_ADDRESS,
+ Profiles.COMPANY_NAME,
+ Profiles.ADDRESS_LINE_1,
+ Profiles.ADDRESS_LINE_2,
+ Profiles.CITY,
+ Profiles.STATE,
+ Profiles.ZIP_CODE,
+ Profiles.COUNTRY,
+ Profiles.PHONE_NUMBER
+ };
+
+ final String[] selectArgs = { Integer.toString(id) };
+ return getDatabase(false).query(PROFILES_TABLE_NAME, cols, Profiles._ID + "=?", selectArgs,
+ null, null, null, "1");
+ }
+
+ public void dropProfile(int id) {
+ final String sql = "DELETE FROM " + PROFILES_TABLE_NAME +" WHERE " + Profiles._ID + " = ?;";
+ final Object[] params = { id };
+ getDatabase(true).execSQL(sql, params);
+ }
+
+ public void close() {
+ mOpenHelper.close();
+ }
+}
diff --git a/src/com/android/browser/AutoFillSettingsFragment.java b/src/com/android/browser/AutoFillSettingsFragment.java
new file mode 100644
index 000000000..06a425638
--- /dev/null
+++ b/src/com/android/browser/AutoFillSettingsFragment.java
@@ -0,0 +1,168 @@
+/*
+ * 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.app.Fragment;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.view.LayoutInflater;
+import android.webkit.WebSettings.AutoFillProfile;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+public class AutoFillSettingsFragment extends Fragment {
+
+ private static final String LOGTAG = "AutoFillSettingsFragment";
+
+ private EditText mFullNameEdit;
+ private EditText mEmailEdit;
+ private EditText mCompanyEdit;
+ private EditText mAddressLine1Edit;
+ private EditText mAddressLine2Edit;
+ private EditText mCityEdit;
+ private EditText mStateEdit;
+ private EditText mZipEdit;
+ private EditText mCountryEdit;
+ private EditText mPhoneEdit;
+
+ // Used to display toast after DB interactions complete.
+ private Handler mHandler;
+
+ private final static int PROFILE_SAVED_MSG = 100;
+ private final static int PROFILE_DELETED_MSG = 101;
+
+ // For now we support just one profile so it's safe to hardcode the
+ // id to 1 here. In the future this unique identifier will be set
+ // dynamically.
+ private int mUniqueId = 1;
+
+ public AutoFillSettingsFragment() {
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case PROFILE_SAVED_MSG:
+ Toast.makeText(getActivity(), R.string.autofill_profile_successful_save,
+ Toast.LENGTH_SHORT).show();
+ break;
+
+ case PROFILE_DELETED_MSG:
+ Toast.makeText(getActivity(), R.string.autofill_profile_successful_delete,
+ Toast.LENGTH_SHORT).show();
+ break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.autofill_settings_fragment, container, false);
+
+ mFullNameEdit = (EditText)v.findViewById(R.id.autofill_profile_editor_name_edit);
+ mEmailEdit = (EditText)v.findViewById(R.id.autofill_profile_editor_email_address_edit);
+ mCompanyEdit = (EditText)v.findViewById(R.id.autofill_profile_editor_company_name_edit);
+ mAddressLine1Edit = (EditText)v.findViewById(
+ R.id.autofill_profile_editor_address_line_1_edit);
+ mAddressLine2Edit = (EditText)v.findViewById(
+ R.id.autofill_profile_editor_address_line_2_edit);
+ mCityEdit = (EditText)v.findViewById(R.id.autofill_profile_editor_city_edit);
+ mStateEdit = (EditText)v.findViewById(R.id.autofill_profile_editor_state_edit);
+ mZipEdit = (EditText)v.findViewById(R.id.autofill_profile_editor_zip_code_edit);
+ mCountryEdit = (EditText)v.findViewById(R.id.autofill_profile_editor_country_edit);
+ mPhoneEdit = (EditText)v.findViewById(R.id.autofill_profile_editor_phone_number_edit);
+
+ Button saveButton = (Button)v.findViewById(R.id.autofill_profile_editor_save_button);
+ saveButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View button) {
+ AutoFillProfile newProfile = new AutoFillProfile(
+ mUniqueId,
+ mFullNameEdit.getText().toString(),
+ mEmailEdit.getText().toString(),
+ mCompanyEdit.getText().toString(),
+ mAddressLine1Edit.getText().toString(),
+ mAddressLine2Edit.getText().toString(),
+ mCityEdit.getText().toString(),
+ mStateEdit.getText().toString(),
+ mZipEdit.getText().toString(),
+ mCountryEdit.getText().toString(),
+ mPhoneEdit.getText().toString());
+
+ BrowserSettings.getInstance().setAutoFillProfile(getActivity(), newProfile,
+ mHandler.obtainMessage(PROFILE_SAVED_MSG));
+ }
+ });
+
+ Button deleteButton = (Button)v.findViewById(R.id.autofill_profile_editor_delete_button);
+ deleteButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View button) {
+ // Clear the UI.
+ mFullNameEdit.setText("");
+ mEmailEdit.setText("");
+ mCompanyEdit.setText("");
+ mAddressLine1Edit.setText("");
+ mAddressLine2Edit.setText("");
+ mCityEdit.setText("");
+ mStateEdit.setText("");
+ mZipEdit.setText("");
+ mCountryEdit.setText("");
+ mPhoneEdit.setText("");
+
+ // Update browser settings and native with a null profile. This will
+ // trigger the current profile to get deleted from the DB.
+ BrowserSettings.getInstance().setAutoFillProfile(getActivity(), null,
+ mHandler.obtainMessage(PROFILE_DELETED_MSG));
+ }
+ });
+
+ Button cancelButton = (Button)v.findViewById(R.id.autofill_profile_editor_cancel_button);
+ cancelButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View button) {
+ getFragmentManager().popBackStack();
+ }
+ });
+
+ // Populate the text boxes with any pre existing AutoFill data.
+ AutoFillProfile activeProfile = BrowserSettings.getInstance().getAutoFillProfile();
+ if (activeProfile != null) {
+ mFullNameEdit.setText(activeProfile.getFullName());
+ mEmailEdit.setText(activeProfile.getEmailAddress());
+ mCompanyEdit.setText(activeProfile.getCompanyName());
+ mAddressLine1Edit.setText(activeProfile.getAddressLine1());
+ mAddressLine2Edit.setText(activeProfile.getAddressLine2());
+ mCityEdit.setText(activeProfile.getCity());
+ mStateEdit.setText(activeProfile.getState());
+ mZipEdit.setText(activeProfile.getZipCode());
+ mCountryEdit.setText(activeProfile.getCountry());
+ mPhoneEdit.setText(activeProfile.getPhoneNumber());
+ }
+
+ return v;
+ }
+}
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
new file mode 100644
index 000000000..5f8944fc8
--- /dev/null
+++ b/src/com/android/browser/BaseUi.java
@@ -0,0 +1,895 @@
+/*
+ * 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.app.ActionBar;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.webkit.WebChromeClient;
+import android.webkit.WebHistoryItem;
+import android.webkit.WebView;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import java.util.List;
+
+/**
+ * UI interface definitions
+ */
+public class BaseUi implements UI, WebViewFactory {
+
+ private static final String LOGTAG = "BaseUi";
+
+ private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
+ new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ private static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
+ new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ Gravity.CENTER);
+
+ Activity mActivity;
+ UiController mUiController;
+ TabControl mTabControl;
+ private Tab mActiveTab;
+
+ private Drawable mSecLockIcon;
+ private Drawable mMixLockIcon;
+
+ private boolean mXLargeScreenSize;
+ private FrameLayout mBrowserFrameLayout;
+ private FrameLayout mContentView;
+ private FrameLayout mCustomViewContainer;
+ private TitleBarBase mTitleBar;
+ private TitleBarBase mFakeTitleBar;
+ private TabBar mTabBar;
+
+ private View mCustomView;
+ private WebChromeClient.CustomViewCallback mCustomViewCallback;
+
+ private CombinedBookmarkHistoryView mComboView;
+
+ private LinearLayout mErrorConsoleContainer = null;
+
+ private Toast mStopToast;
+ private ActiveTabsPage mActiveTabsPage;
+
+ // the default <video> poster
+ private Bitmap mDefaultVideoPoster;
+ // the video progress view
+ private View mVideoProgressView;
+
+ boolean mExtendedMenuOpen;
+ boolean mOptionsMenuOpen;
+
+ private boolean mActivityPaused;
+
+ public BaseUi(Activity browser, UiController controller) {
+ mActivity = browser;
+ mUiController = controller;
+ mTabControl = controller.getTabControl();
+ Resources res = mActivity.getResources();
+ mSecLockIcon = res.getDrawable(R.drawable.ic_secure);
+ mMixLockIcon = res.getDrawable(R.drawable.ic_partial_secure);
+
+
+ mXLargeScreenSize = (res.getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK)
+ == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+
+ FrameLayout frameLayout = (FrameLayout) mActivity.getWindow()
+ .getDecorView().findViewById(android.R.id.content);
+ mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(mActivity)
+ .inflate(R.layout.custom_screen, null);
+ mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(
+ R.id.main_content);
+ mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout
+ .findViewById(R.id.error_console);
+ mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
+ .findViewById(R.id.fullscreen_custom_content);
+ frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
+
+ if (mXLargeScreenSize) {
+ mTitleBar = new TitleBarXLarge(mActivity, mUiController);
+ mTitleBar.setProgress(100);
+ mFakeTitleBar = new TitleBarXLarge(mActivity, mUiController);
+ ActionBar actionBar = mActivity.getActionBar();
+ mTabBar = new TabBar(mActivity, mUiController, this);
+ actionBar.setCustomNavigationMode(mTabBar);
+ } else {
+ mTitleBar = new TitleBar(mActivity, mUiController);
+ // mTitleBar will be always be shown in the fully loaded mode on
+ // phone
+ mTitleBar.setProgress(100);
+ mFakeTitleBar = new TitleBar(mActivity, mUiController);
+ }
+ }
+
+ // webview factory
+
+ @Override
+ public WebView createWebView(boolean privateBrowsing) {
+ // Create a new WebView
+ ScrollWebView w = new ScrollWebView(mActivity, null,
+ android.R.attr.webViewStyle, privateBrowsing);
+ w.setScrollbarFadingEnabled(true);
+ w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
+ w.setMapTrackballToArrowKeys(false); // use trackball directly
+ // Enable the built-in zoom
+ w.getSettings().setBuiltInZoomControls(true);
+ if (mXLargeScreenSize) {
+ w.setScrollListener(mTabBar);
+ w.getSettings().setDisplayZoomControls(false);
+ }
+
+ // Add this WebView to the settings observer list and update the
+ // settings
+ final BrowserSettings s = BrowserSettings.getInstance();
+ s.addObserver(w.getSettings()).update(s, null);
+ return w;
+ }
+
+ @Override
+ public WebView createSubWebView(boolean privateBrowsing) {
+ ScrollWebView web = (ScrollWebView) createWebView(privateBrowsing);
+ if (mXLargeScreenSize) {
+ // no scroll listener for subview
+ web.setScrollListener(null);
+ }
+ return web;
+ }
+
+ void stopWebViewScrolling() {
+ ScrollWebView web = (ScrollWebView) mUiController.getCurrentWebView();
+ if (web != null) {
+ web.stopScroll();
+ }
+ }
+
+ private void cancelStopToast() {
+ if (mStopToast != null) {
+ mStopToast.cancel();
+ mStopToast = null;
+ }
+ }
+
+ // lifecycle
+
+ public void onPause() {
+ // FIXME: This removes the active tabs page and resets the menu to
+ // MAIN_MENU. A better solution might be to do this work in onNewIntent
+ // but then we would need to save it in onSaveInstanceState and restore
+ // it in onCreate/onRestoreInstanceState
+ if (mActiveTabsPage != null) {
+ mUiController.removeActiveTabsPage(true);
+ }
+ cancelStopToast();
+ mActivityPaused = true;
+ }
+
+ public void onResume() {
+ mActivityPaused = false;
+ }
+
+ public void onDestroy() {
+ hideFakeTitleBar();
+ }
+
+ public void onConfigurationChanged(Configuration config) {
+ }
+
+ // key handling
+
+ @Override
+ public boolean onBackKey() {
+ if (mActiveTabsPage != null) {
+ // if tab page is showing, hide it
+ mUiController.removeActiveTabsPage(true);
+ return true;
+ }
+ if (mComboView != null) {
+ if (!mComboView.onBackPressed()) {
+ mUiController.removeComboView();
+ }
+ return true;
+ }
+ if (mCustomView != null) {
+ mUiController.hideCustomView();
+ return true;
+ }
+ return false;
+ }
+
+ // WebView callbacks
+
+ @Override
+ public void onPageStarted(Tab tab, String url, Bitmap favicon) {
+ if (mXLargeScreenSize) {
+ mTabBar.onPageStarted(tab, url, favicon);
+ }
+ if (tab.inForeground()) {
+ resetLockIcon(tab, url);
+ setUrlTitle(tab, url, null);
+ setFavicon(tab, favicon);
+ }
+
+ }
+
+ @Override
+ public void onPageFinished(Tab tab, String url) {
+ if (mXLargeScreenSize) {
+ mTabBar.onPageFinished(tab);
+ }
+ if (tab.inForeground()) {
+ // Reset the title and icon in case we stopped a provisional load.
+ resetTitleAndIcon(tab);
+ // Update the lock icon image only once we are done loading
+ updateLockIconToLatest(tab);
+ }
+ }
+
+ @Override
+ public void onPageStopped(Tab tab) {
+ cancelStopToast();
+ if (tab.inForeground()) {
+ mStopToast = Toast
+ .makeText(mActivity, R.string.stopping, Toast.LENGTH_SHORT);
+ mStopToast.show();
+ }
+ }
+
+ @Override
+ public void onProgressChanged(Tab tab, int progress) {
+ if (mXLargeScreenSize) {
+ mTabBar.onProgress(tab, progress);
+ }
+ if (tab.inForeground()) {
+ mFakeTitleBar.setProgress(progress);
+ if (progress == 100) {
+ if (!mOptionsMenuOpen || !mExtendedMenuOpen) {
+ hideFakeTitleBar();
+ }
+ } else {
+ if (!mOptionsMenuOpen || mExtendedMenuOpen) {
+ showFakeTitleBar();
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean needsRestoreAllTabs() {
+ return mXLargeScreenSize;
+ }
+
+ @Override
+ public void addTab(Tab tab) {
+ if (mXLargeScreenSize) {
+ mTabBar.onNewTab(tab);
+ }
+ }
+
+ @Override
+ public void setActiveTab(Tab tab) {
+ if ((tab != mActiveTab) && (mActiveTab != null)) {
+ removeTabFromContentView(mActiveTab);
+ }
+ mActiveTab = tab;
+ attachTabToContentView(tab);
+ setShouldShowErrorConsole(tab, mUiController.shouldShowErrorConsole());
+
+ WebView view = tab.getWebView();
+ // TabControl.setCurrentTab has been called before this,
+ // so the tab is guaranteed to have a webview
+ if (view == null) {
+ Log.e(LOGTAG, "active tab with no webview detected");
+ return;
+ }
+ view.setEmbeddedTitleBar(mTitleBar);
+ if (tab.isInVoiceSearchMode()) {
+ showVoiceTitleBar(tab.getVoiceDisplayTitle());
+ } else {
+ revertVoiceTitleBar(tab);
+ }
+
+ if (mXLargeScreenSize) {
+ // Request focus on the top window.
+ mTabBar.onSetActiveTab(tab);
+ }
+ resetTitleIconAndProgress(tab);
+ updateLockIconToLatest(tab);
+ tab.getTopWindow().requestFocus();
+ }
+
+ @Override
+ public void updateTabs(List<Tab> tabs) {
+ if (mXLargeScreenSize) {
+ mTabBar.updateTabs(tabs);
+ }
+ }
+
+ @Override
+ public void removeTab(Tab tab) {
+ if (mActiveTab == tab) {
+ removeTabFromContentView(tab);
+ mActiveTab = null;
+ }
+ if (mXLargeScreenSize) {
+ mTabBar.onRemoveTab(tab);
+ }
+ }
+
+ @Override
+ public void detachTab(Tab tab) {
+ removeTabFromContentView(tab);
+ }
+
+ @Override
+ public void attachTab(Tab tab) {
+ attachTabToContentView(tab);
+ }
+
+ private void attachTabToContentView(Tab tab) {
+ if (tab.getWebView() == null) {
+ return;
+ }
+ View container = tab.getViewContainer();
+ WebView mainView = tab.getWebView();
+ // Attach the WebView to the container and then attach the
+ // container to the content view.
+ FrameLayout wrapper =
+ (FrameLayout) container.findViewById(R.id.webview_wrapper);
+ ViewGroup parent = (ViewGroup) mainView.getParent();
+ if (parent != wrapper) {
+ if (parent != null) {
+ Log.w(LOGTAG, "mMainView already has a parent in"
+ + " attachTabToContentView!");
+ parent.removeView(mainView);
+ }
+ wrapper.addView(mainView);
+ } else {
+ Log.w(LOGTAG, "mMainView is already attached to wrapper in"
+ + " attachTabToContentView!");
+ }
+ parent = (ViewGroup) container.getParent();
+ if (parent != mContentView) {
+ if (parent != null) {
+ Log.w(LOGTAG, "mContainer already has a parent in"
+ + " attachTabToContentView!");
+ parent.removeView(container);
+ }
+ mContentView.addView(container, COVER_SCREEN_PARAMS);
+ } else {
+ Log.w(LOGTAG, "mContainer is already attached to content in"
+ + " attachTabToContentView!");
+ }
+ mUiController.attachSubWindow(tab);
+ }
+
+ private void removeTabFromContentView(Tab tab) {
+ // Remove the container that contains the main WebView.
+ WebView mainView = tab.getWebView();
+ View container = tab.getViewContainer();
+ if (mainView == null) {
+ return;
+ }
+ // Remove the container from the content and then remove the
+ // WebView from the container. This will trigger a focus change
+ // needed by WebView.
+ FrameLayout wrapper =
+ (FrameLayout) container.findViewById(R.id.webview_wrapper);
+ wrapper.removeView(mainView);
+ mContentView.removeView(container);
+ mUiController.endActionMode();
+ mUiController.removeSubWindow(tab);
+ ErrorConsoleView errorConsole = tab.getErrorConsole(false);
+ if (errorConsole != null) {
+ mErrorConsoleContainer.removeView(errorConsole);
+ }
+ mainView.setEmbeddedTitleBar(null);
+ }
+
+ /**
+ * create a sub window container and webview for the tab
+ * Note: this methods operates through side-effects for now
+ * it sets both the subView and subViewContainer for the given tab
+ * @param tab tab to create the sub window for
+ * @param subView webview to be set as a subwindow for the tab
+ */
+ @Override
+ public void createSubWindow(Tab tab, WebView subView) {
+ View subViewContainer = mActivity.getLayoutInflater().inflate(
+ R.layout.browser_subwindow, null);
+ ViewGroup inner = (ViewGroup) subViewContainer
+ .findViewById(R.id.inner_container);
+ inner.addView(subView, new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ final ImageButton cancel = (ImageButton) subViewContainer
+ .findViewById(R.id.subwindow_close);
+ final WebView cancelSubView = subView;
+ cancel.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ cancelSubView.getWebChromeClient().onCloseWindow(cancelSubView);
+ }
+ });
+ tab.setSubWebView(subView);
+ tab.setSubViewContainer(subViewContainer);
+ }
+
+ /**
+ * Remove the sub window from the content view.
+ */
+ @Override
+ public void removeSubWindow(View subviewContainer) {
+ mContentView.removeView(subviewContainer);
+ mUiController.endActionMode();
+ }
+
+ /**
+ * Attach the sub window to the content view.
+ */
+ @Override
+ public void attachSubWindow(View container) {
+ if (container.getParent() != null) {
+ // already attached, remove first
+ ((ViewGroup) container.getParent()).removeView(container);
+ }
+ mContentView.addView(container, COVER_SCREEN_PARAMS);
+ }
+
+ void showFakeTitleBar() {
+ if (!isFakeTitleBarShowing() && mActiveTabsPage == null &&
+ !mActivityPaused) {
+ WebView mainView = mUiController.getCurrentWebView();
+ // if there is no current WebView, don't show the faked title bar;
+ 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 (mUiController.isInCustomActionMode()) {
+ // Do not show the fake title bar, while a custom ActionMode
+ // (i.e. find or select) is showing.
+ return;
+ }
+ if (mXLargeScreenSize) {
+ mContentView.addView(mFakeTitleBar);
+ mTabBar.onShowTitleBar();
+ } else {
+ WindowManager manager = (WindowManager)
+ mActivity.getSystemService(Context.WINDOW_SERVICE);
+
+ // Add the title bar to the window manager so it can receive
+ // touches
+ // while the menu is up
+ WindowManager.LayoutParams params =
+ new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ params.gravity = Gravity.TOP;
+ boolean atTop = mainView.getScrollY() == 0;
+ params.windowAnimations = atTop ? 0 : R.style.TitleBar;
+ manager.addView(mFakeTitleBar, params);
+ }
+ }
+ }
+
+ void hideFakeTitleBar() {
+ if (!isFakeTitleBarShowing()) return;
+ if (mXLargeScreenSize) {
+ mContentView.removeView(mFakeTitleBar);
+ mTabBar.onHideTitleBar();
+ } else {
+ WindowManager.LayoutParams params =
+ (WindowManager.LayoutParams) mFakeTitleBar.getLayoutParams();
+ WebView mainView = mUiController.getCurrentWebView();
+ // Although we decided whether or not to animate based on the
+ // current
+ // scroll position, the scroll position may have changed since the
+ // fake title bar was displayed. Make sure it has the appropriate
+ // animation/lack thereof before removing.
+ params.windowAnimations =
+ mainView != null && mainView.getScrollY() == 0 ?
+ 0 : R.style.TitleBar;
+ WindowManager manager = (WindowManager) mActivity
+ .getSystemService(Context.WINDOW_SERVICE);
+ manager.updateViewLayout(mFakeTitleBar, params);
+ manager.removeView(mFakeTitleBar);
+ }
+ }
+
+ boolean isFakeTitleBarShowing() {
+ return (mFakeTitleBar.getParent() != null);
+ }
+
+ @Override
+ public void showComboView(boolean startWithHistory, Bundle extras) {
+ mComboView = new CombinedBookmarkHistoryView(mActivity,
+ mUiController,
+ startWithHistory ?
+ CombinedBookmarkHistoryView.FRAGMENT_ID_HISTORY
+ : CombinedBookmarkHistoryView.FRAGMENT_ID_BOOKMARKS,
+ extras);
+ mTitleBar.setVisibility(View.GONE);
+ hideFakeTitleBar();
+ mContentView.addView(mComboView, COVER_SCREEN_PARAMS);
+ }
+
+ /**
+ * dismiss the ComboPage
+ */
+ @Override
+ public void hideComboView() {
+ if (mComboView != null) {
+ mContentView.removeView(mComboView);
+ mTitleBar.setVisibility(View.VISIBLE);
+ mComboView = null;
+ }
+ }
+
+ @Override
+ public void showCustomView(View view,
+ WebChromeClient.CustomViewCallback callback) {
+ // if a view already exists then immediately terminate the new one
+ if (mCustomView != null) {
+ callback.onCustomViewHidden();
+ return;
+ }
+
+ // Add the custom view to its container.
+ mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
+ mCustomView = view;
+ mCustomViewCallback = callback;
+ // Hide the content view.
+ mContentView.setVisibility(View.GONE);
+ // Finally show the custom view container.
+ setStatusBarVisibility(false);
+ mCustomViewContainer.setVisibility(View.VISIBLE);
+ mCustomViewContainer.bringToFront();
+ }
+
+ @Override
+ public void onHideCustomView() {
+ if (mCustomView == null)
+ return;
+
+ // Hide the custom view.
+ mCustomView.setVisibility(View.GONE);
+ // Remove the custom view from its container.
+ mCustomViewContainer.removeView(mCustomView);
+ mCustomView = null;
+ mCustomViewContainer.setVisibility(View.GONE);
+ mCustomViewCallback.onCustomViewHidden();
+ // Show the content view.
+ setStatusBarVisibility(true);
+ mContentView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public boolean isCustomViewShowing() {
+ return mCustomView != null;
+ }
+
+ @Override
+ public void showVoiceTitleBar(String title) {
+ mTitleBar.setInVoiceMode(true);
+ mTitleBar.setDisplayTitle(title);
+ mFakeTitleBar.setInVoiceMode(true);
+ mFakeTitleBar.setDisplayTitle(title);
+ }
+
+ @Override
+ public void revertVoiceTitleBar(Tab tab) {
+ mTitleBar.setInVoiceMode(false);
+ String url = tab.getCurrentUrl();
+ mTitleBar.setDisplayTitle(url);
+ mFakeTitleBar.setInVoiceMode(false);
+ mFakeTitleBar.setDisplayTitle(url);
+ }
+
+ // -------------------------------------------------------------------------
+
+ @Override
+ public void resetTitleAndRevertLockIcon(Tab tab) {
+ tab.revertLockIcon();
+ updateLockIconToLatest(tab);
+ resetTitleIconAndProgress(tab);
+ }
+
+ /**
+ * Resets the lock icon. This method is called when we start a new load and
+ * know the url to be loaded.
+ */
+ private void resetLockIcon(Tab tab, String url) {
+ // Save the lock-icon state (we revert to it if the load gets cancelled)
+ tab.resetLockIcon(url);
+ updateLockIconImage(Tab.LOCK_ICON_UNSECURE);
+ }
+
+ /**
+ * Update the lock icon to correspond to our latest state.
+ */
+ private void updateLockIconToLatest(Tab t) {
+ if (t != null) {
+ updateLockIconImage(t.getLockIconType());
+ }
+ }
+
+ /**
+ * Reset the title, favicon, and progress.
+ */
+ private void resetTitleIconAndProgress(Tab tab) {
+ WebView current = tab.getWebView();
+ if (current == null) {
+ return;
+ }
+ resetTitleAndIcon(current);
+ int progress = current.getProgress();
+ current.getWebChromeClient().onProgressChanged(current, progress);
+ }
+
+ @Override
+ public void resetTitleAndIcon(Tab tab) {
+ WebView current = tab.getWebView();
+ if (current != null) {
+ resetTitleAndIcon(current);
+ }
+ }
+
+ // Reset the title and the icon based on the given item.
+ private void resetTitleAndIcon(WebView view) {
+ WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
+ Tab tab = mTabControl.getTabFromView(view);
+ if (item != null) {
+ setUrlTitle(tab, item.getUrl(), item.getTitle());
+ setFavicon(tab, item.getFavicon());
+ } else {
+ setUrlTitle(tab, null, null);
+ setFavicon(tab, null);
+ }
+ }
+
+ /**
+ * Updates the lock-icon image in the title-bar.
+ */
+ private void updateLockIconImage(int lockIconType) {
+ Drawable d = null;
+ if (lockIconType == Tab.LOCK_ICON_SECURE) {
+ d = mSecLockIcon;
+ } else if (lockIconType == Tab.LOCK_ICON_MIXED) {
+ d = mMixLockIcon;
+ }
+ mTitleBar.setLock(d);
+ mFakeTitleBar.setLock(d);
+ }
+
+ // active tabs page
+
+ public void showActiveTabsPage() {
+ mActiveTabsPage = new ActiveTabsPage(mActivity, mUiController);
+ mTitleBar.setVisibility(View.GONE);
+ hideFakeTitleBar();
+ mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
+ mActiveTabsPage.requestFocus();
+ }
+
+ /**
+ * Remove the active tabs page.
+ * @param needToAttach If true, the active tabs page did not attach a tab
+ * to the content view, so we need to do that here.
+ */
+ public void removeActiveTabsPage() {
+ mContentView.removeView(mActiveTabsPage);
+ mTitleBar.setVisibility(View.VISIBLE);
+ mActiveTabsPage = null;
+ }
+
+ // action mode callbacks
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ // hide the fake title bar when CAB is shown
+ hideFakeTitleBar();
+ }
+
+ @Override
+ public void onActionModeFinished(boolean inLoad) {
+ if (inLoad) {
+ // the titlebar was removed when the CAB was shown
+ // if the page is loading, show it again
+ showFakeTitleBar();
+ }
+ }
+
+ // menu handling callbacks
+
+ @Override
+ public void onOptionsMenuOpened() {
+ mOptionsMenuOpen = true;
+ // options menu opened, show fake title bar
+ showFakeTitleBar();
+ }
+
+ @Override
+ public void onExtendedMenuOpened() {
+ // Switching the menu to expanded view, so hide the
+ // title bar.
+ mExtendedMenuOpen = true;
+ hideFakeTitleBar();
+ }
+
+ @Override
+ public void onOptionsMenuClosed(boolean inLoad) {
+ mOptionsMenuOpen = false;
+ if (!inLoad) {
+ hideFakeTitleBar();
+ }
+ }
+
+ @Override
+ public void onExtendedMenuClosed(boolean inLoad) {
+ mExtendedMenuOpen = false;
+ if (inLoad) {
+ showFakeTitleBar();
+ }
+ }
+
+ @Override
+ public void onContextMenuCreated(Menu menu) {
+ hideFakeTitleBar();
+ }
+
+ @Override
+ public void onContextMenuClosed(Menu menu, boolean inLoad) {
+ if (inLoad) {
+ showFakeTitleBar();
+ }
+ }
+
+ @Override
+ public void onScroll(boolean titleVisible) {
+ if (mTabBar != null) {
+ mTabBar.onScroll(titleVisible);
+ }
+ }
+
+ // error console
+
+ @Override
+ public void setShouldShowErrorConsole(Tab tab, boolean flag) {
+ ErrorConsoleView errorConsole = tab.getErrorConsole(true);
+ if (flag) {
+ // Setting the show state of the console will cause it's the layout
+ // to be inflated.
+ if (errorConsole.numberOfErrors() > 0) {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+ } else {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+ }
+ if (errorConsole.getParent() != null) {
+ mErrorConsoleContainer.removeView(errorConsole);
+ }
+ // Now we can add it to the main view.
+ mErrorConsoleContainer.addView(errorConsole,
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ } else {
+ mErrorConsoleContainer.removeView(errorConsole);
+ }
+ }
+
+ private void setStatusBarVisibility(boolean visible) {
+ int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
+ mActivity.getWindow().setFlags(flag,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+
+ @Override
+ public void setUrlTitle(Tab tab, String url, String title) {
+ if (TextUtils.isEmpty(title)) {
+ if (TextUtils.isEmpty(url)) {
+ title = mActivity.getResources()
+ .getString(R.string.title_bar_loading);
+ } else {
+ title = url;
+ }
+ }
+ if (tab.isInVoiceSearchMode()) return;
+ if (tab.inForeground()) {
+ mTitleBar.setDisplayTitle(url);
+ mFakeTitleBar.setDisplayTitle(url);
+ }
+ if (mXLargeScreenSize) {
+ mTabBar.onUrlAndTitle(tab, url, title);
+ }
+ }
+
+ // Set the favicon in the title bar.
+ @Override
+ public void setFavicon(Tab tab, Bitmap icon) {
+ mTitleBar.setFavicon(icon);
+ mFakeTitleBar.setFavicon(icon);
+ if (mXLargeScreenSize) {
+ mTabBar.onFavicon(tab, icon);
+ }
+ }
+ @Override
+ public boolean showsWeb() {
+ return mCustomView == null && mActiveTabsPage == null
+ && mComboView == null;
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ if (!mXLargeScreenSize) {
+ final MenuItem newtab = menu.findItem(R.id.new_tab_menu_id);
+ newtab.setEnabled(mUiController.getTabControl().canCreateNewTab());
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Helper function for WebChromeClient
+ // -------------------------------------------------------------------------
+
+ @Override
+ public Bitmap getDefaultVideoPoster() {
+ if (mDefaultVideoPoster == null) {
+ mDefaultVideoPoster = BitmapFactory.decodeResource(
+ mActivity.getResources(), R.drawable.default_video_poster);
+ }
+ return mDefaultVideoPoster;
+ }
+
+ @Override
+ public View getVideoLoadingProgressView() {
+ if (mVideoProgressView == null) {
+ LayoutInflater inflater = LayoutInflater.from(mActivity);
+ mVideoProgressView = inflater.inflate(
+ R.layout.video_loading_progress, null);
+ }
+ return mVideoProgressView;
+ }
+
+}
diff --git a/src/com/android/browser/BookmarkUtils.java b/src/com/android/browser/BookmarkUtils.java
new file mode 100644
index 000000000..a63b90fb0
--- /dev/null
+++ b/src/com/android/browser/BookmarkUtils.java
@@ -0,0 +1,184 @@
+/*
+ * 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.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.preference.PreferenceManager;
+import android.provider.Browser;
+import android.provider.BrowserContract;
+import android.text.TextUtils;
+
+class BookmarkUtils {
+ private final static String LOGTAG = "BookmarkUtils";
+
+ // XXX: There is no public string defining this intent so if Home changes the value, we
+ // have to update this string.
+ private static final String INSTALL_SHORTCUT = "com.android.launcher.action.INSTALL_SHORTCUT";
+
+ enum BookmarkIconType {
+ ICON_INSTALLABLE_WEB_APP, // Icon for an installable web app (launches WebAppRuntime).
+ ICON_HOME_SHORTCUT // Icon for a shortcut on the home screen (launches Browser).
+ };
+
+ /**
+ * Creates an icon to be associated with this bookmark. If available, the apple touch icon
+ * will be used, else we draw our own depending on the type of "bookmark" being created.
+ */
+ static Bitmap createIcon(Context context, Bitmap touchIcon, Bitmap favicon,
+ BookmarkIconType type) {
+ int iconDimension = context.getResources().getDimensionPixelSize(
+ android.R.dimen.app_icon_size);
+
+ Bitmap bm = Bitmap.createBitmap(iconDimension, iconDimension, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bm);
+ Rect iconBounds = new Rect(0, 0, bm.getWidth(), bm.getHeight());
+
+ // Use the apple-touch-icon if available
+ if (touchIcon != null) {
+ drawTouchIconToCanvas(touchIcon, canvas, iconBounds);
+ } else {
+ // No touch icon so create our own.
+ // Set the background based on the type of shortcut (either webapp or home shortcut).
+ Bitmap icon = getIconBackground(context, type);
+
+ if (icon != null) {
+ // Now draw the correct icon background into our new bitmap.
+ canvas.drawBitmap(icon, null, iconBounds, null);
+ }
+
+ // If we have a favicon, overlay it in a nice rounded white box on top of the
+ // background.
+ if (favicon != null) {
+ drawFaviconToCanvas(favicon, canvas, iconBounds,
+ context.getResources().getDisplayMetrics().density);
+ }
+ }
+ return bm;
+ }
+
+ /**
+ * Convenience method for creating an intent that will add a shortcut to the home screen.
+ */
+ static Intent createAddToHomeIntent(Context context, String url, String title,
+ Bitmap touchIcon, Bitmap favicon) {
+ Intent i = new Intent(INSTALL_SHORTCUT);
+ Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ long urlHash = url.hashCode();
+ long uniqueId = (urlHash << 32) | shortcutIntent.hashCode();
+ shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID, Long.toString(uniqueId));
+ i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+ i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
+ i.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(context, touchIcon, favicon,
+ BookmarkIconType.ICON_HOME_SHORTCUT));
+
+ // Do not allow duplicate items
+ i.putExtra("duplicate", false);
+ return i;
+ }
+
+ private static Bitmap getIconBackground(Context context, BookmarkIconType type) {
+ if (type == BookmarkIconType.ICON_HOME_SHORTCUT) {
+ // Want to create a shortcut icon on the homescreen, so the icon
+ // background is the red bookmark.
+ return BitmapFactory.decodeResource(context.getResources(),
+ R.mipmap.ic_launcher_shortcut_browser_bookmark);
+ } else if (type == BookmarkIconType.ICON_INSTALLABLE_WEB_APP) {
+ // Use the web browser icon as the background for the icon for an installable
+ // web app.
+ return BitmapFactory.decodeResource(context.getResources(),
+ R.mipmap.ic_launcher_browser);
+ }
+ return null;
+ }
+
+ private static void drawTouchIconToCanvas(Bitmap touchIcon, Canvas canvas, Rect iconBounds) {
+ Rect src = new Rect(0, 0, touchIcon.getWidth(), touchIcon.getHeight());
+
+ // Paint used for scaling the bitmap and drawing the rounded rect.
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setFilterBitmap(true);
+ canvas.drawBitmap(touchIcon, src, iconBounds, paint);
+
+ // Construct a path from a round rect. This will allow drawing with
+ // an inverse fill so we can punch a hole using the round rect.
+ Path path = new Path();
+ path.setFillType(Path.FillType.INVERSE_WINDING);
+ RectF rect = new RectF(iconBounds);
+ rect.inset(1, 1);
+ path.addRoundRect(rect, 8f, 8f, Path.Direction.CW);
+
+ // Reuse the paint and clear the outside of the rectangle.
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ canvas.drawPath(path, paint);
+ }
+
+ private static void drawFaviconToCanvas(Bitmap favicon, Canvas canvas, Rect iconBounds,
+ float density) {
+ // Make a Paint for the white background rectangle and for
+ // filtering the favicon.
+ Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ p.setStyle(Paint.Style.FILL_AND_STROKE);
+ p.setColor(Color.WHITE);
+
+ // Create a rectangle that is slightly wider than the favicon
+ final float iconSize = 16 * density; // 16x16 favicon
+ final float padding = 2 * density; // white padding around icon
+ final float rectSize = iconSize + 2 * padding;
+ final float x = iconBounds.exactCenterX() - (rectSize / 2);
+ // Note: Subtract 2 dip from the y position since the box is
+ // slightly higher than center. Use padding since it is already
+ // 2 * density.
+ final float y = iconBounds.exactCenterY() - (rectSize / 2) - padding;
+ RectF r = new RectF(x, y, x + rectSize, y + rectSize);
+
+ // Draw a white rounded rectangle behind the favicon
+ canvas.drawRoundRect(r, 2, 2, p);
+
+ // Draw the favicon in the same rectangle as the rounded
+ // rectangle but inset by the padding
+ // (results in a 16x16 favicon).
+ r.inset(padding, padding);
+ canvas.drawBitmap(favicon, null, r, p);
+ }
+
+ /* package */ static Uri getBookmarksUri(Context context) {
+ Uri uri = BrowserContract.Bookmarks.CONTENT_URI;
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String accountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
+ String accountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
+ if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+ uri = uri.buildUpon()
+ .appendQueryParameter(BrowserContract.Bookmarks.PARAM_ACCOUNT_NAME, accountName)
+ .appendQueryParameter(BrowserContract.Bookmarks.PARAM_ACCOUNT_TYPE, accountType)
+ .build();
+ }
+ return uri;
+ }
+};
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
index 5ada9dcb8..383ae7fc5 100644
--- a/src/com/android/browser/Bookmarks.java
+++ b/src/com/android/browser/Bookmarks.java
@@ -20,16 +20,20 @@ import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.provider.Browser;
+import android.os.AsyncTask;
+import android.preference.PreferenceManager;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Combined;
+import android.provider.BrowserContract.Images;
import android.util.Log;
import android.webkit.WebIconDatabase;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
-import java.util.Date;
/**
* This class is purely to have a common place for adding/deleting bookmarks.
@@ -53,89 +57,38 @@ import java.util.Date;
* @param context Context of the calling Activity. This is used to make
* Toast confirming that the bookmark has been added. If the
* caller provides null, the Toast will not be shown.
- * @param cr The ContentResolver being used to add the bookmark to the db.
* @param url URL of the website to be bookmarked.
* @param name Provided name for the bookmark.
* @param thumbnail A thumbnail for the bookmark.
* @param retainIcon Whether to retain the page's icon in the icon database.
* This will usually be <code>true</code> except when bookmarks are
* added by a settings restore agent.
+ * @param parent ID of the parent folder.
*/
- /* package */ static void addBookmark(Context context,
- ContentResolver cr, String url, String name,
- Bitmap thumbnail, boolean retainIcon) {
+ /* package */ static void addBookmark(Context context, boolean showToast, String url,
+ String name, Bitmap thumbnail, boolean retainIcon, long parent) {
// Want to append to the beginning of the list
- long creationTime = new Date().getTime();
- ContentValues map = new ContentValues();
- Cursor cursor = null;
+ ContentValues values = new ContentValues();
try {
- cursor = Browser.getVisitedLike(cr, url);
- if (cursor.moveToFirst() && cursor.getInt(
- Browser.HISTORY_PROJECTION_BOOKMARK_INDEX) == 0) {
- // This means we have been to this site but not bookmarked
- // it, so convert the history item to a bookmark
- map.put(Browser.BookmarkColumns.CREATED, creationTime);
- map.put(Browser.BookmarkColumns.TITLE, name);
- map.put(Browser.BookmarkColumns.BOOKMARK, 1);
- map.put(Browser.BookmarkColumns.THUMBNAIL,
- bitmapToBytes(thumbnail));
- cr.update(Browser.BOOKMARKS_URI, map,
- "_id = " + cursor.getInt(0), null);
- } else {
- int count = cursor.getCount();
- boolean matchedTitle = false;
- for (int i = 0; i < count; i++) {
- // One or more bookmarks already exist for this site.
- // Check the names of each
- cursor.moveToPosition(i);
- if (cursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX)
- .equals(name)) {
- // The old bookmark has the same name.
- // Update its creation time.
- map.put(Browser.BookmarkColumns.CREATED,
- creationTime);
- cr.update(Browser.BOOKMARKS_URI, map,
- "_id = " + cursor.getInt(0), null);
- matchedTitle = true;
- break;
- }
- }
- if (!matchedTitle) {
- // Adding a bookmark for a site the user has visited,
- // or a new bookmark (with a different name) for a site
- // the user has visited
- map.put(Browser.BookmarkColumns.TITLE, name);
- map.put(Browser.BookmarkColumns.URL, url);
- map.put(Browser.BookmarkColumns.CREATED, creationTime);
- map.put(Browser.BookmarkColumns.BOOKMARK, 1);
- map.put(Browser.BookmarkColumns.DATE, 0);
- map.put(Browser.BookmarkColumns.THUMBNAIL,
- bitmapToBytes(thumbnail));
- int visits = 0;
- if (count > 0) {
- // The user has already bookmarked, and possibly
- // visited this site. However, they are creating
- // a new bookmark with the same url but a different
- // name. The new bookmark should have the same
- // number of visits as the already created bookmark.
- visits = cursor.getInt(
- Browser.HISTORY_PROJECTION_VISITS_INDEX);
- }
- // Bookmark starts with 3 extra visits so that it will
- // bubble up in the most visited and goto search box
- map.put(Browser.BookmarkColumns.VISITS, visits + 3);
- cr.insert(Browser.BOOKMARKS_URI, map);
- }
- }
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String accountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
+ String accountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
+ values.put(BrowserContract.Bookmarks.ACCOUNT_TYPE, accountType);
+ values.put(BrowserContract.Bookmarks.ACCOUNT_NAME, accountName);
+ values.put(BrowserContract.Bookmarks.TITLE, name);
+ values.put(BrowserContract.Bookmarks.URL, url);
+ values.put(BrowserContract.Bookmarks.IS_FOLDER, 0);
+ values.put(BrowserContract.Bookmarks.THUMBNAIL,
+ bitmapToBytes(thumbnail));
+ values.put(BrowserContract.Bookmarks.PARENT, parent);
+ context.getContentResolver().insert(BrowserContract.Bookmarks.CONTENT_URI, values);
} catch (IllegalStateException e) {
Log.e(LOGTAG, "addBookmark", e);
- } finally {
- if (cursor != null) cursor.close();
}
if (retainIcon) {
WebIconDatabase.getInstance().retainIconForPageUrl(url);
}
- if (context != null) {
+ if (showToast) {
Toast.makeText(context, R.string.added_to_bookmarks,
Toast.LENGTH_LONG).show();
}
@@ -155,40 +108,26 @@ import java.util.Date;
ContentResolver cr, String url, String title) {
Cursor cursor = null;
try {
- cursor = cr.query(
- Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- "url = ? AND title = ?",
+ cursor = cr.query(BrowserContract.Bookmarks.CONTENT_URI,
+ new String[] { BrowserContract.Bookmarks._ID },
+ BrowserContract.Bookmarks.URL + " = ? AND " +
+ BrowserContract.Bookmarks.TITLE + " = ?",
new String[] { url, title },
null);
- boolean first = cursor.moveToFirst();
+
// Should be in the database no matter what
- if (!first) {
+ if (!cursor.moveToFirst()) {
throw new AssertionError("URL is not in the database! " + url
+ " " + title);
}
+
// Remove from bookmarks
WebIconDatabase.getInstance().releaseIconForPageUrl(url);
- Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
- cursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
- int numVisits = cursor.getInt(
- Browser.HISTORY_PROJECTION_VISITS_INDEX);
- if (0 == numVisits) {
- cr.delete(uri, null, null);
- } else {
- // It is no longer a bookmark, but it is still a visited
- // site.
- ContentValues values = new ContentValues();
- values.put(Browser.BookmarkColumns.BOOKMARK, 0);
- try {
- cr.update(uri, values, null, null);
- } catch (IllegalStateException e) {
- Log.e("removeFromBookmarks", "no database!");
- }
- }
+ Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
+ cursor.getLong(0));
+ cr.delete(uri, null, null);
if (context != null) {
- Toast.makeText(context, R.string.removed_from_bookmarks,
- Toast.LENGTH_LONG).show();
+ Toast.makeText(context, R.string.removed_from_bookmarks, Toast.LENGTH_LONG).show();
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "removeFromBookmarks", e);
@@ -219,4 +158,86 @@ import java.util.Date;
}
return false;
}
+
+ static final String QUERY_BOOKMARKS_WHERE =
+ Combined.URL + " == ? OR " +
+ Combined.URL + " == ? OR " +
+ Combined.URL + " LIKE ? || '%' OR " +
+ Combined.URL + " LIKE ? || '%'";
+
+ /* package */ static Cursor queryCombinedForUrl(ContentResolver cr,
+ String originalUrl, String url) {
+ if (cr == null || url == null) {
+ return null;
+ }
+
+ // If originalUrl is null, just set it to url.
+ if (originalUrl == null) {
+ originalUrl = url;
+ }
+
+ // Look for both the original url and the actual url. This takes in to
+ // account redirects.
+ String originalUrlNoQuery = removeQuery(originalUrl);
+ String urlNoQuery = removeQuery(url);
+ originalUrl = originalUrlNoQuery + '?';
+ url = urlNoQuery + '?';
+
+ // Use NoQuery to search for the base url (i.e. if the url is
+ // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com)
+ // Use url to match the base url with other queries (i.e. if the url is
+ // http://www.google.com/m, search for
+ // http://www.google.com/m?some_query)
+ final String[] selArgs = new String[] { originalUrlNoQuery, urlNoQuery, originalUrl, url };
+ final String[] projection = new String[] { Combined.URL };
+ return cr.query(Combined.CONTENT_URI, projection, QUERY_BOOKMARKS_WHERE, selArgs, null);
+ }
+
+ // Strip the query from the given url.
+ static String removeQuery(String url) {
+ if (url == null) {
+ return null;
+ }
+ int query = url.indexOf('?');
+ String noQuery = url;
+ if (query != -1) {
+ noQuery = url.substring(0, query);
+ }
+ return noQuery;
+ }
+
+ /**
+ * Update the bookmark's favicon. This is a convenience method for updating
+ * a bookmark favicon for the originalUrl and url of the passed in WebView.
+ * @param cr The ContentResolver to use.
+ * @param originalUrl The original url before any redirects.
+ * @param url The current url.
+ * @param favicon The favicon bitmap to write to the db.
+ */
+ /* package */ static void updateFavicon(final ContentResolver cr,
+ final String originalUrl, final String url, final Bitmap favicon) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ Cursor cursor = queryCombinedForUrl(cr, originalUrl, url);
+ try {
+ if (cursor.moveToFirst()) {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+ ContentValues values = new ContentValues();
+ values.put(Images.FAVICON, os.toByteArray());
+ values.put(Images.URL, cursor.getString(0));
+
+ do {
+ cr.update(Images.CONTENT_URI, values, null, null);
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return null;
+ }
+ }.execute();
+ }
}
diff --git a/src/com/android/browser/BookmarksLoader.java b/src/com/android/browser/BookmarksLoader.java
new file mode 100644
index 000000000..770ca600b
--- /dev/null
+++ b/src/com/android/browser/BookmarksLoader.java
@@ -0,0 +1,72 @@
+/*
+ * 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.content.CursorLoader;
+import android.net.Uri;
+import android.provider.BrowserContract.Bookmarks;
+import android.text.TextUtils;
+
+public class BookmarksLoader extends CursorLoader {
+ public static final String ARG_ACCOUNT_TYPE = "acct_type";
+ public static final String ARG_ACCOUNT_NAME = "acct_name";
+
+ public static final int COLUMN_INDEX_ID = 0;
+ public static final int COLUMN_INDEX_URL = 1;
+ public static final int COLUMN_INDEX_TITLE = 2;
+ public static final int COLUMN_INDEX_FAVICON = 3;
+ public static final int COLUMN_INDEX_THUMBNAIL = 4;
+ public static final int COLUMN_INDEX_TOUCH_ICON = 5;
+ public static final int COLUMN_INDEX_IS_FOLDER = 6;
+ public static final int COLUMN_INDEX_PARENT = 8;
+
+ public static final String[] PROJECTION = new String[] {
+ Bookmarks._ID, // 0
+ Bookmarks.URL, // 1
+ Bookmarks.TITLE, // 2
+ Bookmarks.FAVICON, // 3
+ Bookmarks.THUMBNAIL, // 4
+ Bookmarks.TOUCH_ICON, // 5
+ Bookmarks.IS_FOLDER, // 6
+ Bookmarks.POSITION, // 7
+ Bookmarks.PARENT, // 8
+ };
+
+ private String mAccountType;
+ private String mAccountName;
+
+ public BookmarksLoader(Context context, String accountType, String accountName) {
+ super(context, addAccount(Bookmarks.CONTENT_URI_DEFAULT_FOLDER, accountType, accountName),
+ PROJECTION, null, null, null);
+ mAccountType = accountType;
+ mAccountName = accountName;
+ }
+
+ @Override
+ public void setUri(Uri uri) {
+ super.setUri(addAccount(uri, mAccountType, mAccountName));
+ }
+
+ private static Uri addAccount(Uri uri, String accountType, String accountName) {
+ if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+ return uri.buildUpon().appendQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE, accountType).
+ appendQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME, accountName).build();
+ }
+ return uri;
+ }
+}
diff --git a/src/com/android/browser/BreadCrumbView.java b/src/com/android/browser/BreadCrumbView.java
new file mode 100644
index 000000000..4939b48e3
--- /dev/null
+++ b/src/com/android/browser/BreadCrumbView.java
@@ -0,0 +1,299 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple bread crumb view
+ * Use setController to receive callbacks from user interactions
+ * Use pushView, popView, clear, and getTopData to change/access the view stack
+ */
+public class BreadCrumbView extends LinearLayout implements OnClickListener {
+
+ interface Controller {
+ public void onTop(int level, Object data);
+ }
+
+ private ImageButton mBackButton;
+ private Controller mController;
+ private List<Crumb> mCrumbs;
+ private boolean mUseBackButton;
+ private Drawable mSeparatorDrawable;
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public BreadCrumbView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public BreadCrumbView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /**
+ * @param context
+ */
+ public BreadCrumbView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ mUseBackButton = false;
+ mCrumbs = new ArrayList<Crumb>();
+ mSeparatorDrawable = ctx.getResources().getDrawable(
+ R.drawable.crumb_divider);
+ }
+
+ public void setUseBackButton(boolean useflag) {
+ mUseBackButton = useflag;
+ if (mUseBackButton && (mBackButton == null)) {
+ addBackButton();
+ } else if (!mUseBackButton && (mBackButton != null)) {
+ removeView(mBackButton);
+ mBackButton = null;
+ }
+ }
+
+ public void setController(Controller ctl) {
+ mController = ctl;
+ }
+
+ public Object getTopData() {
+ Crumb c = getTopCrumb();
+ if (c != null) {
+ return c.data;
+ }
+ return null;
+ }
+
+ public int size() {
+ return mCrumbs.size();
+ }
+
+ public void clear() {
+ while (mCrumbs.size() > 1) {
+ pop(false);
+ }
+ pop(true);
+ }
+
+ public void notifyController() {
+ if (mController != null) {
+ if (mCrumbs.size() > 0) {
+ mController.onTop(mCrumbs.size(), getTopCrumb().data);
+ } else {
+ mController.onTop(0, null);
+ }
+ }
+ }
+
+ public void pushView(String name, Object data) {
+ pushView(name, true, data);
+ }
+
+ public void pushView(String name, boolean canGoBack, Object data) {
+ Crumb crumb = new Crumb(name, canGoBack, data);
+ pushCrumb(crumb);
+ }
+
+ public void pushView(View view, Object data) {
+ Crumb crumb = new Crumb(view, true, data);
+ pushCrumb(crumb);
+ }
+
+ public void popView() {
+ pop(true);
+ }
+
+ private void addBackButton() {
+ mBackButton = new ImageButton(mContext);
+ mBackButton.setImageResource(R.drawable.ic_back_normal);
+ mBackButton.setBackgroundResource(R.drawable.browserbarbutton);
+ mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT));
+ mBackButton.setOnClickListener(this);
+ mBackButton.setVisibility(View.INVISIBLE);
+ addView(mBackButton, 0);
+ }
+
+ private void pushCrumb(Crumb crumb) {
+ if (!mUseBackButton || (mCrumbs.size() > 0)) {
+ addSeparator();
+ }
+ mCrumbs.add(crumb);
+ addView(crumb.crumbView);
+ if (mUseBackButton) {
+ mBackButton.setVisibility(crumb.canGoBack ? View.VISIBLE : View.INVISIBLE);
+ }
+ crumb.crumbView.setOnClickListener(this);
+ }
+
+ private void addSeparator() {
+ ImageView sep = new ImageView(mContext);
+ sep.setImageDrawable(mSeparatorDrawable);
+ sep.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT));
+ addView(sep);
+ }
+
+ private void pop(boolean notify) {
+ int n = mCrumbs.size();
+ if (n > 0) {
+ removeLastView();
+ if (!mUseBackButton || (n > 1)) {
+ // remove separator
+ removeLastView();
+ }
+ mCrumbs.remove(n - 1);
+ if (mUseBackButton) {
+ Crumb top = getTopCrumb();
+ if (top != null && top.canGoBack) {
+ mBackButton.setVisibility(View.VISIBLE);
+ } else {
+ mBackButton.setVisibility(View.INVISIBLE);
+ }
+ }
+ if (notify) {
+ notifyController();
+ }
+ }
+ }
+
+ private void removeLastView() {
+ int ix = getChildCount();
+ if (ix > 0) {
+ removeViewAt(ix-1);
+ }
+ }
+
+ private Crumb getTopCrumb() {
+ Crumb crumb = null;
+ if (mCrumbs.size() > 0) {
+ crumb = mCrumbs.get(mCrumbs.size() - 1);
+ }
+ return crumb;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mBackButton == v) {
+ popView();
+ notifyController();
+ } else {
+ // pop until view matches crumb view
+ while (v != getTopCrumb().crumbView) {
+ pop(false);
+ }
+ notifyController();
+ }
+ }
+ @Override
+ public int getBaseline() {
+ int ix = getChildCount();
+ if (ix > 0) {
+ // If there is at least one crumb, the baseline will be its
+ // baseline.
+ return getChildAt(ix-1).getBaseline();
+ }
+ return super.getBaseline();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int height = mSeparatorDrawable.getIntrinsicHeight();
+ if (mMeasuredHeight < height) {
+ // This should only be an issue if there are currently no separators
+ // showing; i.e. if there is one crumb and no back button.
+ int mode = View.MeasureSpec.getMode(heightMeasureSpec);
+ switch(mode) {
+ case View.MeasureSpec.AT_MOST:
+ if (View.MeasureSpec.getSize(heightMeasureSpec) < height) {
+ return;
+ }
+ break;
+ case View.MeasureSpec.EXACTLY:
+ return;
+ default:
+ break;
+ }
+ setMeasuredDimension(mMeasuredWidth, height);
+ }
+ }
+
+ class Crumb {
+
+ public View crumbView;
+ public boolean canGoBack;
+ public Object data;
+
+ public Crumb(String title, boolean backEnabled, Object tag) {
+ init(makeCrumbView(title), backEnabled, tag);
+ }
+
+ public Crumb(View view, boolean backEnabled, Object tag) {
+ init(view, backEnabled, tag);
+ }
+
+ private void init(View view, boolean back, Object tag) {
+ canGoBack = back;
+ crumbView = view;
+ data = tag;
+ }
+
+ private TextView makeCrumbView(String name) {
+ TextView tv = new TextView(mContext);
+ tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
+ tv.setPadding(16, 0, 16, 0);
+ tv.setGravity(Gravity.CENTER_VERTICAL);
+ tv.setText(name);
+ tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT));
+ tv.setMaxWidth(mContext.getResources().getInteger(
+ R.integer.max_width_crumb));
+ tv.setMaxLines(1);
+ tv.setEllipsize(TextUtils.TruncateAt.END);
+ return tv;
+ }
+
+ }
+
+}
diff --git a/src/com/android/browser/Browser.java b/src/com/android/browser/Browser.java
index 7822ec88f..97b99672a 100644
--- a/src/com/android/browser/Browser.java
+++ b/src/com/android/browser/Browser.java
@@ -57,7 +57,7 @@ public class Browser extends Application {
CookieSyncManager.createInstance(this);
// remove all expired cookies
CookieManager.getInstance().removeExpiredCookie();
- BrowserSettings.getInstance().loadFromDb(this);
+ BrowserSettings.getInstance().asyncLoadFromDb(this);
}
static Intent createBrowserViewIntent() {
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 0a3fec9a4..984454745 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -17,154 +17,33 @@
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;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentProvider;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Picture;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.Uri;
-import android.net.WebAddress;
-import android.net.http.SslCertificate;
-import android.net.http.SslError;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Debug;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.provider.Browser;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Intents.Insert;
-import android.provider.Downloads;
-import android.provider.MediaStore;
-import android.speech.RecognizerResultsIntent;
-import android.text.IClipboard;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
import android.util.Log;
-import android.util.Patterns;
+import android.view.ActionMode;
import android.view.ContextMenu;
-import android.view.Gravity;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.Menu;
-import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.webkit.CookieManager;
-import android.webkit.CookieSyncManager;
-import android.webkit.DownloadListener;
-import android.webkit.HttpAuthHandler;
-import android.webkit.PluginManager;
-import android.webkit.SslErrorHandler;
-import android.webkit.URLUtil;
-import android.webkit.ValueCallback;
-import android.webkit.WebChromeClient;
-import android.webkit.WebHistoryItem;
-import android.webkit.WebIconDatabase;
-import android.webkit.WebView;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerFuture;
-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;
+import android.view.accessibility.AccessibilityManager;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.text.ParseException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+public class BrowserActivity extends Activity {
-public class BrowserActivity extends Activity
- implements View.OnCreateContextMenuListener, DownloadListener {
-
- /* Define some aliases to make these debugging flags easier to refer to.
- * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
- */
- private final static boolean DEBUG = com.android.browser.Browser.DEBUG;
- private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
- private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
+ private final static String LOGTAG = "browser";
- private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
- @Override
- public Void doInBackground(File... files) {
- if (files != null) {
- for (File f : files) {
- if (!f.delete()) {
- Log.e(LOGTAG, f.getPath() + " was not deleted");
- }
- }
- }
- return null;
- }
- }
+ private final static boolean LOGV_ENABLED =
+ com.android.browser.Browser.LOGV_ENABLED;
- /**
- * This layout holds everything you see below the status bar, including the
- * error console, the custom view container, and the webviews.
- */
- private FrameLayout mBrowserFrameLayout;
+ private Controller mController;
+ private UI mUi;
@Override
public void onCreate(Bundle icicle) {
@@ -172,8 +51,21 @@ public class BrowserActivity extends Activity
Log.v(LOGTAG, this + " onStart");
}
super.onCreate(icicle);
- // test the browser in OpenGL
- // requestWindowFeature(Window.FEATURE_OPENGL);
+
+ // We load the first set of BrowserSettings from the db asynchronously
+ // but if it has not completed at this point, we have no choice but
+ // to block waiting for them to finish loading. :(
+ BrowserSettings.getInstance().waitForLoadFromDbToComplete();
+
+ // render the browser in OpenGL
+ if (BrowserSettings.getInstance().isHardwareAccelerated()) {
+ // Set the flag in the activity's window
+ this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ } else {
+ // Clear the flag in the activity's window
+ this.getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ }
// enable this to test the browser in 32bit
if (false) {
@@ -181,543 +73,41 @@ public class BrowserActivity extends Activity
BitmapFactory.setDefaultConfig(Bitmap.Config.ARGB_8888);
}
- setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
-
- 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())) {
+ if (IntentHandler.handleWebSearchIntent(this, null, getIntent())) {
finish();
return;
}
- mSecLockIcon = Resources.getSystem().getDrawable(
- android.R.drawable.ic_secure);
- mMixLockIcon = Resources.getSystem().getDrawable(
- android.R.drawable.ic_partial_secure);
-
- FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
- .findViewById(com.android.internal.R.id.content);
- mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this)
- .inflate(R.layout.custom_screen, null);
- mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(
- R.id.main_content);
- mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout
- .findViewById(R.id.error_console);
- mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
- .findViewById(R.id.fullscreen_custom_content);
- frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
- mTitleBar = new TitleBar(this);
- // mTitleBar will be always shown in the fully loaded mode
- mTitleBar.setProgress(100);
- mFakeTitleBar = new TitleBar(this);
-
- // Create the tab control and our initial tab
- mTabControl = new TabControl(this);
-
- // Open the icon database and retain all the bookmark urls for favicons
- retainIconsOnStartup();
-
- mSettings.setTabControl(mTabControl);
-
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
-
- // Find out if the network is currently up.
- ConnectivityManager cm = (ConnectivityManager) getSystemService(
- Context.CONNECTIVITY_SERVICE);
- NetworkInfo info = cm.getActiveNetworkInfo();
- if (info != null) {
- mIsNetworkUp = info.isAvailable();
- }
-
- /* enables registration for changes in network status from
- http stack */
- mNetworkStateChangedFilter = new IntentFilter();
- mNetworkStateChangedFilter.addAction(
- ConnectivityManager.CONNECTIVITY_ACTION);
- mNetworkStateIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(
- ConnectivityManager.CONNECTIVITY_ACTION)) {
-
- NetworkInfo info = intent.getParcelableExtra(
- ConnectivityManager.EXTRA_NETWORK_INFO);
- String typeName = info.getTypeName();
- String subtypeName = info.getSubtypeName();
- sendNetworkType(typeName.toLowerCase(),
- (subtypeName != null ? subtypeName.toLowerCase() : ""));
-
- onNetworkToggle(info.isAvailable());
- }
- }
- };
-
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- mPackageInstallationReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final String packageName = intent.getData()
- .getSchemeSpecificPart();
- final boolean replacing = intent.getBooleanExtra(
- Intent.EXTRA_REPLACING, false);
- if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
- // if it is replacing, refreshPlugins() when adding
- return;
- }
-
- if (sGoogleApps.contains(packageName)) {
- BrowserActivity.this.packageChanged(packageName,
- Intent.ACTION_PACKAGE_ADDED.equals(action));
- }
-
- PackageManager pm = BrowserActivity.this.getPackageManager();
- PackageInfo pkgInfo = null;
- try {
- pkgInfo = pm.getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS);
- } catch (PackageManager.NameNotFoundException e) {
- return;
- }
- if (pkgInfo != null) {
- String permissions[] = pkgInfo.requestedPermissions;
- if (permissions == null) {
- return;
- }
- boolean permissionOk = false;
- for (String permit : permissions) {
- if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
- permissionOk = true;
- break;
- }
- }
- if (permissionOk) {
- PluginManager.getInstance(BrowserActivity.this)
- .refreshPlugins(
- Intent.ACTION_PACKAGE_ADDED
- .equals(action));
- }
- }
- }
- };
- registerReceiver(mPackageInstallationReceiver, filter);
-
- if (!mTabControl.restoreState(icicle)) {
- // clear up the thumbnail directory if we can't restore the state as
- // none of the files in the directory are referenced any more.
- new ClearThumbnails().execute(
- mTabControl.getThumbnailDir().listFiles());
- // there is no quit on Android. But if we can't restore the state,
- // we can treat it as a new Browser, remove the old session cookies.
- CookieManager.getInstance().removeSessionCookie();
- final Intent intent = getIntent();
- final Bundle extra = intent.getExtras();
- // Create an initial tab.
- // If the intent is ACTION_VIEW and data is not null, the Browser is
- // invoked to view the content by another application. In this case,
- // the tab will be close when exit.
- UrlData urlData = getUrlDataFromIntent(intent);
-
- String action = intent.getAction();
- final Tab t = mTabControl.createNewTab(
- (Intent.ACTION_VIEW.equals(action) &&
- intent.getData() != null)
- || RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
- .equals(action),
- intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
- mTabControl.setCurrentTab(t);
- attachTabToContentView(t);
- WebView webView = t.getWebView();
- if (extra != null) {
- int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
- if (scale > 0 && scale <= 1000) {
- webView.setInitialScale(scale);
- }
- }
-
- if (urlData.isEmpty()) {
- loadUrl(webView, mSettings.getHomePage());
- } else {
- loadUrlDataIn(t, urlData);
- }
+ if (((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
+ .isEnabled()) {
+ setDefaultKeyMode(DEFAULT_KEYS_DISABLE);
} else {
- // TabControl.restoreState() will create a new tab even if
- // restoring the state fails.
- attachTabToContentView(mTabControl.getCurrentTab());
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
}
- // 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) {
- mTabControl.getCurrentWebView().setJsFlags(jsFlags);
- }
- // Work out which packages are installed on the system.
- getInstalledPackages();
+ mController = new Controller(this);
+ mUi = new BaseUi(this, mController);
+ mController.setUi(mUi);
+ mController.setWebViewFactory((BaseUi) mUi);
- // Start watching the default geolocation permissions
- mSystemAllowGeolocationOrigins
- = new SystemAllowGeolocationOrigins(getApplicationContext());
- mSystemAllowGeolocationOrigins.start();
+ mController.start(icicle, getIntent());
}
- /**
- * Feed the previously stored results strings to the BrowserProvider so that
- * the SearchDialog will show them instead of the standard searches.
- * @param result String to show on the editable line of the SearchDialog.
- */
- /* package */ void showVoiceSearchResults(String result) {
- ContentProviderClient client = mResolver.acquireContentProviderClient(
- Browser.BOOKMARKS_URI);
- ContentProvider prov = client.getLocalContentProvider();
- BrowserProvider bp = (BrowserProvider) prov;
- bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults());
- client.release();
+ Controller getController() {
+ return mController;
+ }
- Bundle bundle = createGoogleSearchSourceBundle(
- GOOGLE_SEARCH_SOURCE_SEARCHKEY);
- bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true);
- startSearch(result, false, bundle, false);
+ // TODO: this is here for the test classes
+ // remove once tests are fixed
+ TabControl getTabControl() {
+ return mController.getTabControl();
}
@Override
protected void onNewIntent(Intent intent) {
- Tab current = mTabControl.getCurrentTab();
- // When a tab is closed on exit, the current tab index is set to -1.
- // Reset before proceed as Browser requires the current tab to be set.
- if (current == null) {
- // Try to reset the tab in case the index was incorrect.
- current = mTabControl.getTab(0);
- if (current == null) {
- // No tabs at all so just ignore this intent.
- return;
- }
- mTabControl.setCurrentTab(current);
- attachTabToContentView(current);
- resetTitleAndIcon(current.getWebView());
- }
- final String action = intent.getAction();
- final int flags = intent.getFlags();
- if (Intent.ACTION_MAIN.equals(action) ||
- (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
- // just resume the browser
- return;
- }
- // In case the SearchDialog is open.
- ((SearchManager) getSystemService(Context.SEARCH_SERVICE))
- .stopSearch();
- boolean activateVoiceSearch = RecognizerResultsIntent
- .ACTION_VOICE_SEARCH_RESULTS.equals(action);
- if (Intent.ACTION_VIEW.equals(action)
- || Intent.ACTION_SEARCH.equals(action)
- || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
- || Intent.ACTION_WEB_SEARCH.equals(action)
- || activateVoiceSearch) {
- if (current.isInVoiceSearchMode()) {
- String title = current.getVoiceDisplayTitle();
- if (title != null && title.equals(intent.getStringExtra(
- SearchManager.QUERY))) {
- // The user submitted the same search as the last voice
- // search, so do nothing.
- return;
- }
- if (Intent.ACTION_SEARCH.equals(action)
- && current.voiceSearchSourceIsGoogle()) {
- Intent logIntent = new Intent(
- LoggingEvents.ACTION_LOG_EVENT);
- logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
- LoggingEvents.VoiceSearch.QUERY_UPDATED);
- logIntent.putExtra(
- LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE,
- intent.getDataString());
- sendBroadcast(logIntent);
- // Note, onPageStarted will revert the voice title bar
- // When http://b/issue?id=2379215 is fixed, we should update
- // the title bar here.
- }
- }
- // If this was a search request (e.g. search query directly typed into the address bar),
- // pass it on to the default web search provider.
- if (handleWebSearchIntent(intent)) {
- return;
- }
-
- UrlData urlData = getUrlDataFromIntent(intent);
- if (urlData.isEmpty()) {
- urlData = new UrlData(mSettings.getHomePage());
- }
-
- final String appId = intent
- .getStringExtra(Browser.EXTRA_APPLICATION_ID);
- if ((Intent.ACTION_VIEW.equals(action)
- // If a voice search has no appId, it means that it came
- // from the browser. In that case, reuse the current tab.
- || (activateVoiceSearch && appId != null))
- && !getPackageName().equals(appId)
- && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
- Tab appTab = mTabControl.getTabFromId(appId);
- if (appTab != null) {
- Log.i(LOGTAG, "Reusing tab for " + appId);
- // Dismiss the subwindow if applicable.
- dismissSubWindow(appTab);
- // Since we might kill the WebView, remove it from the
- // content view first.
- removeTabFromContentView(appTab);
- // Recreate the main WebView after destroying the old one.
- // If the WebView has the same original url and is on that
- // page, it can be reused.
- boolean needsLoad =
- mTabControl.recreateWebView(appTab, urlData);
-
- if (current != appTab) {
- switchToTab(mTabControl.getTabIndex(appTab));
- if (needsLoad) {
- loadUrlDataIn(appTab, urlData);
- }
- } else {
- // If the tab was the current tab, we have to attach
- // it to the view system again.
- attachTabToContentView(appTab);
- if (needsLoad) {
- loadUrlDataIn(appTab, urlData);
- }
- }
- return;
- } else {
- // No matching application tab, try to find a regular tab
- // with a matching url.
- appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
- if (appTab != null) {
- if (current != appTab) {
- switchToTab(mTabControl.getTabIndex(appTab));
- }
- // Otherwise, we are already viewing the correct tab.
- } else {
- // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
- // will be opened in a new tab unless we have reached
- // MAX_TABS. Then the url will be opened in the current
- // tab. If a new tab is created, it will have "true" for
- // exit on close.
- openTabAndShow(urlData, true, appId);
- }
- }
- } else {
- if (!urlData.isEmpty()
- && urlData.mUrl.startsWith("about:debug")) {
- if ("about:debug.dom".equals(urlData.mUrl)) {
- current.getWebView().dumpDomTree(false);
- } else if ("about:debug.dom.file".equals(urlData.mUrl)) {
- current.getWebView().dumpDomTree(true);
- } else if ("about:debug.render".equals(urlData.mUrl)) {
- current.getWebView().dumpRenderTree(false);
- } else if ("about:debug.render.file".equals(urlData.mUrl)) {
- current.getWebView().dumpRenderTree(true);
- } else if ("about:debug.display".equals(urlData.mUrl)) {
- current.getWebView().dumpDisplayTree();
- } else if (urlData.mUrl.startsWith("about:debug.drag")) {
- int index = urlData.mUrl.codePointAt(16) - '0';
- if (index <= 0 || index > 9) {
- current.getWebView().setDragTracker(null);
- } else {
- current.getWebView().setDragTracker(new MeshTracker(index));
- }
- } else {
- mSettings.toggleDebugSettings();
- }
- return;
- }
- // Get rid of the subwindow if it exists
- dismissSubWindow(current);
- // If the current Tab is being used as an application tab,
- // remove the association, since the new Intent means that it is
- // no longer associated with that application.
- current.setAppId(null);
- loadUrlDataIn(current, urlData);
- }
- }
- }
-
- /**
- * 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.
- * @return true if the intent was handled and web search activity was launched, false if not.
- */
- private boolean handleWebSearchIntent(Intent intent) {
- if (intent == null) return false;
-
- String url = null;
- final String action = intent.getAction();
- if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS.equals(
- action)) {
- return false;
- }
- if (Intent.ACTION_VIEW.equals(action)) {
- Uri data = intent.getData();
- if (data != null) url = data.toString();
- } else if (Intent.ACTION_SEARCH.equals(action)
- || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
- || Intent.ACTION_WEB_SEARCH.equals(action)) {
- url = intent.getStringExtra(SearchManager.QUERY);
- }
- return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA),
- intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
- }
-
- /**
- * Launches the default web search activity with the query parameters if the given url string
- * was identified as plain search terms and not URL/shortcut.
- * @return true if the request was handled and web search activity was launched, false if not.
- */
- private boolean handleWebSearchRequest(String inUrl, Bundle appData, String extraData) {
- if (inUrl == null) return false;
-
- // In general, we shouldn't modify URL from Intent.
- // But currently, we get the user-typed URL from search box as well.
- String url = fixUrl(inUrl).trim();
-
- // 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()) {
- return false;
- }
-
- final ContentResolver cr = mResolver;
- final String newUrl = url;
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... unused) {
- Browser.updateVisitedHistory(cr, newUrl, false);
- Browser.addSearchUrl(cr, newUrl);
- return null;
- }
- }.execute();
-
- SearchEngine searchEngine = mSettings.getSearchEngine();
- if (searchEngine == null) return false;
- searchEngine.startSearch(this, url, appData, extraData);
-
- return true;
- }
-
- private UrlData getUrlDataFromIntent(Intent intent) {
- String url = "";
- Map<String, String> headers = null;
- if (intent != null) {
- final String action = intent.getAction();
- if (Intent.ACTION_VIEW.equals(action)) {
- url = smartUrlFilter(intent.getData());
- if (url != null && url.startsWith("content:")) {
- /* Append mimetype so webview knows how to display */
- String mimeType = intent.resolveType(getContentResolver());
- if (mimeType != null) {
- url += "?" + mimeType;
- }
- }
- if (url != null && url.startsWith("http")) {
- final Bundle pairs = intent
- .getBundleExtra(Browser.EXTRA_HEADERS);
- if (pairs != null && !pairs.isEmpty()) {
- Iterator<String> iter = pairs.keySet().iterator();
- headers = new HashMap<String, String>();
- while (iter.hasNext()) {
- String key = iter.next();
- headers.put(key, pairs.getString(key));
- }
- }
- }
- } else if (Intent.ACTION_SEARCH.equals(action)
- || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
- || Intent.ACTION_WEB_SEARCH.equals(action)) {
- url = intent.getStringExtra(SearchManager.QUERY);
- if (url != null) {
- mLastEnteredUrl = url;
- // In general, we shouldn't modify URL from Intent.
- // But currently, we get the user-typed URL from search box as well.
- url = fixUrl(url);
- url = smartUrlFilter(url);
- final ContentResolver cr = mResolver;
- final String newUrl = url;
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... unused) {
- Browser.updateVisitedHistory(cr, newUrl, false);
- return null;
- }
- }.execute();
- String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
- if (url.contains(searchSource)) {
- String source = null;
- final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
- if (appData != null) {
- source = appData.getString(Search.SOURCE);
- }
- if (TextUtils.isEmpty(source)) {
- source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
- }
- url = url.replace(searchSource, "&source=android-"+source+"&");
- }
- }
- }
- }
- return new UrlData(url, headers, intent);
- }
- /* package */ void showVoiceTitleBar(String title) {
- mTitleBar.setInVoiceMode(true);
- mFakeTitleBar.setInVoiceMode(true);
-
- mTitleBar.setDisplayTitle(title);
- mFakeTitleBar.setDisplayTitle(title);
- }
- /* package */ void revertVoiceTitleBar() {
- mTitleBar.setInVoiceMode(false);
- mFakeTitleBar.setInVoiceMode(false);
-
- mTitleBar.setDisplayTitle(mUrl);
- mFakeTitleBar.setDisplayTitle(mUrl);
- }
- /* package */ static String fixUrl(String inUrl) {
- // FIXME: Converting the url to lower case
- // duplicates functionality in smartUrlFilter().
- // However, changing all current callers of fixUrl to
- // call smartUrlFilter in addition may have unwanted
- // consequences, and is deferred for now.
- int colon = inUrl.indexOf(':');
- boolean allLower = true;
- for (int index = 0; index < colon; index++) {
- char ch = inUrl.charAt(index);
- if (!Character.isLetter(ch)) {
- break;
- }
- allLower &= Character.isLowerCase(ch);
- if (index == colon - 1 && !allLower) {
- inUrl = inUrl.substring(0, colon).toLowerCase()
- + inUrl.substring(colon);
- }
- }
- if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
- return inUrl;
- if (inUrl.startsWith("http:") ||
- inUrl.startsWith("https:")) {
- if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
- inUrl = inUrl.replaceFirst("/", "//");
- } else inUrl = inUrl.replaceFirst(":", "://");
- }
- return inUrl;
+ mController.handleNewIntent(intent);
}
@Override
@@ -726,170 +116,26 @@ public class BrowserActivity extends Activity
if (LOGV_ENABLED) {
Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
}
-
- if (!mActivityInPause) {
- Log.e(LOGTAG, "BrowserActivity is already resumed.");
- return;
- }
-
- mTabControl.resumeCurrentTab();
- mActivityInPause = false;
- resumeWebViewTimers();
-
- if (mWakeLock.isHeld()) {
- mHandler.removeMessages(RELEASE_WAKELOCK);
- mWakeLock.release();
- }
-
- registerReceiver(mNetworkStateIntentReceiver,
- mNetworkStateChangedFilter);
- WebView.enablePlatformNotifications();
+ mController.onResume();
}
- /**
- * Since the actual title bar is embedded in the WebView, and removing it
- * would change its appearance, use a different TitleBar to show overlayed
- * at the top of the screen, when the menu is open or the page is loading.
- */
- private TitleBar mFakeTitleBar;
-
- /**
- * Keeps track of whether the options menu is open. This is important in
- * determining whether to show or hide the title bar overlay.
- */
- private boolean mOptionsMenuOpen;
-
- /**
- * Only meaningful when mOptionsMenuOpen is true. This variable keeps track
- * of whether the configuration has changed. The first onMenuOpened call
- * after a configuration change is simply a reopening of the same menu
- * (i.e. mIconView did not change).
- */
- private boolean mConfigChanged;
-
- /**
- * Whether or not the options menu is in its smaller, icon menu form. When
- * true, we want the title bar overlay to be up. When false, we do not.
- * Only meaningful if mOptionsMenuOpen is true.
- */
- private boolean mIconView;
-
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
if (Window.FEATURE_OPTIONS_PANEL == featureId) {
- if (mOptionsMenuOpen) {
- if (mConfigChanged) {
- // We do not need to make any changes to the state of the
- // title bar, since the only thing that happened was a
- // change in orientation
- mConfigChanged = false;
- } else {
- if (mIconView) {
- // Switching the menu to expanded view, so hide the
- // title bar.
- hideFakeTitleBar();
- mIconView = false;
- } else {
- // Switching the menu back to icon view, so show the
- // title bar once again.
- showFakeTitleBar();
- mIconView = true;
- }
- }
- } else {
- // The options menu is closed, so open it, and show the title
- showFakeTitleBar();
- mOptionsMenuOpen = true;
- mConfigChanged = false;
- mIconView = true;
- }
+ mController.onMenuOpened(featureId, menu);
}
return true;
}
- private void showFakeTitleBar() {
- if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null
- && !mActivityInPause) {
- WebView mainView = mTabControl.getCurrentWebView();
- // if there is no current WebView, don't show the faked title bar;
- 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);
-
- // Add the title bar to the window manager so it can receive touches
- // while the menu is up
- WindowManager.LayoutParams params
- = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- params.gravity = Gravity.TOP;
- boolean atTop = mainView.getScrollY() == 0;
- params.windowAnimations = atTop ? 0 : R.style.TitleBar;
- manager.addView(mFakeTitleBar, params);
- }
- }
-
@Override
public void onOptionsMenuClosed(Menu menu) {
- mOptionsMenuOpen = false;
- if (!mInLoad) {
- hideFakeTitleBar();
- } else if (!mIconView) {
- // The page is currently loading, and we are in expanded mode, so
- // we were not showing the menu. Show it once again. It will be
- // removed when the page finishes.
- showFakeTitleBar();
- }
- }
-
- private void hideFakeTitleBar() {
- if (mFakeTitleBar.getParent() == null) return;
- WindowManager.LayoutParams params = (WindowManager.LayoutParams)
- mFakeTitleBar.getLayoutParams();
- WebView mainView = mTabControl.getCurrentWebView();
- // Although we decided whether or not to animate based on the current
- // scroll position, the scroll position may have changed since the
- // fake title bar was displayed. Make sure it has the appropriate
- // animation/lack thereof before removing.
- params.windowAnimations = mainView != null && mainView.getScrollY() == 0
- ? 0 : R.style.TitleBar;
- WindowManager manager
- = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
- manager.updateViewLayout(mFakeTitleBar, params);
- manager.removeView(mFakeTitleBar);
- }
-
- /**
- * Special method for the fake title bar to call when displaying its context
- * menu, since it is in its own Window, and its parent does not show a
- * context menu.
- */
- /* package */ void showTitleBarContextMenu() {
- if (null == mTitleBar.getParent()) {
- return;
- }
- openContextMenu(mTitleBar);
+ mController.onOptionsMenuClosed(menu);
}
@Override
public void onContextMenuClosed(Menu menu) {
super.onContextMenuClosed(menu);
- if (mInLoad) {
- showFakeTitleBar();
- }
+ mController.onContextMenuClosed(menu);
}
/**
@@ -902,46 +148,13 @@ public class BrowserActivity extends Activity
if (LOGV_ENABLED) {
Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
}
- // the default implementation requires each view to have an id. As the
- // browser handles the state itself and it doesn't use id for the views,
- // don't call the default implementation. Otherwise it will trigger the
- // warning like this, "couldn't save which view has focus because the
- // focused view XXX has no id".
-
- // Save all the tabs
- mTabControl.saveState(outState);
+ mController.onSaveInstanceState(outState);
}
@Override
protected void onPause() {
+ mController.onPause();
super.onPause();
-
- if (mActivityInPause) {
- Log.e(LOGTAG, "BrowserActivity is already paused.");
- return;
- }
-
- mTabControl.pauseCurrentTab();
- mActivityInPause = true;
- if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
- mWakeLock.acquire();
- mHandler.sendMessageDelayed(mHandler
- .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
- }
-
- // FIXME: This removes the active tabs page and resets the menu to
- // MAIN_MENU. A better solution might be to do this work in onNewIntent
- // but then we would need to save it in onSaveInstanceState and restore
- // it in onCreate/onRestoreInstanceState
- if (mActiveTabsPage != null) {
- removeActiveTabPage(true);
- }
-
- cancelStopToast();
-
- // unregister network state listener
- unregisterReceiver(mNetworkStateIntentReceiver);
- WebView.disablePlatformNotifications();
}
@Override
@@ -950,3070 +163,83 @@ public class BrowserActivity extends Activity
Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
}
super.onDestroy();
-
- if (mUploadMessage != null) {
- mUploadMessage.onReceiveValue(null);
- mUploadMessage = null;
- }
-
- if (mTabControl == null) return;
-
- // Remove the fake title bar if it is there
- hideFakeTitleBar();
-
- // Remove the current tab and sub window
- Tab t = mTabControl.getCurrentTab();
- if (t != null) {
- dismissSubWindow(t);
- removeTabFromContentView(t);
- }
- // Destroy all the tabs
- mTabControl.destroy();
- WebIconDatabase.getInstance().close();
-
- unregisterReceiver(mPackageInstallationReceiver);
-
- // Stop watching the default geolocation permissions
- mSystemAllowGeolocationOrigins.stop();
- mSystemAllowGeolocationOrigins = null;
+ mController.onDestroy();
+ mUi = null;
+ mController = null;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
- mConfigChanged = true;
super.onConfigurationChanged(newConfig);
-
- if (mPageInfoDialog != null) {
- mPageInfoDialog.dismiss();
- showPageInfo(
- mPageInfoView,
- mPageInfoFromShowSSLCertificateOnError);
- }
- if (mSSLCertificateDialog != null) {
- mSSLCertificateDialog.dismiss();
- showSSLCertificate(
- mSSLCertificateView);
- }
- if (mSSLCertificateOnErrorDialog != null) {
- mSSLCertificateOnErrorDialog.dismiss();
- showSSLCertificateOnError(
- mSSLCertificateOnErrorView,
- mSSLCertificateOnErrorHandler,
- mSSLCertificateOnErrorError);
- }
- if (mHttpAuthenticationDialog != null) {
- String title = ((TextView) mHttpAuthenticationDialog
- .findViewById(com.android.internal.R.id.alertTitle)).getText()
- .toString();
- String name = ((TextView) mHttpAuthenticationDialog
- .findViewById(R.id.username_edit)).getText().toString();
- String password = ((TextView) mHttpAuthenticationDialog
- .findViewById(R.id.password_edit)).getText().toString();
- int focusId = mHttpAuthenticationDialog.getCurrentFocus()
- .getId();
- mHttpAuthenticationDialog.dismiss();
- showHttpAuthentication(mHttpAuthHandler, null, null, title,
- name, password, focusId);
- }
+ mController.onConfgurationChanged(newConfig);
}
@Override
public void onLowMemory() {
super.onLowMemory();
- mTabControl.freeMemory();
- }
-
- private void resumeWebViewTimers() {
- Tab tab = mTabControl.getCurrentTab();
- if (tab == null) return; // monkey can trigger this
- boolean inLoad = tab.inLoad();
- if ((!mActivityInPause && !inLoad) || (mActivityInPause && inLoad)) {
- CookieSyncManager.getInstance().startSync();
- WebView w = tab.getWebView();
- if (w != null) {
- w.resumeTimers();
- }
- }
- }
-
- private boolean pauseWebViewTimers() {
- Tab tab = mTabControl.getCurrentTab();
- boolean inLoad = tab.inLoad();
- if (mActivityInPause && !inLoad) {
- CookieSyncManager.getInstance().stopSync();
- WebView w = mTabControl.getCurrentWebView();
- if (w != null) {
- w.pauseTimers();
- }
- return true;
- } else {
- return false;
- }
- }
-
- // Open the icon database and retain all the icons for visited sites.
- private void retainIconsOnStartup() {
- final WebIconDatabase db = WebIconDatabase.getInstance();
- db.open(getDir("icons", 0).getPath());
- Cursor c = null;
- try {
- c = Browser.getAllBookmarks(mResolver);
- if (c.moveToFirst()) {
- int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
- do {
- String url = c.getString(urlIndex);
- db.retainIconForPageUrl(url);
- } while (c.moveToNext());
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "retainIconsOnStartup", e);
- } finally {
- if (c!= null) c.close();
- }
- }
-
- // Helper method for getting the top window.
- WebView getTopWindow() {
- return mTabControl.getCurrentTopWebView();
- }
-
- TabControl getTabControl() {
- return mTabControl;
+ mController.onLowMemory();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
-
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.browser, menu);
- mMenu = menu;
- updateInLoadMenuItems();
- return true;
- }
-
- /**
- * As the menu can be open when loading state changes
- * we must manually update the state of the stop/reload menu
- * item
- */
- private void updateInLoadMenuItems() {
- if (mMenu == null) {
- return;
- }
- MenuItem src = mInLoad ?
- mMenu.findItem(R.id.stop_menu_id):
- mMenu.findItem(R.id.reload_menu_id);
- MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
- dest.setIcon(src.getIcon());
- dest.setTitle(src.getTitle());
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- // chording is not an issue with context menus, but we use the same
- // options selector, so set mCanChord to true so we can access them.
- mCanChord = true;
- int id = item.getItemId();
- boolean result = true;
- switch (id) {
- // For the context menu from the title bar
- case R.id.title_bar_copy_page_url:
- Tab currentTab = mTabControl.getCurrentTab();
- if (null == currentTab) {
- result = false;
- break;
- }
- WebView mainView = currentTab.getWebView();
- if (null == mainView) {
- result = false;
- break;
- }
- copy(mainView.getUrl());
- break;
- // -- Browser context menu
- case R.id.open_context_menu_id:
- case R.id.open_newtab_context_menu_id:
- case R.id.bookmark_context_menu_id:
- case R.id.save_link_context_menu_id:
- case R.id.share_link_context_menu_id:
- case R.id.copy_link_context_menu_id:
- final WebView webView = getTopWindow();
- if (null == webView) {
- result = false;
- break;
- }
- final HashMap hrefMap = new HashMap();
- hrefMap.put("webview", webView);
- final Message msg = mHandler.obtainMessage(
- FOCUS_NODE_HREF, id, 0, hrefMap);
- webView.requestFocusNodeHref(msg);
- break;
-
- default:
- // For other context menus
- result = onOptionsItemSelected(item);
- }
- mCanChord = false;
- return result;
- }
-
- private Bundle createGoogleSearchSourceBundle(String source) {
- Bundle bundle = new Bundle();
- bundle.putString(Search.SOURCE, source);
- return bundle;
- }
-
- /* package */ void editUrl() {
- if (mOptionsMenuOpen) closeOptionsMenu();
- String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
- startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
- null, false);
+ return mController.onCreateOptionsMenu(menu);
}
- /**
- * Overriding this to insert a local information bundle
- */
@Override
- public void startSearch(String initialQuery, boolean selectInitialQuery,
- Bundle appSearchData, boolean globalSearch) {
- 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);
- }
-
- /**
- * Switch tabs. Called by the TitleBarSet when sliding the title bar
- * results in changing tabs.
- * @param index Index of the tab to change to, as defined by
- * mTabControl.getTabIndex(Tab t).
- * @return boolean True if we successfully switched to a different tab. If
- * the indexth tab is null, or if that tab is the same as
- * the current one, return false.
- */
- /* package */ boolean switchToTab(int index) {
- Tab tab = mTabControl.getTab(index);
- Tab currentTab = mTabControl.getCurrentTab();
- if (tab == null || tab == currentTab) {
- return false;
- }
- if (currentTab != null) {
- // currentTab may be null if it was just removed. In that case,
- // we do not need to remove it
- removeTabFromContentView(currentTab);
- }
- mTabControl.setCurrentTab(tab);
- attachTabToContentView(tab);
- resetTitleIconAndProgress();
- updateLockIconToLatest();
- return true;
- }
-
- /* package */ Tab openTabToHomePage() {
- return openTabAndShow(mSettings.getHomePage(), false, null);
- }
-
- /* package */ void closeCurrentWindow() {
- final Tab current = mTabControl.getCurrentTab();
- if (mTabControl.getTabCount() == 1) {
- // This is the last tab. Open a new one, with the home
- // page and close the current one.
- openTabToHomePage();
- closeTab(current);
- return;
- }
- final Tab parent = current.getParentTab();
- int indexToShow = -1;
- if (parent != null) {
- indexToShow = mTabControl.getTabIndex(parent);
- } else {
- final int currentIndex = mTabControl.getCurrentIndex();
- // Try to move to the tab to the right
- indexToShow = currentIndex + 1;
- if (indexToShow > mTabControl.getTabCount() - 1) {
- // Try to move to the tab to the left
- indexToShow = currentIndex - 1;
- }
- }
- if (switchToTab(indexToShow)) {
- // Close window
- closeTab(current);
- }
- }
-
- private ActiveTabsPage mActiveTabsPage;
-
- /**
- * Remove the active tabs page.
- * @param needToAttach If true, the active tabs page did not attach a tab
- * to the content view, so we need to do that here.
- */
- /* package */ void removeActiveTabPage(boolean needToAttach) {
- mContentView.removeView(mActiveTabsPage);
- mActiveTabsPage = null;
- mMenuState = R.id.MAIN_MENU;
- if (needToAttach) {
- attachTabToContentView(mTabControl.getCurrentTab());
- }
- 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);
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ return mController.prepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if (!mCanChord) {
- // The user has already fired a shortcut with this hold down of the
- // menu key.
- return false;
- }
- if (null == getTopWindow()) {
- return false;
- }
- if (mMenuIsDown) {
- // The shortcut action consumes the MENU. Even if it is still down,
- // it won't trigger the next shortcut action. In the case of the
- // shortcut action triggering a new activity, like Bookmarks, we
- // won't get onKeyUp for MENU. So it is important to reset it here.
- mMenuIsDown = false;
- }
- switch (item.getItemId()) {
- // -- Main menu
- case R.id.new_tab_menu_id:
- openTabToHomePage();
- break;
-
- case R.id.goto_menu_id:
- editUrl();
- break;
-
- case R.id.bookmarks_menu_id:
- bookmarksOrHistoryPicker(false);
- break;
-
- case R.id.active_tabs_menu_id:
- mActiveTabsPage = new ActiveTabsPage(this, mTabControl);
- removeTabFromContentView(mTabControl.getCurrentTab());
- hideFakeTitleBar();
- mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
- mActiveTabsPage.requestFocus();
- mMenuState = EMPTY_MENU;
- break;
-
- case R.id.add_bookmark_menu_id:
- Intent i = new Intent(BrowserActivity.this,
- AddBookmarkPage.class);
- WebView w = getTopWindow();
- i.putExtra("url", w.getUrl());
- i.putExtra("title", w.getTitle());
- i.putExtra("touch_icon_url", w.getTouchIconUrl());
- i.putExtra("thumbnail", createScreenshot(w));
- startActivity(i);
- break;
-
- case R.id.stop_reload_menu_id:
- if (mInLoad) {
- stopLoading();
- } else {
- getTopWindow().reload();
- }
- break;
-
- case R.id.back_menu_id:
- getTopWindow().goBack();
- break;
-
- case R.id.forward_menu_id:
- getTopWindow().goForward();
- break;
-
- case R.id.close_menu_id:
- // Close the subwindow if it exists.
- if (mTabControl.getCurrentSubWindow() != null) {
- dismissSubWindow(mTabControl.getCurrentTab());
- break;
- }
- closeCurrentWindow();
- break;
-
- case R.id.homepage_menu_id:
- Tab current = mTabControl.getCurrentTab();
- if (current != null) {
- dismissSubWindow(current);
- loadUrl(current.getWebView(), mSettings.getHomePage());
- }
- break;
-
- case R.id.preferences_menu_id:
- Intent intent = new Intent(this,
- BrowserPreferencesPage.class);
- intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE,
- getTopWindow().getUrl());
- startActivityForResult(intent, PREFERENCES_PAGE);
- break;
-
- case R.id.find_menu_id:
- showFindDialog();
- break;
-
- case R.id.select_text_id:
- 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;
-
- case R.id.classic_history_menu_id:
- bookmarksOrHistoryPicker(true);
- break;
-
- case R.id.title_bar_share_page_url:
- case R.id.share_page_menu_id:
- Tab currentTab = mTabControl.getCurrentTab();
- if (null == currentTab) {
- mCanChord = false;
- return false;
- }
- currentTab.populatePickerData();
- sharePage(this, currentTab.getTitle(),
- currentTab.getUrl(), currentTab.getFavicon(),
- createScreenshot(currentTab.getWebView()));
- break;
-
- case R.id.dump_nav_menu_id:
- getTopWindow().debugDump();
- break;
-
- case R.id.dump_counters_menu_id:
- getTopWindow().dumpV8Counters();
- break;
-
- case R.id.zoom_in_menu_id:
- getTopWindow().zoomIn();
- break;
-
- case R.id.zoom_out_menu_id:
- getTopWindow().zoomOut();
- break;
-
- case R.id.view_downloads_menu_id:
- viewDownloads();
- break;
-
- case R.id.window_one_menu_id:
- case R.id.window_two_menu_id:
- case R.id.window_three_menu_id:
- case R.id.window_four_menu_id:
- case R.id.window_five_menu_id:
- case R.id.window_six_menu_id:
- case R.id.window_seven_menu_id:
- case R.id.window_eight_menu_id:
- {
- int menuid = item.getItemId();
- for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
- if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
- Tab desiredTab = mTabControl.getTab(id);
- if (desiredTab != null &&
- desiredTab != mTabControl.getCurrentTab()) {
- switchToTab(id);
- }
- break;
- }
- }
- }
- break;
-
- default:
- if (!super.onOptionsItemSelected(item)) {
- return false;
- }
- // Otherwise fall through.
- }
- mCanChord = false;
- return true;
- }
-
- 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
- public boolean onPrepareOptionsMenu(Menu menu) {
- // This happens when the user begins to hold down the menu key, so
- // allow them to chord to get a shortcut.
- mCanChord = true;
- // Note: setVisible will decide whether an item is visible; while
- // setEnabled() will decide whether an item is enabled, which also means
- // whether the matching shortcut key will function.
- super.onPrepareOptionsMenu(menu);
- switch (mMenuState) {
- case EMPTY_MENU:
- if (mCurrentMenuState != mMenuState) {
- menu.setGroupVisible(R.id.MAIN_MENU, false);
- menu.setGroupEnabled(R.id.MAIN_MENU, false);
- menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
- }
- break;
- default:
- if (mCurrentMenuState != mMenuState) {
- menu.setGroupVisible(R.id.MAIN_MENU, true);
- menu.setGroupEnabled(R.id.MAIN_MENU, true);
- menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
- }
- final WebView w = getTopWindow();
- boolean canGoBack = false;
- boolean canGoForward = false;
- boolean isHome = false;
- if (w != null) {
- canGoBack = w.canGoBack();
- canGoForward = w.canGoForward();
- isHome = mSettings.getHomePage().equals(w.getUrl());
- }
- final MenuItem back = menu.findItem(R.id.back_menu_id);
- back.setEnabled(canGoBack);
-
- final MenuItem home = menu.findItem(R.id.homepage_menu_id);
- home.setEnabled(!isHome);
-
- menu.findItem(R.id.forward_menu_id)
- .setEnabled(canGoForward);
-
- menu.findItem(R.id.new_tab_menu_id).setEnabled(
- mTabControl.canCreateNewTab());
-
- // decide whether to show the share link option
- PackageManager pm = getPackageManager();
- Intent send = new Intent(Intent.ACTION_SEND);
- send.setType("text/plain");
- ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
- menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
-
- boolean isNavDump = mSettings.isNavDump();
- final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
- nav.setVisible(isNavDump);
- nav.setEnabled(isNavDump);
-
- boolean showDebugSettings = mSettings.showDebugSettings();
- final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id);
- counter.setVisible(showDebugSettings);
- counter.setEnabled(showDebugSettings);
-
- break;
+ if (!mController.onOptionsItemSelected(item)) {
+ return super.onOptionsItemSelected(item);
}
- mCurrentMenuState = mMenuState;
return true;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
- if (v instanceof TitleBar) {
- return;
- }
- WebView webview = (WebView) v;
- WebView.HitTestResult result = webview.getHitTestResult();
- if (result == null) {
- return;
- }
-
- int type = result.getType();
- if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
- Log.w(LOGTAG,
- "We should not show context menu when nothing is touched");
- return;
- }
- if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
- // let TextView handles context menu
- return;
- }
-
- // Note, http://b/issue?id=1106666 is requesting that
- // an inflated menu can be used again. This is not available
- // yet, so inflate each time (yuk!)
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.browsercontext, menu);
-
- // Show the correct menu group
- String extra = result.getExtra();
- menu.setGroupVisible(R.id.PHONE_MENU,
- type == WebView.HitTestResult.PHONE_TYPE);
- menu.setGroupVisible(R.id.EMAIL_MENU,
- type == WebView.HitTestResult.EMAIL_TYPE);
- menu.setGroupVisible(R.id.GEO_MENU,
- type == WebView.HitTestResult.GEO_TYPE);
- menu.setGroupVisible(R.id.IMAGE_MENU,
- type == WebView.HitTestResult.IMAGE_TYPE
- || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
- menu.setGroupVisible(R.id.ANCHOR_MENU,
- type == WebView.HitTestResult.SRC_ANCHOR_TYPE
- || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
-
- // Setup custom handling depending on the type
- switch (type) {
- case WebView.HitTestResult.PHONE_TYPE:
- menu.setHeaderTitle(Uri.decode(extra));
- menu.findItem(R.id.dial_context_menu_id).setIntent(
- new Intent(Intent.ACTION_VIEW, Uri
- .parse(WebView.SCHEME_TEL + extra)));
- Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
- addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
- menu.findItem(R.id.add_contact_context_menu_id).setIntent(
- addIntent);
- menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
- new Copy(extra));
- break;
-
- case WebView.HitTestResult.EMAIL_TYPE:
- menu.setHeaderTitle(extra);
- menu.findItem(R.id.email_context_menu_id).setIntent(
- new Intent(Intent.ACTION_VIEW, Uri
- .parse(WebView.SCHEME_MAILTO + extra)));
- menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
- new Copy(extra));
- break;
-
- case WebView.HitTestResult.GEO_TYPE:
- menu.setHeaderTitle(extra);
- menu.findItem(R.id.map_context_menu_id).setIntent(
- new Intent(Intent.ACTION_VIEW, Uri
- .parse(WebView.SCHEME_GEO
- + URLEncoder.encode(extra))));
- menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
- new Copy(extra));
- break;
-
- case WebView.HitTestResult.SRC_ANCHOR_TYPE:
- case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
- TextView titleView = (TextView) LayoutInflater.from(this)
- .inflate(android.R.layout.browser_link_context_header,
- null);
- titleView.setText(extra);
- menu.setHeaderView(titleView);
- // decide whether to show the open link in new tab option
- menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
- mTabControl.canCreateNewTab());
- menu.findItem(R.id.bookmark_context_menu_id).setVisible(
- Bookmarks.urlHasAcceptableScheme(extra));
- PackageManager pm = getPackageManager();
- Intent send = new Intent(Intent.ACTION_SEND);
- send.setType("text/plain");
- ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
- menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
- if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
- break;
- }
- // otherwise fall through to handle image part
- case WebView.HitTestResult.IMAGE_TYPE:
- if (type == WebView.HitTestResult.IMAGE_TYPE) {
- menu.setHeaderTitle(extra);
- }
- menu.findItem(R.id.view_image_context_menu_id).setIntent(
- new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
- menu.findItem(R.id.download_context_menu_id).
- setOnMenuItemClickListener(new Download(extra));
- menu.findItem(R.id.set_wallpaper_context_menu_id).
- setOnMenuItemClickListener(new SetAsWallpaper(extra));
- break;
-
- default:
- Log.w(LOGTAG, "We should not get here.");
- break;
- }
- hideFakeTitleBar();
- }
-
- // Attach the given tab to the content view.
- // this should only be called for the current tab.
- private void attachTabToContentView(Tab t) {
- // Attach the container that contains the main WebView and any other UI
- // associated with the tab.
- t.attachTabToContentView(mContentView);
-
- if (mShouldShowErrorConsole) {
- ErrorConsoleView errorConsole = t.getErrorConsole(true);
- if (errorConsole.numberOfErrors() == 0) {
- errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
- } else {
- errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
- }
-
- mErrorConsoleContainer.addView(errorConsole,
- new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- }
-
- WebView view = t.getWebView();
- view.setEmbeddedTitleBar(mTitleBar);
- if (t.isInVoiceSearchMode()) {
- showVoiceTitleBar(t.getVoiceDisplayTitle());
- } else {
- revertVoiceTitleBar();
- }
- // Request focus on the top window.
- t.getTopWindow().requestFocus();
- }
-
- // Attach a sub window to the main WebView of the given tab.
- void attachSubWindow(Tab t) {
- t.attachSubWindow(mContentView);
- getTopWindow().requestFocus();
+ mController.onCreateContextMenu(menu, v, menuInfo);
}
- // Remove the given tab from the content view.
- private void removeTabFromContentView(Tab t) {
- // Remove the container that contains the main WebView.
- t.removeTabFromContentView(mContentView);
-
- ErrorConsoleView errorConsole = t.getErrorConsole(false);
- if (errorConsole != null) {
- mErrorConsoleContainer.removeView(errorConsole);
- }
-
- WebView view = t.getWebView();
- if (view != null) {
- view.setEmbeddedTitleBar(null);
- }
- }
-
- // Remove the sub window if it exists. Also called by TabControl when the
- // user clicks the 'X' to dismiss a sub window.
- /* package */ void dismissSubWindow(Tab t) {
- t.removeSubWindow(mContentView);
- // dismiss the subwindow. This will destroy the WebView.
- t.dismissSubWindow();
- getTopWindow().requestFocus();
- }
-
- // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
- // that accepts url as string.
- private Tab openTabAndShow(String url, boolean closeOnExit, String appId) {
- return openTabAndShow(new UrlData(url), closeOnExit, appId);
- }
-
- // This method does a ton of stuff. It will attempt to create a new tab
- // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
- // url isn't null, it will load the given url.
- /* package */Tab openTabAndShow(UrlData urlData, boolean closeOnExit,
- String appId) {
- final Tab currentTab = mTabControl.getCurrentTab();
- if (mTabControl.canCreateNewTab()) {
- final Tab tab = mTabControl.createNewTab(closeOnExit, appId,
- urlData.mUrl);
- WebView webview = tab.getWebView();
- // If the last tab was removed from the active tabs page, currentTab
- // will be null.
- if (currentTab != null) {
- removeTabFromContentView(currentTab);
- }
- // We must set the new tab as the current tab to reflect the old
- // animation behavior.
- mTabControl.setCurrentTab(tab);
- attachTabToContentView(tab);
- if (!urlData.isEmpty()) {
- loadUrlDataIn(tab, urlData);
- }
- return tab;
- } else {
- // Get rid of the subwindow if it exists
- dismissSubWindow(currentTab);
- if (!urlData.isEmpty()) {
- // Load the given url.
- loadUrlDataIn(currentTab, urlData);
- }
- return currentTab;
- }
- }
-
- private Tab openTab(String url) {
- if (mSettings.openInBackground()) {
- Tab t = mTabControl.createNewTab();
- if (t != null) {
- WebView view = t.getWebView();
- loadUrl(view, url);
- }
- return t;
- } else {
- return openTabAndShow(url, false, null);
- }
- }
-
- private class Copy implements OnMenuItemClickListener {
- private CharSequence mText;
-
- public boolean onMenuItemClick(MenuItem item) {
- copy(mText);
- return true;
- }
-
- public Copy(CharSequence toCopy) {
- mText = toCopy;
- }
- }
-
- private class Download implements OnMenuItemClickListener {
- private String mText;
-
- public boolean onMenuItemClick(MenuItem item) {
- onDownloadStartNoStream(mText, null, null, null, -1);
- return true;
- }
-
- public Download(String toDownload) {
- mText = toDownload;
- }
- }
-
- private class SetAsWallpaper extends Thread implements
- OnMenuItemClickListener, DialogInterface.OnCancelListener {
- private URL mUrl;
- private ProgressDialog mWallpaperProgress;
- private boolean mCanceled = false;
-
- public SetAsWallpaper(String url) {
- try {
- mUrl = new URL(url);
- } catch (MalformedURLException e) {
- mUrl = null;
- }
- }
-
- public void onCancel(DialogInterface dialog) {
- mCanceled = true;
- }
-
- public boolean onMenuItemClick(MenuItem item) {
- if (mUrl != null) {
- // The user may have tried to set a image with a large file size as their
- // background so it may take a few moments to perform the operation. Display
- // a progress spinner while it is working.
- mWallpaperProgress = new ProgressDialog(BrowserActivity.this);
- mWallpaperProgress.setIndeterminate(true);
- mWallpaperProgress.setMessage(getText(R.string.progress_dialog_setting_wallpaper));
- mWallpaperProgress.setCancelable(true);
- mWallpaperProgress.setOnCancelListener(this);
- mWallpaperProgress.show();
- start();
- }
- return true;
- }
-
- public void run() {
- Drawable oldWallpaper = BrowserActivity.this.getWallpaper();
- try {
- // TODO: This will cause the resource to be downloaded again, when we
- // should in most cases be able to grab it from the cache. To fix this
- // we should query WebCore to see if we can access a cached version and
- // instead open an input stream on that. This pattern could also be used
- // in the download manager where the same problem exists.
- InputStream inputstream = mUrl.openStream();
- if (inputstream != null) {
- setWallpaper(inputstream);
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Unable to set new wallpaper");
- // Act as though the user canceled the operation so we try to
- // restore the old wallpaper.
- mCanceled = true;
- }
-
- if (mCanceled) {
- // Restore the old wallpaper if the user cancelled whilst we were setting
- // the new wallpaper.
- int width = oldWallpaper.getIntrinsicWidth();
- int height = oldWallpaper.getIntrinsicHeight();
- Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
- Canvas canvas = new Canvas(bm);
- oldWallpaper.setBounds(0, 0, width, height);
- oldWallpaper.draw(canvas);
- try {
- setWallpaper(bm);
- } catch (IOException e) {
- Log.e(LOGTAG, "Unable to restore old wallpaper.");
- }
- mCanceled = false;
- }
-
- if (mWallpaperProgress.isShowing()) {
- mWallpaperProgress.dismiss();
- }
- }
- }
-
- private void copy(CharSequence text) {
- try {
- IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
- if (clip != null) {
- clip.setClipboardText(text);
- }
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Copy failed", e);
- }
- }
-
- /**
- * Resets the browser title-view to whatever it must be
- * (for example, if we had a loading error)
- * When we have a new page, we call resetTitle, when we
- * have to reset the titlebar to whatever it used to be
- * (for example, if the user chose to stop loading), we
- * call resetTitleAndRevertLockIcon.
- */
- /* package */ void resetTitleAndRevertLockIcon() {
- mTabControl.getCurrentTab().revertLockIcon();
- updateLockIconToLatest();
- resetTitleIconAndProgress();
- }
-
- /**
- * Reset the title, favicon, and progress.
- */
- private void resetTitleIconAndProgress() {
- WebView current = mTabControl.getCurrentWebView();
- if (current == null) {
- return;
- }
- resetTitleAndIcon(current);
- int progress = current.getProgress();
- current.getWebChromeClient().onProgressChanged(current, progress);
- }
-
- // Reset the title and the icon based on the given item.
- private void resetTitleAndIcon(WebView view) {
- WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
- if (item != null) {
- setUrlTitle(item.getUrl(), item.getTitle());
- setFavicon(item.getFavicon());
- } else {
- setUrlTitle(null, null);
- setFavicon(null);
- }
- }
-
- /**
- * Sets a title composed of the URL and the title string.
- * @param url The URL of the site being loaded.
- * @param title The title of the site being loaded.
- */
- void setUrlTitle(String url, String title) {
- mUrl = url;
- mTitle = title;
-
- // If we are in voice search mode, the title has already been set.
- if (mTabControl.getCurrentTab().isInVoiceSearchMode()) return;
- mTitleBar.setDisplayTitle(url);
- mFakeTitleBar.setDisplayTitle(url);
- }
-
- /**
- * @param url The URL to build a title version of the URL from.
- * @return The title version of the URL or null if fails.
- * The title version of the URL can be either the URL hostname,
- * or the hostname with an "https://" prefix (for secure URLs),
- * or an empty string if, for example, the URL in question is a
- * file:// URL with no hostname.
- */
- /* package */ static String buildTitleUrl(String url) {
- String titleUrl = null;
-
- if (url != null) {
- try {
- // parse the url string
- URL urlObj = new URL(url);
- if (urlObj != null) {
- titleUrl = "";
-
- String protocol = urlObj.getProtocol();
- String host = urlObj.getHost();
-
- if (host != null && 0 < host.length()) {
- titleUrl = host;
- if (protocol != null) {
- // if a secure site, add an "https://" prefix!
- if (protocol.equalsIgnoreCase("https")) {
- titleUrl = protocol + "://" + host;
- }
- }
- }
- }
- } catch (MalformedURLException e) {}
- }
-
- return titleUrl;
- }
-
- // Set the favicon in the title bar.
- void setFavicon(Bitmap icon) {
- mTitleBar.setFavicon(icon);
- mFakeTitleBar.setFavicon(icon);
- }
-
- /**
- * Close the tab, remove its associated title bar, and adjust mTabControl's
- * current tab to a valid value.
- */
- /* package */ void closeTab(Tab t) {
- int currentIndex = mTabControl.getCurrentIndex();
- int removeIndex = mTabControl.getTabIndex(t);
- mTabControl.removeTab(t);
- if (currentIndex >= removeIndex && currentIndex != 0) {
- currentIndex--;
- }
- mTabControl.setCurrentTab(mTabControl.getTab(currentIndex));
- resetTitleIconAndProgress();
- }
-
- /* package */ void goBackOnePageOrQuit() {
- Tab current = mTabControl.getCurrentTab();
- if (current == null) {
- /*
- * Instead of finishing the activity, simply push this to the back
- * of the stack and let ActivityManager to choose the foreground
- * activity. As BrowserActivity is singleTask, it will be always the
- * root of the task. So we can use either true or false for
- * moveTaskToBack().
- */
- moveTaskToBack(true);
- return;
- }
- WebView w = current.getWebView();
- if (w.canGoBack()) {
- w.goBack();
- } else {
- // Check to see if we are closing a window that was created by
- // another window. If so, we switch back to that window.
- Tab parent = current.getParentTab();
- if (parent != null) {
- switchToTab(mTabControl.getTabIndex(parent));
- // Now we close the other tab
- closeTab(current);
- } else {
- if (current.closeOnExit()) {
- // force the tab's inLoad() to be false as we are going to
- // either finish the activity or remove the tab. This will
- // ensure pauseWebViewTimers() taking action.
- mTabControl.getCurrentTab().clearInLoad();
- if (mTabControl.getTabCount() == 1) {
- finish();
- return;
- }
- // call pauseWebViewTimers() now, we won't be able to call
- // it in onPause() as the WebView won't be valid.
- // Temporarily change mActivityInPause to be true as
- // pauseWebViewTimers() will do nothing if mActivityInPause
- // is false.
- boolean savedState = mActivityInPause;
- if (savedState) {
- Log.e(LOGTAG, "BrowserActivity is already paused "
- + "while handing goBackOnePageOrQuit.");
- }
- mActivityInPause = true;
- pauseWebViewTimers();
- mActivityInPause = savedState;
- removeTabFromContentView(current);
- mTabControl.removeTab(current);
- }
- /*
- * Instead of finishing the activity, simply push this to the back
- * of the stack and let ActivityManager to choose the foreground
- * activity. As BrowserActivity is singleTask, it will be always the
- * root of the task. So we can use either true or false for
- * moveTaskToBack().
- */
- moveTaskToBack(true);
- }
- }
- }
-
- boolean isMenuDown() {
- return mMenuIsDown;
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ return mController.onContextItemSelected(item);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- // Even if MENU is already held down, we need to call to super to open
- // the IME on long press.
- if (KeyEvent.KEYCODE_MENU == keyCode) {
- mMenuIsDown = true;
- return super.onKeyDown(keyCode, event);
- }
- // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
- // still down, we don't want to trigger the search. Pretend to consume
- // the key and do nothing.
- if (mMenuIsDown) return true;
-
- switch(keyCode) {
- case KeyEvent.KEYCODE_SPACE:
- // WebView/WebTextView handle the keys in the KeyDown. As
- // the Activity's shortcut keys are only handled when WebView
- // doesn't, have to do it in onKeyDown instead of onKeyUp.
- if (event.isShiftPressed()) {
- getTopWindow().pageUp(false);
- } else {
- getTopWindow().pageDown(false);
- }
- return true;
- case KeyEvent.KEYCODE_BACK:
- if (event.getRepeatCount() == 0) {
- event.startTracking();
- return true;
- } else if (mCustomView == null && mActiveTabsPage == null
- && event.isLongPress()) {
- bookmarksOrHistoryPicker(true);
- return true;
- }
- break;
- }
- return super.onKeyDown(keyCode, event);
+ return mController.onKeyDown(keyCode, event) ||
+ super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- switch(keyCode) {
- case KeyEvent.KEYCODE_MENU:
- mMenuIsDown = false;
- break;
- case KeyEvent.KEYCODE_BACK:
- if (event.isTracking() && !event.isCanceled()) {
- if (mCustomView != null) {
- // if a custom view is showing, hide it
- mTabControl.getCurrentWebView().getWebChromeClient()
- .onHideCustomView();
- } else if (mActiveTabsPage != null) {
- // if tab page is showing, hide it
- removeActiveTabPage(true);
- } else {
- WebView subwindow = mTabControl.getCurrentSubWindow();
- if (subwindow != null) {
- if (subwindow.canGoBack()) {
- subwindow.goBack();
- } else {
- dismissSubWindow(mTabControl.getCurrentTab());
- }
- } else {
- goBackOnePageOrQuit();
- }
- }
- return true;
- }
- break;
- }
- return super.onKeyUp(keyCode, event);
- }
-
- /* package */ void stopLoading() {
- mDidStopLoad = true;
- resetTitleAndRevertLockIcon();
- WebView w = getTopWindow();
- w.stopLoading();
- // FIXME: before refactor, it is using mWebViewClient. So I keep the
- // same logic here. But for subwindow case, should we call into the main
- // WebView's onPageFinished as we never call its onPageStarted and if
- // the page finishes itself, we don't call onPageFinished.
- mTabControl.getCurrentWebView().getWebViewClient().onPageFinished(w,
- w.getUrl());
-
- cancelStopToast();
- mStopToast = Toast
- .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
- mStopToast.show();
- }
-
- boolean didUserStopLoading() {
- return mDidStopLoad;
- }
-
- private void cancelStopToast() {
- if (mStopToast != null) {
- mStopToast.cancel();
- mStopToast = null;
- }
+ return mController.onKeyUp(keyCode, event) ||
+ super.onKeyUp(keyCode, event);
}
- // called by a UI or non-UI thread to post the message
- public void postMessage(int what, int arg1, int arg2, Object obj,
- long delayMillis) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(what, arg1, arg2,
- obj), delayMillis);
- }
-
- // called by a UI or non-UI thread to remove the message
- void removeMessages(int what, Object object) {
- mHandler.removeMessages(what, object);
- }
-
- // public message ids
- public final static int LOAD_URL = 1001;
- public final static int STOP_LOAD = 1002;
-
- // Message Ids
- private static final int FOCUS_NODE_HREF = 102;
- private static final int RELEASE_WAKELOCK = 107;
-
- static final int UPDATE_BOOKMARK_THUMBNAIL = 108;
-
- // Private handler for handling javascript and saving passwords
- private Handler mHandler = new Handler() {
-
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case FOCUS_NODE_HREF:
- {
- String url = (String) msg.getData().get("url");
- String title = (String) msg.getData().get("title");
- if (url == null || url.length() == 0) {
- break;
- }
- HashMap focusNodeMap = (HashMap) msg.obj;
- WebView view = (WebView) focusNodeMap.get("webview");
- // Only apply the action if the top window did not change.
- if (getTopWindow() != view) {
- break;
- }
- switch (msg.arg1) {
- case R.id.open_context_menu_id:
- case R.id.view_image_context_menu_id:
- loadUrlFromContext(getTopWindow(), url);
- break;
- case R.id.open_newtab_context_menu_id:
- final Tab parent = mTabControl.getCurrentTab();
- final Tab newTab = openTab(url);
- if (newTab != parent) {
- parent.addChildTab(newTab);
- }
- break;
- case R.id.bookmark_context_menu_id:
- Intent intent = new Intent(BrowserActivity.this,
- AddBookmarkPage.class);
- intent.putExtra("url", url);
- intent.putExtra("title", title);
- startActivity(intent);
- break;
- case R.id.share_link_context_menu_id:
- // See if this site has been visited before
- StringBuilder sb = new StringBuilder(
- Browser.BookmarkColumns.URL + " = ");
- DatabaseUtils.appendEscapedSQLString(sb, url);
- Cursor c = null;
- try {
- c = mResolver.query(Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- sb.toString(),
- null,
- null);
- if (c.moveToFirst()) {
- // The site has been visited before, so grab the
- // info from the database.
- Bitmap favicon = null;
- Bitmap thumbnail = null;
- String linkTitle = c.getString(Browser.
- HISTORY_PROJECTION_TITLE_INDEX);
- byte[] data = c.getBlob(Browser.
- HISTORY_PROJECTION_FAVICON_INDEX);
- if (data != null) {
- favicon = BitmapFactory.decodeByteArray(
- data, 0, data.length);
- }
- data = c.getBlob(Browser.
- HISTORY_PROJECTION_THUMBNAIL_INDEX);
- if (data != null) {
- thumbnail = BitmapFactory.decodeByteArray(
- data, 0, data.length);
- }
- sharePage(BrowserActivity.this,
- linkTitle, url, favicon, thumbnail);
- } else {
- Browser.sendString(BrowserActivity.this, url,
- getString(
- R.string.choosertitle_sharevia));
- }
- } finally {
- if (c != null) c.close();
- }
- // close the cursor after its used
- if (c != null) {
- c.close();
- }
- break;
- case R.id.copy_link_context_menu_id:
- copy(url);
- break;
- case R.id.save_link_context_menu_id:
- case R.id.download_context_menu_id:
- onDownloadStartNoStream(url, null, null, null, -1);
- break;
- }
- break;
- }
-
- case LOAD_URL:
- loadUrlFromContext(getTopWindow(), (String) msg.obj);
- break;
-
- case STOP_LOAD:
- stopLoading();
- break;
-
- case RELEASE_WAKELOCK:
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- // if we reach here, Browser should be still in the
- // background loading after WAKELOCK_TIMEOUT (5-min).
- // To avoid burning the battery, stop loading.
- mTabControl.stopAllLoading();
- }
- break;
-
- case UPDATE_BOOKMARK_THUMBNAIL:
- WebView view = (WebView) msg.obj;
- if (view != null) {
- updateScreenshot(view);
- }
- break;
- }
- }
- };
-
- /**
- * Share a page, providing the title, url, favicon, and a screenshot. Uses
- * an {@link Intent} to launch the Activity chooser.
- * @param c Context used to launch a new Activity.
- * @param title Title of the page. Stored in the Intent with
- * {@link Intent#EXTRA_SUBJECT}
- * @param url URL of the page. Stored in the Intent with
- * {@link Intent#EXTRA_TEXT}
- * @param favicon Bitmap of the favicon for the page. Stored in the Intent
- * with {@link Browser#EXTRA_SHARE_FAVICON}
- * @param screenshot Bitmap of a screenshot of the page. Stored in the
- * Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT}
- */
- public static final void sharePage(Context c, String title, String url,
- Bitmap favicon, Bitmap screenshot) {
- Intent send = new Intent(Intent.ACTION_SEND);
- send.setType("text/plain");
- send.putExtra(Intent.EXTRA_TEXT, url);
- send.putExtra(Intent.EXTRA_SUBJECT, title);
- send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon);
- send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot);
- try {
- c.startActivity(Intent.createChooser(send, c.getString(
- R.string.choosertitle_sharevia)));
- } catch(android.content.ActivityNotFoundException ex) {
- // if no app handles it, do nothing
- }
- }
-
- private void updateScreenshot(WebView view) {
- // If this is a bookmarked site, add a screenshot to the database.
- // FIXME: When should we update? Every time?
- // FIXME: Would like to make sure there is actually something to
- // draw, but the API for that (WebViewCore.pictureReady()) is not
- // currently accessible here.
-
- final Bitmap bm = createScreenshot(view);
- if (bm == null) {
- return;
- }
-
- final ContentResolver cr = getContentResolver();
- final String url = view.getUrl();
- final String originalUrl = view.getOriginalUrl();
-
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... unused) {
- Cursor c = null;
- try {
- c = BrowserBookmarksAdapter.queryBookmarksForUrl(
- cr, originalUrl, url, true);
- if (c != null) {
- if (c.moveToFirst()) {
- ContentValues values = new ContentValues();
- final ByteArrayOutputStream os
- = new ByteArrayOutputStream();
- bm.compress(Bitmap.CompressFormat.PNG, 100, os);
- values.put(Browser.BookmarkColumns.THUMBNAIL,
- os.toByteArray());
- do {
- cr.update(ContentUris.withAppendedId(
- Browser.BOOKMARKS_URI, c.getInt(0)),
- values, null, null);
- } while (c.moveToNext());
- }
- }
- } catch (IllegalStateException e) {
- // Ignore
- } finally {
- if (c != null) c.close();
- }
- return null;
- }
- }.execute();
- }
-
- /**
- * Values for the size of the thumbnail created when taking a screenshot.
- * Lazily initialized. Instead of using these directly, use
- * getDesiredThumbnailWidth() or getDesiredThumbnailHeight().
- */
- private static int THUMBNAIL_WIDTH = 0;
- private static int THUMBNAIL_HEIGHT = 0;
-
- /**
- * Return the desired width for thumbnail screenshots, which are stored in
- * the database, and used on the bookmarks screen.
- * @param context Context for finding out the density of the screen.
- * @return int desired width for thumbnail screenshot.
- */
- /* package */ static int getDesiredThumbnailWidth(Context context) {
- if (THUMBNAIL_WIDTH == 0) {
- float density = context.getResources().getDisplayMetrics().density;
- THUMBNAIL_WIDTH = (int) (90 * density);
- THUMBNAIL_HEIGHT = (int) (80 * density);
- }
- return THUMBNAIL_WIDTH;
- }
-
- /**
- * Return the desired height for thumbnail screenshots, which are stored in
- * the database, and used on the bookmarks screen.
- * @param context Context for finding out the density of the screen.
- * @return int desired height for thumbnail screenshot.
- */
- /* package */ static int getDesiredThumbnailHeight(Context context) {
- // To ensure that they are both initialized.
- getDesiredThumbnailWidth(context);
- return THUMBNAIL_HEIGHT;
- }
-
- private Bitmap createScreenshot(WebView view) {
- Picture thumbnail = view.capturePicture();
- if (thumbnail == null) {
- return null;
- }
- Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this),
- getDesiredThumbnailHeight(this), Bitmap.Config.RGB_565);
- Canvas canvas = new Canvas(bm);
- // May need to tweak these values to determine what is the
- // best scale factor
- int thumbnailWidth = thumbnail.getWidth();
- int thumbnailHeight = thumbnail.getHeight();
- float scaleFactorX = 1.0f;
- float scaleFactorY = 1.0f;
- if (thumbnailWidth > 0) {
- scaleFactorX = (float) getDesiredThumbnailWidth(this) /
- (float)thumbnailWidth;
- } else {
- return null;
- }
-
- if (view.getWidth() > view.getHeight() &&
- thumbnailHeight < view.getHeight() && thumbnailHeight > 0) {
- // If the device is in landscape and the page is shorter
- // than the height of the view, stretch the thumbnail to fill the
- // space.
- scaleFactorY = (float) getDesiredThumbnailHeight(this) /
- (float)thumbnailHeight;
- } else {
- // In the portrait case, this looks nice.
- scaleFactorY = scaleFactorX;
- }
-
- canvas.scale(scaleFactorX, scaleFactorY);
-
- thumbnail.draw(canvas);
- return bm;
- }
-
- // -------------------------------------------------------------------------
- // Helper function for WebViewClient.
- //-------------------------------------------------------------------------
-
- // Use in overrideUrlLoading
- /* package */ final static String SCHEME_WTAI = "wtai://wp/";
- /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
- /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
- /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
-
- // Keep this initial progress in sync with initialProgressValue (* 100)
- // in ProgressTracker.cpp
- private final static int INITIAL_PROGRESS = 10;
-
- void onPageStarted(WebView view, String url, Bitmap favicon) {
- // when BrowserActivity just starts, onPageStarted may be called before
- // onResume as it is triggered from onCreate. Call resumeWebViewTimers
- // to start the timer. As we won't switch tabs while an activity is in
- // pause state, we can ensure calling resume and pause in pair.
- if (mActivityInPause) resumeWebViewTimers();
-
- resetLockIcon(url);
- setUrlTitle(url, null);
- setFavicon(favicon);
- // Show some progress so that the user knows the page is beginning to
- // load
- onProgressChanged(view, INITIAL_PROGRESS);
- mDidStopLoad = false;
- if (!mIsNetworkUp) createAndShowNetworkDialog();
- closeDialogs();
- if (mSettings.isTracing()) {
- String host;
- try {
- WebAddress uri = new WebAddress(url);
- host = uri.mHost;
- } catch (android.net.ParseException ex) {
- host = "browser";
- }
- host = host.replace('.', '_');
- host += ".trace";
- mInTrace = true;
- Debug.startMethodTracing(host, 20 * 1024 * 1024);
- }
-
- // Performance probe
- if (false) {
- mStart = SystemClock.uptimeMillis();
- mProcessStart = Process.getElapsedCpuTime();
- long[] sysCpu = new long[7];
- if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
- sysCpu, null)) {
- mUserStart = sysCpu[0] + sysCpu[1];
- mSystemStart = sysCpu[2];
- mIdleStart = sysCpu[3];
- mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
- }
- mUiStart = SystemClock.currentThreadTimeMillis();
- }
- }
-
- void onPageFinished(WebView view, String url) {
- // Reset the title and icon in case we stopped a provisional load.
- resetTitleAndIcon(view);
- // Update the lock icon image only once we are done loading
- updateLockIconToLatest();
- // pause the WebView timer and release the wake lock if it is finished
- // while BrowserActivity is in pause state.
- if (mActivityInPause && pauseWebViewTimers()) {
- if (mWakeLock.isHeld()) {
- mHandler.removeMessages(RELEASE_WAKELOCK);
- mWakeLock.release();
- }
- }
-
- // Performance probe
- if (false) {
- long[] sysCpu = new long[7];
- if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
- sysCpu, null)) {
- String uiInfo = "UI thread used "
- + (SystemClock.currentThreadTimeMillis() - mUiStart)
- + " ms";
- if (LOGD_ENABLED) {
- Log.d(LOGTAG, uiInfo);
- }
- //The string that gets written to the log
- String performanceString = "It took total "
- + (SystemClock.uptimeMillis() - mStart)
- + " ms clock time to load the page."
- + "\nbrowser process used "
- + (Process.getElapsedCpuTime() - mProcessStart)
- + " ms, user processes used "
- + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
- + " ms, kernel used "
- + (sysCpu[2] - mSystemStart) * 10
- + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
- + " ms and irq took "
- + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
- * 10 + " ms, " + uiInfo;
- if (LOGD_ENABLED) {
- Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
- }
- if (url != null) {
- // strip the url to maintain consistency
- String newUrl = new String(url);
- if (newUrl.startsWith("http://www.")) {
- newUrl = newUrl.substring(11);
- } else if (newUrl.startsWith("http://")) {
- newUrl = newUrl.substring(7);
- } else if (newUrl.startsWith("https://www.")) {
- newUrl = newUrl.substring(12);
- } else if (newUrl.startsWith("https://")) {
- newUrl = newUrl.substring(8);
- }
- if (LOGD_ENABLED) {
- Log.d(LOGTAG, newUrl + " loaded");
- }
- }
- }
- }
-
- if (mInTrace) {
- mInTrace = false;
- Debug.stopMethodTracing();
- }
- }
-
- boolean shouldOverrideUrlLoading(WebView view, String url) {
- if (url.startsWith(SCHEME_WTAI)) {
- // wtai://wp/mc;number
- // number=string(phone-number)
- if (url.startsWith(SCHEME_WTAI_MC)) {
- Intent intent = new Intent(Intent.ACTION_VIEW,
- Uri.parse(WebView.SCHEME_TEL +
- url.substring(SCHEME_WTAI_MC.length())));
- startActivity(intent);
- return true;
- }
- // wtai://wp/sd;dtmf
- // dtmf=string(dialstring)
- if (url.startsWith(SCHEME_WTAI_SD)) {
- // TODO: only send when there is active voice connection
- return false;
- }
- // wtai://wp/ap;number;name
- // number=string(phone-number)
- // name=string
- if (url.startsWith(SCHEME_WTAI_AP)) {
- // TODO
- return false;
- }
- }
-
- // The "about:" schemes are internal to the browser; don't want these to
- // be dispatched to other apps.
- if (url.startsWith("about:")) {
- return false;
- }
-
- Intent intent;
- // perform generic parsing of the URI to turn it into an Intent.
- try {
- intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
- } catch (URISyntaxException ex) {
- Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
- return false;
- }
-
- // check whether the intent can be resolved. If not, we will see
- // whether we can download it from the Market.
- if (getPackageManager().resolveActivity(intent, 0) == null) {
- String packagename = intent.getPackage();
- if (packagename != null) {
- intent = new Intent(Intent.ACTION_VIEW, Uri
- .parse("market://search?q=pname:" + packagename));
- intent.addCategory(Intent.CATEGORY_BROWSABLE);
- startActivity(intent);
- return true;
- } else {
- return false;
- }
- }
-
- // sanitize the Intent, ensuring web pages can not bypass browser
- // security (only access to BROWSABLE activities).
- intent.addCategory(Intent.CATEGORY_BROWSABLE);
- intent.setComponent(null);
- try {
- if (startActivityIfNeeded(intent, -1)) {
- return true;
- }
- } catch (ActivityNotFoundException ex) {
- // ignore the error. If no application can handle the URL,
- // eg about:blank, assume the browser can handle it.
- }
-
- if (mMenuIsDown) {
- openTab(url);
- closeOptionsMenu();
- return true;
- }
- return false;
- }
-
- // -------------------------------------------------------------------------
- // Helper function for WebChromeClient
- // -------------------------------------------------------------------------
-
- void onProgressChanged(WebView view, int newProgress) {
- mFakeTitleBar.setProgress(newProgress);
-
- if (newProgress == 100) {
- // onProgressChanged() may continue to be called after the main
- // frame has finished loading, as any remaining sub frames continue
- // to load. We'll only get called once though with newProgress as
- // 100 when everything is loaded. (onPageFinished is called once
- // when the main frame completes loading regardless of the state of
- // any sub frames so calls to onProgressChanges may continue after
- // onPageFinished has executed)
- if (mInLoad) {
- mInLoad = false;
- updateInLoadMenuItems();
- // If the options menu is open, leave the title bar
- if (!mOptionsMenuOpen || !mIconView) {
- hideFakeTitleBar();
- }
- }
- } else {
- if (!mInLoad) {
- // onPageFinished may have already been called but a subframe is
- // still loading and updating the progress. Reset mInLoad and
- // update the menu items.
- mInLoad = true;
- updateInLoadMenuItems();
- }
- // When the page first begins to load, the Activity may still be
- // paused, in which case showFakeTitleBar will do nothing. Call
- // again as the page continues to load so that it will be shown.
- // (Calling it will the fake title bar is already showing will also
- // do nothing.
- if (!mOptionsMenuOpen || mIconView) {
- // This page has begun to load, so show the title bar
- showFakeTitleBar();
- }
- }
- }
-
- void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
- // if a view already exists then immediately terminate the new one
- if (mCustomView != null) {
- callback.onCustomViewHidden();
- return;
- }
-
- // Add the custom view to its container.
- mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
- mCustomView = view;
- mCustomViewCallback = callback;
- // Save the menu state and set it to empty while the custom
- // view is showing.
- mOldMenuState = mMenuState;
- mMenuState = EMPTY_MENU;
- // Hide the content view.
- mContentView.setVisibility(View.GONE);
- // Finally show the custom view container.
- setStatusBarVisibility(false);
- mCustomViewContainer.setVisibility(View.VISIBLE);
- mCustomViewContainer.bringToFront();
- }
-
- void onHideCustomView() {
- if (mCustomView == null)
- return;
-
- // Hide the custom view.
- mCustomView.setVisibility(View.GONE);
- // Remove the custom view from its container.
- mCustomViewContainer.removeView(mCustomView);
- mCustomView = null;
- // Reset the old menu state.
- mMenuState = mOldMenuState;
- mOldMenuState = EMPTY_MENU;
- mCustomViewContainer.setVisibility(View.GONE);
- mCustomViewCallback.onCustomViewHidden();
- // Show the content view.
- setStatusBarVisibility(true);
- mContentView.setVisibility(View.VISIBLE);
- }
-
- Bitmap getDefaultVideoPoster() {
- if (mDefaultVideoPoster == null) {
- mDefaultVideoPoster = BitmapFactory.decodeResource(
- getResources(), R.drawable.default_video_poster);
- }
- return mDefaultVideoPoster;
- }
-
- View getVideoLoadingProgressView() {
- if (mVideoProgressView == null) {
- LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this);
- mVideoProgressView = inflater.inflate(
- R.layout.video_loading_progress, null);
- }
- return mVideoProgressView;
- }
-
- /*
- * The Object used to inform the WebView of the file to upload.
- */
- private ValueCallback<Uri> mUploadMessage;
-
- void openFileChooser(ValueCallback<Uri> uploadMsg) {
- if (mUploadMessage != null) return;
- mUploadMessage = uploadMsg;
- Intent i = new Intent(Intent.ACTION_GET_CONTENT);
- i.addCategory(Intent.CATEGORY_OPENABLE);
- i.setType("*/*");
- BrowserActivity.this.startActivityForResult(Intent.createChooser(i,
- getString(R.string.choose_upload)), FILE_SELECTED);
- }
-
- // -------------------------------------------------------------------------
- // Implement functions for DownloadListener
- // -------------------------------------------------------------------------
-
- /**
- * Notify the host application a download should be done, or that
- * the data should be streamed if a streaming viewer is available.
- * @param url The full url to the content that should be downloaded
- * @param contentDisposition Content-disposition http header, if
- * present.
- * @param mimetype The mimetype of the content reported by the server
- * @param contentLength The file size reported by the server
- */
- public void onDownloadStart(String url, String userAgent,
- String contentDisposition, String mimetype, long contentLength) {
- // if we're dealing wih A/V content that's not explicitly marked
- // for download, check if it's streamable.
- if (contentDisposition == null
- || !contentDisposition.regionMatches(
- true, 0, "attachment", 0, 10)) {
- // query the package manager to see if there's a registered handler
- // that matches.
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.parse(url), mimetype);
- ResolveInfo info = getPackageManager().resolveActivity(intent,
- PackageManager.MATCH_DEFAULT_ONLY);
- if (info != null) {
- ComponentName myName = getComponentName();
- // If we resolved to ourselves, we don't want to attempt to
- // load the url only to try and download it again.
- if (!myName.getPackageName().equals(
- info.activityInfo.packageName)
- || !myName.getClassName().equals(
- info.activityInfo.name)) {
- // someone (other than us) knows how to handle this mime
- // type with this scheme, don't download.
- try {
- startActivity(intent);
- return;
- } catch (ActivityNotFoundException ex) {
- if (LOGD_ENABLED) {
- Log.d(LOGTAG, "activity not found for " + mimetype
- + " over " + Uri.parse(url).getScheme(),
- ex);
- }
- // Best behavior is to fall back to a download in this
- // case
- }
- }
- }
- }
- onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
- }
-
- // This is to work around the fact that java.net.URI throws Exceptions
- // instead of just encoding URL's properly
- // Helper method for onDownloadStartNoStream
- private static String encodePath(String path) {
- char[] chars = path.toCharArray();
-
- boolean needed = false;
- for (char c : chars) {
- if (c == '[' || c == ']') {
- needed = true;
- break;
- }
- }
- if (needed == false) {
- return path;
- }
-
- StringBuilder sb = new StringBuilder("");
- for (char c : chars) {
- if (c == '[' || c == ']') {
- sb.append('%');
- sb.append(Integer.toHexString(c));
- } else {
- sb.append(c);
- }
- }
-
- return sb.toString();
- }
-
- /**
- * Notify the host application a download should be done, even if there
- * is a streaming viewer available for thise type.
- * @param url The full url to the content that should be downloaded
- * @param contentDisposition Content-disposition http header, if
- * present.
- * @param mimetype The mimetype of the content reported by the server
- * @param contentLength The file size reported by the server
- */
- /*package */ void onDownloadStartNoStream(String url, String userAgent,
- String contentDisposition, String mimetype, long contentLength) {
-
- String filename = URLUtil.guessFileName(url,
- contentDisposition, mimetype);
-
- // Check to see if we have an SDCard
- String status = Environment.getExternalStorageState();
- if (!status.equals(Environment.MEDIA_MOUNTED)) {
- int title;
- String msg;
-
- // Check to see if the SDCard is busy, same as the music app
- if (status.equals(Environment.MEDIA_SHARED)) {
- msg = getString(R.string.download_sdcard_busy_dlg_msg);
- title = R.string.download_sdcard_busy_dlg_title;
- } else {
- msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
- title = R.string.download_no_sdcard_dlg_title;
- }
-
- new AlertDialog.Builder(this)
- .setTitle(title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(msg)
- .setPositiveButton(R.string.ok, null)
- .show();
- return;
- }
-
- // java.net.URI is a lot stricter than KURL so we have to encode some
- // extra characters. Fix for b 2538060 and b 1634719
- WebAddress webAddress;
- try {
- webAddress = new WebAddress(url);
- webAddress.mPath = encodePath(webAddress.mPath);
- } catch (Exception e) {
- // This only happens for very bad urls, we want to chatch the
- // exception here
- Log.e(LOGTAG, "Exception trying to parse url:" + url);
- return;
- }
-
- // XXX: Have to use the old url since the cookies were stored using the
- // old percent-encoded url.
- String cookies = CookieManager.getInstance().getCookie(url);
-
- ContentValues values = new ContentValues();
- values.put(Downloads.Impl.COLUMN_URI, webAddress.toString());
- values.put(Downloads.Impl.COLUMN_COOKIE_DATA, cookies);
- values.put(Downloads.Impl.COLUMN_USER_AGENT, userAgent);
- values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
- getPackageName());
- values.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
- OpenDownloadReceiver.class.getCanonicalName());
- values.put(Downloads.Impl.COLUMN_VISIBILITY,
- Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- values.put(Downloads.Impl.COLUMN_MIME_TYPE, mimetype);
- values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, filename);
- values.put(Downloads.Impl.COLUMN_DESCRIPTION, webAddress.mHost);
- if (contentLength > 0) {
- values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, contentLength);
- }
- if (mimetype == null) {
- // We must have long pressed on a link or image to download it. We
- // are not sure of the mimetype in this case, so do a head request
- new FetchUrlMimeType(this).execute(values);
- } else {
- final Uri contentUri =
- getContentResolver().insert(Downloads.Impl.CONTENT_URI, values);
- }
- Toast.makeText(this, R.string.download_pending, Toast.LENGTH_SHORT)
- .show();
- }
-
- // -------------------------------------------------------------------------
-
- /**
- * Resets the lock icon. This method is called when we start a new load and
- * know the url to be loaded.
- */
- private void resetLockIcon(String url) {
- // Save the lock-icon state (we revert to it if the load gets cancelled)
- mTabControl.getCurrentTab().resetLockIcon(url);
- updateLockIconImage(LOCK_ICON_UNSECURE);
- }
-
- /**
- * Update the lock icon to correspond to our latest state.
- */
- private void updateLockIconToLatest() {
- updateLockIconImage(mTabControl.getCurrentTab().getLockIconType());
- }
-
- /**
- * Updates the lock-icon image in the title-bar.
- */
- private void updateLockIconImage(int lockIconType) {
- Drawable d = null;
- if (lockIconType == LOCK_ICON_SECURE) {
- d = mSecLockIcon;
- } else if (lockIconType == LOCK_ICON_MIXED) {
- d = mMixLockIcon;
- }
- mTitleBar.setLock(d);
- mFakeTitleBar.setLock(d);
- }
-
- /**
- * Displays a page-info dialog.
- * @param tab The tab to show info about
- * @param fromShowSSLCertificateOnError The flag that indicates whether
- * this dialog was opened from the SSL-certificate-on-error dialog or
- * not. This is important, since we need to know whether to return to
- * the parent dialog or simply dismiss.
- */
- private void showPageInfo(final Tab tab,
- final boolean fromShowSSLCertificateOnError) {
- final LayoutInflater factory = LayoutInflater
- .from(this);
-
- final View pageInfoView = factory.inflate(R.layout.page_info, null);
-
- final WebView view = tab.getWebView();
-
- String url = null;
- String title = null;
-
- if (view == null) {
- url = tab.getUrl();
- title = tab.getTitle();
- } else if (view == mTabControl.getCurrentWebView()) {
- // Use the cached title and url if this is the current WebView
- url = mUrl;
- title = mTitle;
- } else {
- url = view.getUrl();
- title = view.getTitle();
- }
-
- if (url == null) {
- url = "";
- }
- if (title == null) {
- title = "";
- }
-
- ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
- ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
-
- mPageInfoView = tab;
- mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError;
-
- AlertDialog.Builder alertDialogBuilder =
- new AlertDialog.Builder(this)
- .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
- .setView(pageInfoView)
- .setPositiveButton(
- R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- mPageInfoDialog = null;
- mPageInfoView = null;
-
- // if we came here from the SSL error dialog
- if (fromShowSSLCertificateOnError) {
- // go back to the SSL error dialog
- showSSLCertificateOnError(
- mSSLCertificateOnErrorView,
- mSSLCertificateOnErrorHandler,
- mSSLCertificateOnErrorError);
- }
- }
- })
- .setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- mPageInfoDialog = null;
- mPageInfoView = null;
-
- // if we came here from the SSL error dialog
- if (fromShowSSLCertificateOnError) {
- // go back to the SSL error dialog
- showSSLCertificateOnError(
- mSSLCertificateOnErrorView,
- mSSLCertificateOnErrorHandler,
- mSSLCertificateOnErrorError);
- }
- }
- });
-
- // if we have a main top-level page SSL certificate set or a certificate
- // error
- if (fromShowSSLCertificateOnError ||
- (view != null && view.getCertificate() != null)) {
- // add a 'View Certificate' button
- alertDialogBuilder.setNeutralButton(
- R.string.view_certificate,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- mPageInfoDialog = null;
- mPageInfoView = null;
-
- // if we came here from the SSL error dialog
- if (fromShowSSLCertificateOnError) {
- // go back to the SSL error dialog
- showSSLCertificateOnError(
- mSSLCertificateOnErrorView,
- mSSLCertificateOnErrorHandler,
- mSSLCertificateOnErrorError);
- } else {
- // otherwise, display the top-most certificate from
- // the chain
- if (view.getCertificate() != null) {
- showSSLCertificate(tab);
- }
- }
- }
- });
- }
-
- mPageInfoDialog = alertDialogBuilder.show();
- }
-
- /**
- * Displays the main top-level page SSL certificate dialog
- * (accessible from the Page-Info dialog).
- * @param tab The tab to show certificate for.
- */
- private void showSSLCertificate(final Tab tab) {
- final View certificateView =
- inflateCertificateView(tab.getWebView().getCertificate());
- if (certificateView == null) {
- return;
- }
-
- LayoutInflater factory = LayoutInflater.from(this);
-
- final LinearLayout placeholder =
- (LinearLayout)certificateView.findViewById(R.id.placeholder);
-
- LinearLayout ll = (LinearLayout) factory.inflate(
- R.layout.ssl_success, placeholder);
- ((TextView)ll.findViewById(R.id.success))
- .setText(R.string.ssl_certificate_is_valid);
-
- mSSLCertificateView = tab;
- mSSLCertificateDialog =
- new AlertDialog.Builder(this)
- .setTitle(R.string.ssl_certificate).setIcon(
- R.drawable.ic_dialog_browser_certificate_secure)
- .setView(certificateView)
- .setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- mSSLCertificateDialog = null;
- mSSLCertificateView = null;
-
- showPageInfo(tab, false);
- }
- })
- .setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- mSSLCertificateDialog = null;
- mSSLCertificateView = null;
-
- showPageInfo(tab, false);
- }
- })
- .show();
- }
-
- /**
- * Displays the SSL error certificate dialog.
- * @param view The target web-view.
- * @param handler The SSL error handler responsible for cancelling the
- * connection that resulted in an SSL error or proceeding per user request.
- * @param error The SSL error object.
- */
- void showSSLCertificateOnError(
- final WebView view, final SslErrorHandler handler, final SslError error) {
-
- final View certificateView =
- inflateCertificateView(error.getCertificate());
- if (certificateView == null) {
- return;
- }
-
- LayoutInflater factory = LayoutInflater.from(this);
-
- final LinearLayout placeholder =
- (LinearLayout)certificateView.findViewById(R.id.placeholder);
-
- if (error.hasError(SslError.SSL_UNTRUSTED)) {
- LinearLayout ll = (LinearLayout)factory
- .inflate(R.layout.ssl_warning, placeholder);
- ((TextView)ll.findViewById(R.id.warning))
- .setText(R.string.ssl_untrusted);
- }
-
- if (error.hasError(SslError.SSL_IDMISMATCH)) {
- LinearLayout ll = (LinearLayout)factory
- .inflate(R.layout.ssl_warning, placeholder);
- ((TextView)ll.findViewById(R.id.warning))
- .setText(R.string.ssl_mismatch);
- }
-
- if (error.hasError(SslError.SSL_EXPIRED)) {
- LinearLayout ll = (LinearLayout)factory
- .inflate(R.layout.ssl_warning, placeholder);
- ((TextView)ll.findViewById(R.id.warning))
- .setText(R.string.ssl_expired);
- }
-
- if (error.hasError(SslError.SSL_NOTYETVALID)) {
- LinearLayout ll = (LinearLayout)factory
- .inflate(R.layout.ssl_warning, placeholder);
- ((TextView)ll.findViewById(R.id.warning))
- .setText(R.string.ssl_not_yet_valid);
- }
-
- mSSLCertificateOnErrorHandler = handler;
- mSSLCertificateOnErrorView = view;
- mSSLCertificateOnErrorError = error;
- mSSLCertificateOnErrorDialog =
- new AlertDialog.Builder(this)
- .setTitle(R.string.ssl_certificate).setIcon(
- R.drawable.ic_dialog_browser_certificate_partially_secure)
- .setView(certificateView)
- .setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- mSSLCertificateOnErrorDialog = null;
- mSSLCertificateOnErrorView = null;
- mSSLCertificateOnErrorHandler = null;
- mSSLCertificateOnErrorError = null;
-
- view.getWebViewClient().onReceivedSslError(
- view, handler, error);
- }
- })
- .setNeutralButton(R.string.page_info_view,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- mSSLCertificateOnErrorDialog = null;
-
- // do not clear the dialog state: we will
- // need to show the dialog again once the
- // user is done exploring the page-info details
-
- showPageInfo(mTabControl.getTabFromView(view),
- true);
- }
- })
- .setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- mSSLCertificateOnErrorDialog = null;
- mSSLCertificateOnErrorView = null;
- mSSLCertificateOnErrorHandler = null;
- mSSLCertificateOnErrorError = null;
-
- view.getWebViewClient().onReceivedSslError(
- view, handler, error);
- }
- })
- .show();
- }
-
- /**
- * Inflates the SSL certificate view (helper method).
- * @param certificate The SSL certificate.
- * @return The resultant certificate view with issued-to, issued-by,
- * issued-on, expires-on, and possibly other fields set.
- * If the input certificate is null, returns null.
- */
- private View inflateCertificateView(SslCertificate certificate) {
- if (certificate == null) {
- return null;
- }
-
- LayoutInflater factory = LayoutInflater.from(this);
-
- View certificateView = factory.inflate(
- R.layout.ssl_certificate, null);
-
- // issued to:
- SslCertificate.DName issuedTo = certificate.getIssuedTo();
- if (issuedTo != null) {
- ((TextView) certificateView.findViewById(R.id.to_common))
- .setText(issuedTo.getCName());
- ((TextView) certificateView.findViewById(R.id.to_org))
- .setText(issuedTo.getOName());
- ((TextView) certificateView.findViewById(R.id.to_org_unit))
- .setText(issuedTo.getUName());
- }
-
- // issued by:
- SslCertificate.DName issuedBy = certificate.getIssuedBy();
- if (issuedBy != null) {
- ((TextView) certificateView.findViewById(R.id.by_common))
- .setText(issuedBy.getCName());
- ((TextView) certificateView.findViewById(R.id.by_org))
- .setText(issuedBy.getOName());
- ((TextView) certificateView.findViewById(R.id.by_org_unit))
- .setText(issuedBy.getUName());
- }
-
- // issued on:
- String issuedOn = formatCertificateDate(
- certificate.getValidNotBeforeDate());
- ((TextView) certificateView.findViewById(R.id.issued_on))
- .setText(issuedOn);
-
- // expires on:
- String expiresOn = formatCertificateDate(
- certificate.getValidNotAfterDate());
- ((TextView) certificateView.findViewById(R.id.expires_on))
- .setText(expiresOn);
-
- return certificateView;
- }
-
- /**
- * Formats the certificate date to a properly localized date string.
- * @return Properly localized version of the certificate date string and
- * the "" if it fails to localize.
- */
- private String formatCertificateDate(Date certificateDate) {
- if (certificateDate == null) {
- return "";
- }
- String formattedDate = DateFormat.getDateFormat(this).format(certificateDate);
- if (formattedDate == null) {
- return "";
- }
- return formattedDate;
- }
-
- /**
- * Displays an http-authentication dialog.
- */
- void showHttpAuthentication(final HttpAuthHandler handler,
- final String host, final String realm, final String title,
- final String name, final String password, int focusId) {
- LayoutInflater factory = LayoutInflater.from(this);
- final View v = factory
- .inflate(R.layout.http_authentication, null);
- if (name != null) {
- ((EditText) v.findViewById(R.id.username_edit)).setText(name);
- }
- if (password != null) {
- ((EditText) v.findViewById(R.id.password_edit)).setText(password);
- }
-
- String titleText = title;
- if (titleText == null) {
- titleText = getText(R.string.sign_in_to).toString().replace(
- "%s1", host).replace("%s2", realm);
- }
-
- mHttpAuthHandler = handler;
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setTitle(titleText)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setView(v)
- .setPositiveButton(R.string.action,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- String nm = ((EditText) v
- .findViewById(R.id.username_edit))
- .getText().toString();
- String pw = ((EditText) v
- .findViewById(R.id.password_edit))
- .getText().toString();
- BrowserActivity.this.setHttpAuthUsernamePassword
- (host, realm, nm, pw);
- handler.proceed(nm, pw);
- mHttpAuthenticationDialog = null;
- mHttpAuthHandler = null;
- }})
- .setNegativeButton(R.string.cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- handler.cancel();
- BrowserActivity.this.resetTitleAndRevertLockIcon();
- mHttpAuthenticationDialog = null;
- mHttpAuthHandler = null;
- }})
- .setOnCancelListener(new DialogInterface.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- handler.cancel();
- BrowserActivity.this.resetTitleAndRevertLockIcon();
- mHttpAuthenticationDialog = null;
- mHttpAuthHandler = null;
- }})
- .create();
- // Make the IME appear when the dialog is displayed if applicable.
- dialog.getWindow().setSoftInputMode(
- WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
- dialog.show();
- if (focusId != 0) {
- dialog.findViewById(focusId).requestFocus();
- } else {
- v.findViewById(R.id.username_edit).requestFocus();
- }
- mHttpAuthenticationDialog = dialog;
- }
-
- public int getProgress() {
- WebView w = mTabControl.getCurrentWebView();
- if (w != null) {
- return w.getProgress();
- } else {
- return 100;
- }
- }
-
- /**
- * Set HTTP authentication password.
- *
- * @param host The host for the password
- * @param realm The realm for the password
- * @param username The username for the password. If it is null, it means
- * password can't be saved.
- * @param password The password
- */
- public void setHttpAuthUsernamePassword(String host, String realm,
- String username,
- String password) {
- WebView w = getTopWindow();
- if (w != null) {
- w.setHttpAuthUsernamePassword(host, realm, username, password);
- }
- }
-
- /**
- * connectivity manager says net has come or gone... inform the user
- * @param up true if net has come up, false if net has gone down
- */
- public void onNetworkToggle(boolean up) {
- if (up == mIsNetworkUp) {
- return;
- } else if (up) {
- mIsNetworkUp = true;
- if (mAlertDialog != null) {
- mAlertDialog.cancel();
- mAlertDialog = null;
- }
- } else {
- mIsNetworkUp = false;
- if (mInLoad) {
- createAndShowNetworkDialog();
- }
- }
- WebView w = mTabControl.getCurrentWebView();
- if (w != null) {
- w.setNetworkAvailable(up);
- }
- }
-
- boolean isNetworkUp() {
- return mIsNetworkUp;
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ super.onActionModeStarted(mode);
+ mController.onActionModeStarted(mode);
}
- // This method shows the network dialog alerting the user that the net is
- // down. It will only show the dialog if mAlertDialog is null.
- private void createAndShowNetworkDialog() {
- if (mAlertDialog == null) {
- mAlertDialog = new AlertDialog.Builder(this)
- .setTitle(R.string.loadSuspendedTitle)
- .setMessage(R.string.loadSuspended)
- .setPositiveButton(R.string.ok, null)
- .show();
- }
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ super.onActionModeFinished(mode);
+ mController.onActionModeFinished(mode);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
- Intent intent) {
- if (getTopWindow() == null) return;
-
- switch (requestCode) {
- case COMBO_PAGE:
- if (resultCode == RESULT_OK && intent != null) {
- String data = intent.getAction();
- Bundle extras = intent.getExtras();
- if (extras != null && extras.getBoolean("new_window", false)) {
- openTab(data);
- } else {
- final Tab currentTab =
- mTabControl.getCurrentTab();
- dismissSubWindow(currentTab);
- if (data != null && data.length() != 0) {
- loadUrl(getTopWindow(), data);
- }
- }
- }
- // Deliberately fall through to PREFERENCES_PAGE, since the
- // same extra may be attached to the COMBO_PAGE
- case PREFERENCES_PAGE:
- if (resultCode == RESULT_OK && intent != null) {
- String action = intent.getStringExtra(Intent.EXTRA_TEXT);
- if (BrowserSettings.PREF_CLEAR_HISTORY.equals(action)) {
- mTabControl.removeParentChildRelationShips();
- }
- }
- break;
- // Choose a file from the file picker.
- case FILE_SELECTED:
- if (null == mUploadMessage) break;
- Uri result = intent == null || resultCode != RESULT_OK ? null
- : intent.getData();
- mUploadMessage.onReceiveValue(result);
- mUploadMessage = null;
- break;
- default:
- break;
- }
- getTopWindow().requestFocus();
- }
-
- /*
- * This method is called as a result of the user selecting the options
- * menu to see the download window. It shows the download window on top of
- * the current window.
- */
- private void viewDownloads() {
- Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
- startActivity(intent);
- }
-
- /**
- * Open the Go page.
- * @param startWithHistory If true, open starting on the history tab.
- * Otherwise, start with the bookmarks tab.
- */
- /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
- WebView current = mTabControl.getCurrentWebView();
- if (current == null) {
- return;
- }
- Intent intent = new Intent(this,
- CombinedBookmarkHistoryActivity.class);
- String title = current.getTitle();
- String url = current.getUrl();
- Bitmap thumbnail = createScreenshot(current);
-
- // Just in case the user opens bookmarks before a page finishes loading
- // so the current history item, and therefore the page, is null.
- if (null == url) {
- url = mLastEnteredUrl;
- // This can happen.
- if (null == url) {
- url = mSettings.getHomePage();
- }
- }
- // In case the web page has not yet received its associated title.
- if (title == null) {
- title = url;
- }
- intent.putExtra("title", title);
- intent.putExtra("url", url);
- intent.putExtra("thumbnail", thumbnail);
- // Disable opening in a new window if we have maxed out the windows
- intent.putExtra("disable_new_window", !mTabControl.canCreateNewTab());
- intent.putExtra("touch_icon_url", current.getTouchIconUrl());
- if (startWithHistory) {
- intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
- CombinedBookmarkHistoryActivity.HISTORY_TAB);
- }
- startActivityForResult(intent, COMBO_PAGE);
- }
-
- // Called when loading from context menu or LOAD_URL message
- private void loadUrlFromContext(WebView view, String url) {
- // In case the user enters nothing.
- if (url != null && url.length() != 0 && view != null) {
- url = smartUrlFilter(url);
- if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) {
- loadUrl(view, url);
- }
- }
- }
-
- /**
- * Load the URL into the given WebView and update the title bar
- * to reflect the new load. Call this instead of WebView.loadUrl
- * directly.
- * @param view The WebView used to load url.
- * @param url The URL to load.
- */
- private void loadUrl(WebView view, String url) {
- updateTitleBarForNewLoad(view, url);
- view.loadUrl(url);
- }
-
- /**
- * Load UrlData into a Tab and update the title bar to reflect the new
- * load. Call this instead of UrlData.loadIn directly.
- * @param t The Tab used to load.
- * @param data The UrlData being loaded.
- */
- private void loadUrlDataIn(Tab t, UrlData data) {
- updateTitleBarForNewLoad(t.getWebView(), data.mUrl);
- data.loadIn(t);
- }
-
- /**
- * If the WebView is the top window, update the title bar to reflect
- * loading the new URL. i.e. set its text, clear the favicon (which
- * will be set once the page begins loading), and set the progress to
- * INITIAL_PROGRESS to show that the page has begun to load. Called
- * by loadUrl and loadUrlDataIn.
- * @param view The WebView that is starting a load.
- * @param url The URL that is being loaded.
- */
- private void updateTitleBarForNewLoad(WebView view, String url) {
- if (view == getTopWindow()) {
- setUrlTitle(url, null);
- setFavicon(null);
- onProgressChanged(view, INITIAL_PROGRESS);
- }
- }
-
- private String smartUrlFilter(Uri inUri) {
- if (inUri != null) {
- return smartUrlFilter(inUri.toString());
- }
- return null;
- }
-
- protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
- "(?i)" + // switch on case insensitive matching
- "(" + // begin group for schema
- "(?:http|https|file):\\/\\/" +
- "|(?:inline|data|about|content|javascript):" +
- ")" +
- "(.*)" );
-
- /**
- * Attempts to determine whether user input is a URL or search
- * terms. Anything with a space is passed to search.
- *
- * Converts to lowercase any mistakenly uppercased schema (i.e.,
- * "Http://" converts to "http://"
- *
- * @return Original or modified URL
- *
- */
- String smartUrlFilter(String url) {
-
- String inUrl = url.trim();
- boolean hasSpace = inUrl.indexOf(' ') != -1;
-
- Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
- if (matcher.matches()) {
- // force scheme to lowercase
- String scheme = matcher.group(1);
- String lcScheme = scheme.toLowerCase();
- if (!lcScheme.equals(scheme)) {
- inUrl = lcScheme + matcher.group(2);
- }
- if (hasSpace) {
- inUrl = inUrl.replace(" ", "%20");
- }
- return inUrl;
- }
- 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);
- }
-
- /* package */ void setShouldShowErrorConsole(boolean flag) {
- if (flag == mShouldShowErrorConsole) {
- // 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 = t.getErrorConsole(true);
-
- if (flag) {
- // Setting the show state of the console will cause it's the layout to be inflated.
- if (errorConsole.numberOfErrors() > 0) {
- errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
- } else {
- errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
- }
-
- // Now we can add it to the main view.
- mErrorConsoleContainer.addView(errorConsole,
- new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- } else {
- mErrorConsoleContainer.removeView(errorConsole);
- }
-
- }
-
- boolean shouldShowErrorConsole() {
- return mShouldShowErrorConsole;
- }
-
- private void setStatusBarVisibility(boolean visible) {
- int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
- getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN);
- }
-
-
- private void sendNetworkType(String type, String subtype) {
- WebView w = mTabControl.getCurrentWebView();
- if (w != null) {
- w.setNetworkType(type, subtype);
- }
- }
-
- private void packageChanged(String packageName, boolean wasAdded) {
- WebView w = mTabControl.getCurrentWebView();
- if (w == null) {
- return;
- }
-
- if (wasAdded) {
- w.addPackageName(packageName);
- } else {
- w.removePackageName(packageName);
- }
- }
-
- private void addPackageNames(Set<String> packageNames) {
- WebView w = mTabControl.getCurrentWebView();
- if (w == null) {
- return;
- }
-
- w.addPackageNames(packageNames);
+ Intent intent) {
+ mController.onActivityResult(requestCode, resultCode, intent);
}
- private void getInstalledPackages() {
- AsyncTask<Void, Void, Set<String> > task =
- new AsyncTask<Void, Void, Set<String> >() {
- protected Set<String> doInBackground(Void... unused) {
- Set<String> installedPackages = new HashSet<String>();
- PackageManager pm = BrowserActivity.this.getPackageManager();
- if (pm != null) {
- List<PackageInfo> packages = pm.getInstalledPackages(0);
- for (PackageInfo p : packages) {
- if (BrowserActivity.this.sGoogleApps.contains(p.packageName)) {
- installedPackages.add(p.packageName);
- }
- }
- }
-
- return installedPackages;
- }
-
- // Executes on the UI thread
- protected void onPostExecute(Set<String> installedPackages) {
- addPackageNames(installedPackages);
- }
- };
- task.execute();
- }
-
- final static int LOCK_ICON_UNSECURE = 0;
- final static int LOCK_ICON_SECURE = 1;
- final static int LOCK_ICON_MIXED = 2;
-
- private BrowserSettings mSettings;
- private TabControl mTabControl;
- private ContentResolver mResolver;
- private FrameLayout mContentView;
- private View mCustomView;
- private FrameLayout mCustomViewContainer;
- private WebChromeClient.CustomViewCallback mCustomViewCallback;
-
- // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
- // view, we should rewrite this.
- private int mCurrentMenuState = 0;
- private int mMenuState = R.id.MAIN_MENU;
- private int mOldMenuState = EMPTY_MENU;
- private static final int EMPTY_MENU = -1;
- 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;
-
- private boolean mInLoad;
- private boolean mIsNetworkUp;
- private boolean mDidStopLoad;
-
- /* package */ boolean mActivityInPause = true;
-
- private boolean mMenuIsDown;
-
- private static boolean mInTrace;
-
- // Performance probe
- private static final int[] SYSTEM_CPU_FORMAT = new int[] {
- Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
- Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
- Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
- Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
- Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
- Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
- Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
- Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
- };
-
- private long mStart;
- private long mProcessStart;
- private long mUserStart;
- private long mSystemStart;
- private long mIdleStart;
- private long mIrqStart;
-
- private long mUiStart;
-
- private Drawable mMixLockIcon;
- private Drawable mSecLockIcon;
-
- /* hold a ref so we can auto-cancel if necessary */
- private AlertDialog mAlertDialog;
-
- // The up-to-date URL and title (these can be different from those stored
- // in WebView, since it takes some time for the information in WebView to
- // get updated)
- private String mUrl;
- private String mTitle;
-
- // As PageInfo has different style for landscape / portrait, we have
- // to re-open it when configuration changed
- private AlertDialog mPageInfoDialog;
- private Tab mPageInfoView;
- // If the Page-Info dialog is launched from the SSL-certificate-on-error
- // dialog, we should not just dismiss it, but should get back to the
- // SSL-certificate-on-error dialog. This flag is used to store this state
- private boolean mPageInfoFromShowSSLCertificateOnError;
-
- // as SSLCertificateOnError has different style for landscape / portrait,
- // we have to re-open it when configuration changed
- private AlertDialog mSSLCertificateOnErrorDialog;
- private WebView mSSLCertificateOnErrorView;
- private SslErrorHandler mSSLCertificateOnErrorHandler;
- private SslError mSSLCertificateOnErrorError;
-
- // as SSLCertificate has different style for landscape / portrait, we
- // have to re-open it when configuration changed
- private AlertDialog mSSLCertificateDialog;
- private Tab mSSLCertificateView;
-
- // as HttpAuthentication has different style for landscape / portrait, we
- // have to re-open it when configuration changed
- private AlertDialog mHttpAuthenticationDialog;
- private HttpAuthHandler mHttpAuthHandler;
-
- /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
- new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
- /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
- new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- Gravity.CENTER);
- // Google search
- final static String QuickSearch_G = "http://www.google.com/m?q=%s";
-
- final static String QUERY_PLACE_HOLDER = "%s";
-
- // "source" parameter for Google search through search key
- final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
- // "source" parameter for Google search through goto menu
- final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
- // "source" parameter for Google search through simplily type
- final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
- // "source" parameter for Google search suggested by the browser
- final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
- // "source" parameter for Google search from unknown source
- final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
-
- private final static String LOGTAG = "browser";
-
- private String mLastEnteredUrl;
-
- private PowerManager.WakeLock mWakeLock;
- private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
-
- private Toast mStopToast;
-
- private TitleBar mTitleBar;
-
- private LinearLayout mErrorConsoleContainer = null;
- private boolean mShouldShowErrorConsole = false;
-
- // As the ids are dynamically created, we can't guarantee that they will
- // be in sequence, so this static array maps ids to a window number.
- final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
- { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
- R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
- R.id.window_seven_menu_id, R.id.window_eight_menu_id };
-
- // monitor platform changes
- private IntentFilter mNetworkStateChangedFilter;
- private BroadcastReceiver mNetworkStateIntentReceiver;
-
- private BroadcastReceiver mPackageInstallationReceiver;
-
- private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins;
-
- // activity requestCode
- final static int COMBO_PAGE = 1;
- final static int PREFERENCES_PAGE = 3;
- final static int FILE_SELECTED = 4;
-
- // the default <video> poster
- private Bitmap mDefaultVideoPoster;
- // the video progress view
- private View mVideoProgressView;
-
- // The Google packages we monitor for the navigator.isApplicationInstalled()
- // API. Add as needed.
- private static Set<String> sGoogleApps;
- static {
- sGoogleApps = new HashSet<String>();
- sGoogleApps.add("com.google.android.youtube");
- }
-
- /**
- * A UrlData class to abstract how the content will be set to WebView.
- * This base class uses loadUrl to show the content.
- */
- /* package */ static class UrlData {
- final String mUrl;
- final Map<String, String> mHeaders;
- final Intent mVoiceIntent;
-
- UrlData(String url) {
- this.mUrl = url;
- this.mHeaders = null;
- this.mVoiceIntent = null;
- }
-
- UrlData(String url, Map<String, String> headers, Intent intent) {
- this.mUrl = url;
- this.mHeaders = headers;
- if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
- .equals(intent.getAction())) {
- this.mVoiceIntent = intent;
- } else {
- this.mVoiceIntent = null;
- }
- }
-
- boolean isEmpty() {
- return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0);
- }
-
- /**
- * Load this UrlData into the given Tab. Use loadUrlDataIn to update
- * the title bar as well.
- */
- public void loadIn(Tab t) {
- if (mVoiceIntent != null) {
- t.activateVoiceSearchMode(mVoiceIntent);
- } else {
- t.getWebView().loadUrl(mUrl, mHeaders);
- }
- }
- };
- /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
}
diff --git a/src/com/android/browser/BrowserBackupAgent.java b/src/com/android/browser/BrowserBackupAgent.java
index c968ce5d7..9c5d65b4c 100644
--- a/src/com/android/browser/BrowserBackupAgent.java
+++ b/src/com/android/browser/BrowserBackupAgent.java
@@ -166,8 +166,11 @@ public class BrowserBackupAgent extends BackupAgent {
if (DEBUG) Log.v(TAG, "Did not see url: " + mark.url);
// Right now we do not reconstruct the db entry in its
// entirety; we just add a new bookmark with the same data
- Bookmarks.addBookmark(null, getContentResolver(),
- mark.url, mark.title, null, false);
+ // FIXME: This file needs to be reworked
+ // anyway For now, add the bookmark at
+ // the root level.
+ Bookmarks.addBookmark(this, false,
+ mark.url, mark.title, null, false, 0);
nUnique++;
} else {
if (DEBUG) Log.v(TAG, "Skipping extant url: " + mark.url);
diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java
index 241b33b75..f587f0189 100644
--- a/src/com/android/browser/BrowserBookmarksAdapter.java
+++ b/src/com/android/browser/BrowserBookmarksAdapter.java
@@ -16,567 +16,106 @@
package com.android.browser;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.ContentObserver;
+import android.content.Context;
import android.database.Cursor;
-import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Browser;
-import android.provider.Browser.BookmarkColumns;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.webkit.WebIconDatabase;
-import android.webkit.WebView;
-import android.widget.BaseAdapter;
+import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.TextView;
-import java.io.ByteArrayOutputStream;
-
-class BrowserBookmarksAdapter extends BaseAdapter {
-
- private String mCurrentPage;
- private String mCurrentTitle;
- private Bitmap mCurrentThumbnail;
- private Cursor mCursor;
- private int mCount;
- private BrowserBookmarksPage mBookmarksPage;
- private ContentResolver mContentResolver;
- private boolean mDataValid;
- private BookmarkViewMode mViewMode;
- private boolean mMostVisited;
- private boolean mNeedsOffset;
- private int mExtraOffset;
+class BrowserBookmarksAdapter extends CursorAdapter {
+ LayoutInflater mInflater;
+ int mCurrentView;
/**
* Create a new BrowserBookmarksAdapter.
- * @param b BrowserBookmarksPage that instantiated this.
- * Necessary so it will adjust its focus
- * appropriately after a search.
*/
- public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage,
- String curTitle, Bitmap curThumbnail, boolean createShortcut,
- boolean mostVisited) {
- mNeedsOffset = !(createShortcut || mostVisited);
- mMostVisited = mostVisited;
- mExtraOffset = mNeedsOffset ? 1 : 0;
- mBookmarksPage = b;
- mCurrentPage = b.getResources().getString(R.string.current_page)
- + curPage;
- mCurrentTitle = curTitle;
- mCurrentThumbnail = curThumbnail;
- mContentResolver = b.getContentResolver();
- mViewMode = BookmarkViewMode.LIST;
+ public BrowserBookmarksAdapter(Context context, int defaultView) {
+ // Make sure to tell the CursorAdapter to avoid the observer and auto-requery
+ // since the Loader will do that for us.
+ super(context, null);
+ mInflater = LayoutInflater.from(context);
+ selectView(defaultView);
+ }
- String whereClause;
- // FIXME: Should have a default sort order that the user selects.
- String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
- if (mostVisited) {
- whereClause = Browser.BookmarkColumns.VISITS + " != 0";
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ if (mCurrentView == BrowserBookmarksPage.VIEW_LIST) {
+ bindListView(view, context, cursor);
} else {
- whereClause = Browser.BookmarkColumns.BOOKMARK + " = 1";
+ bindGridView(view, context, cursor);
}
- mCursor = b.managedQuery(Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION, whereClause, null, orderBy);
- mCursor.registerContentObserver(new ChangeObserver());
- mCursor.registerDataSetObserver(new MyDataSetObserver());
-
- mDataValid = true;
- notifyDataSetChanged();
-
- mCount = mCursor.getCount() + mExtraOffset;
- }
-
- /**
- * Return a hashmap with one row's Title, Url, and favicon.
- * @param position Position in the list.
- * @return Bundle Stores title, url of row position, favicon, and id
- * for the url. Return a blank map if position is out of
- * range.
- */
- public Bundle getRow(int position) {
- Bundle map = new Bundle();
- if (position < mExtraOffset || position >= mCount) {
- return map;
- }
- mCursor.moveToPosition(position- mExtraOffset);
- String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- map.putString(Browser.BookmarkColumns.TITLE,
- mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
- map.putString(Browser.BookmarkColumns.URL, url);
- byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
- if (data != null) {
- map.putParcelable(Browser.BookmarkColumns.FAVICON,
- BitmapFactory.decodeByteArray(data, 0, data.length));
- }
- map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
- return map;
}
- /**
- * Update a row in the database with new information.
- * Requeries the database if the information has changed.
- * @param map Bundle storing id, title and url of new information
- */
- public void updateRow(Bundle map) {
+ void bindGridView(View view, Context context, Cursor cursor) {
+ ImageView thumb = (ImageView) view.findViewById(R.id.thumb);
+ TextView tv = (TextView) view.findViewById(R.id.label);
- // Find the record
- int id = map.getInt("id");
- int position = -1;
- for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
- if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) {
- position = mCursor.getPosition();
- break;
+ tv.setText(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
+ if (cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0) {
+ // folder
+ thumb.setImageResource(R.drawable.ic_folder);
+ } else {
+ byte[] thumbData = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_THUMBNAIL);
+ Bitmap thumbBitmap = null;
+ if (thumbData != null) {
+ thumbBitmap = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length);
}
- }
- if (position < 0) {
- return;
- }
- mCursor.moveToPosition(position);
- ContentValues values = new ContentValues();
- String title = map.getString(Browser.BookmarkColumns.TITLE);
- if (!title.equals(mCursor
- .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) {
- values.put(Browser.BookmarkColumns.TITLE, title);
- }
- String url = map.getString(Browser.BookmarkColumns.URL);
- if (!url.equals(mCursor.
- getString(Browser.HISTORY_PROJECTION_URL_INDEX))) {
- values.put(Browser.BookmarkColumns.URL, url);
- }
-
- if (map.getBoolean("invalidateThumbnail") == true) {
- values.put(Browser.BookmarkColumns.THUMBNAIL, new byte[0]);
- }
- if (values.size() > 0
- && mContentResolver.update(Browser.BOOKMARKS_URI, values,
- "_id = " + id, null) != -1) {
- refreshList();
- }
- }
-
- /**
- * Delete a row from the database. Requeries the database.
- * Does nothing if the provided position is out of range.
- * @param position Position in the list.
- */
- public void deleteRow(int position) {
- if (position < mExtraOffset || position >= getCount()) {
- return;
- }
- mCursor.moveToPosition(position- mExtraOffset);
- String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
- Bookmarks.removeFromBookmarks(null, mContentResolver, url, title);
- refreshList();
- }
-
- /**
- * Delete all bookmarks from the db. Requeries the database.
- * All bookmarks with become visited URLs or if never visited
- * are removed
- */
- public void deleteAllRows() {
- StringBuilder deleteIds = null;
- StringBuilder convertIds = null;
-
- for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
- String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- WebIconDatabase.getInstance().releaseIconForPageUrl(url);
- int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX);
- int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
- if (0 == numVisits) {
- if (deleteIds == null) {
- deleteIds = new StringBuilder();
- deleteIds.append("( ");
- } else {
- deleteIds.append(" OR ( ");
- }
- deleteIds.append(BookmarkColumns._ID);
- deleteIds.append(" = ");
- deleteIds.append(id);
- deleteIds.append(" )");
+ if (thumbBitmap == null) {
+ thumb.setImageResource(R.drawable.browser_thumbnail);
} else {
- // It is no longer a bookmark, but it is still a visited site.
- if (convertIds == null) {
- convertIds = new StringBuilder();
- convertIds.append("( ");
- } else {
- convertIds.append(" OR ( ");
- }
- convertIds.append(BookmarkColumns._ID);
- convertIds.append(" = ");
- convertIds.append(id);
- convertIds.append(" )");
+ thumb.setImageBitmap(thumbBitmap);
}
}
-
- if (deleteIds != null) {
- mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(),
- null);
- }
- if (convertIds != null) {
- ContentValues values = new ContentValues();
- values.put(Browser.BookmarkColumns.BOOKMARK, 0);
- mContentResolver.update(Browser.BOOKMARKS_URI, values,
- convertIds.toString(), null);
- }
- refreshList();
- }
-
- /**
- * Refresh list to recognize a change in the database.
- */
- public void refreshList() {
- mCursor.requery();
- mCount = mCursor.getCount() + mExtraOffset;
- notifyDataSetChanged();
- }
-
- /**
- * Update the bookmark's favicon. This is a convenience method for updating
- * a bookmark favicon for the originalUrl and url of the passed in WebView.
- * @param cr The ContentResolver to use.
- * @param originalUrl The original url before any redirects.
- * @param url The current url.
- * @param favicon The favicon bitmap to write to the db.
- */
- /* package */ static void updateBookmarkFavicon(final ContentResolver cr,
- final String originalUrl, final String url, final Bitmap favicon) {
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... unused) {
- final Cursor c =
- queryBookmarksForUrl(cr, originalUrl, url, true);
- if (c == null) {
- return null;
- }
- if (c.moveToFirst()) {
- ContentValues values = new ContentValues();
- final ByteArrayOutputStream os =
- new ByteArrayOutputStream();
- favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
- values.put(Browser.BookmarkColumns.FAVICON,
- os.toByteArray());
- do {
- cr.update(ContentUris.withAppendedId(
- Browser.BOOKMARKS_URI, c.getInt(0)),
- values, null, null);
- } while (c.moveToNext());
- }
- c.close();
- return null;
- }
- }.execute();
- }
-
- /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
- String originalUrl, String url, boolean onlyBookmarks) {
- if (cr == null || url == null) {
- return null;
- }
-
- // If originalUrl is null, just set it to url.
- if (originalUrl == null) {
- originalUrl = url;
- }
-
- // Look for both the original url and the actual url. This takes in to
- // account redirects.
- String originalUrlNoQuery = removeQuery(originalUrl);
- String urlNoQuery = removeQuery(url);
- originalUrl = originalUrlNoQuery + '?';
- url = urlNoQuery + '?';
-
- // Use NoQuery to search for the base url (i.e. if the url is
- // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com)
- // Use url to match the base url with other queries (i.e. if the url is
- // http://www.google.com/m, search for
- // http://www.google.com/m?some_query)
- final String[] selArgs = new String[] {
- originalUrlNoQuery, urlNoQuery, originalUrl, url };
- String where = BookmarkColumns.URL + " == ? OR "
- + BookmarkColumns.URL + " == ? OR "
- + BookmarkColumns.URL + " LIKE ? || '%' OR "
- + BookmarkColumns.URL + " LIKE ? || '%'";
- if (onlyBookmarks) {
- where = "(" + where + ") AND " + BookmarkColumns.BOOKMARK + " == 1";
- }
- final String[] projection =
- new String[] { Browser.BookmarkColumns._ID };
- return cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs,
- null);
- }
-
- // Strip the query from the given url.
- private static String removeQuery(String url) {
- if (url == null) {
- return null;
- }
- int query = url.indexOf('?');
- String noQuery = url;
- if (query != -1) {
- noQuery = url.substring(0, query);
- }
- return noQuery;
- }
-
- /**
- * How many items should be displayed in the list.
- * @return Count of items.
- */
- public int getCount() {
- if (mDataValid) {
- return mCount;
- } else {
- return 0;
- }
- }
-
- public boolean areAllItemsEnabled() {
- return true;
- }
-
- public boolean isEnabled(int position) {
- return true;
- }
-
- /**
- * Get the data associated with the specified position in the list.
- * @param position Index of the item whose data we want.
- * @return The data at the specified position.
- */
- public Object getItem(int position) {
- return null;
}
- /**
- * Get the row id associated with the specified position in the list.
- * @param position Index of the item whose row id we want.
- * @return The id of the item at the specified position.
- */
- public long getItemId(int position) {
- return position;
- }
+ void bindListView(View view, Context context, Cursor cursor) {
+ ImageView favicon = (ImageView) view.findViewById(R.id.favicon);
+ TextView tv = (TextView) view.findViewById(R.id.label);
- /* package */ void switchViewMode(BookmarkViewMode viewMode) {
- mViewMode = viewMode;
- }
-
- /* package */ void populateBookmarkItem(BookmarkItem b, int position) {
- mCursor.moveToPosition(position - mExtraOffset);
- String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- b.setUrl(url);
- b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
- byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
- Bitmap bitmap = null;
- if (data == null) {
- bitmap = CombinedBookmarkHistoryActivity.getIconListenerSet()
- .getFavicon(url);
+ tv.setText(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
+ if (cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0) {
+ // folder
+ favicon.setImageResource(R.drawable.ic_folder);
} else {
- bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- }
- b.setFavicon(bitmap);
- }
-
- /**
- * Get a View that displays the data at the specified position
- * in the list.
- * @param position Index of the item whose view we want.
- * @return A View corresponding to the data at the specified position.
- */
- public View getView(int position, View convertView, ViewGroup parent) {
- if (!mDataValid) {
- throw new IllegalStateException(
- "this should only be called when the cursor is valid");
- }
- if (position < 0 || position > mCount) {
- throw new AssertionError(
- "BrowserBookmarksAdapter tried to get a view out of range");
- }
- if (mViewMode == BookmarkViewMode.GRID) {
- if (convertView == null || convertView instanceof AddNewBookmark
- || convertView instanceof BookmarkItem) {
- LayoutInflater factory = LayoutInflater.from(mBookmarksPage);
- convertView
- = factory.inflate(R.layout.bookmark_thumbnail, null);
+ byte[] faviconData = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
+ Bitmap faviconBitmap = null;
+ if (faviconData != null) {
+ faviconBitmap = BitmapFactory.decodeByteArray(faviconData, 0, faviconData.length);
}
- View holder = convertView.findViewById(R.id.holder);
- ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb);
- TextView tv = (TextView) convertView.findViewById(R.id.label);
- if (0 == position && mNeedsOffset) {
- // This is to create a bookmark for the current page.
- holder.setVisibility(View.VISIBLE);
- tv.setText(mCurrentTitle);
-
- if (mCurrentThumbnail != null) {
- thumb.setImageBitmap(mCurrentThumbnail);
- } else {
- thumb.setImageResource(
- R.drawable.browser_thumbnail);
- }
- return convertView;
- }
- holder.setVisibility(View.GONE);
- mCursor.moveToPosition(position - mExtraOffset);
- tv.setText(mCursor.getString(
- Browser.HISTORY_PROJECTION_TITLE_INDEX));
- Bitmap thumbnail = getScreenshot(position);
- if (thumbnail == null) {
- thumb.setImageResource(R.drawable.browser_thumbnail);
+ if (faviconBitmap == null) {
+ favicon.setImageResource(R.drawable.app_web_browser_sm);
} else {
- thumb.setImageBitmap(thumbnail);
- }
-
- return convertView;
-
- }
- if (position == 0 && mNeedsOffset) {
- AddNewBookmark b;
- if (convertView instanceof AddNewBookmark) {
- b = (AddNewBookmark) convertView;
- } else {
- b = new AddNewBookmark(mBookmarksPage);
- }
- b.setUrl(mCurrentPage);
- return b;
- }
- if (mMostVisited) {
- if (convertView == null || !(convertView instanceof HistoryItem)) {
- convertView = new HistoryItem(mBookmarksPage);
- }
- } else {
- if (convertView == null || !(convertView instanceof BookmarkItem)) {
- convertView = new BookmarkItem(mBookmarksPage);
+ favicon.setImageBitmap(faviconBitmap);
}
}
- bind((BookmarkItem) convertView, position);
- if (mMostVisited) {
- ((HistoryItem) convertView).setIsBookmark(
- getIsBookmark(position));
- }
- return convertView;
- }
-
- /**
- * Return the title for this item in the list.
- */
- public String getTitle(int position) {
- return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position);
}
- /**
- * Return the Url for this item in the list.
- */
- public String getUrl(int position) {
- return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position);
- }
-
- /**
- * Return the screenshot for this item in the list.
- */
- public Bitmap getScreenshot(int position) {
- return getBitmap(Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX, position);
- }
-
- /**
- * Return the favicon for this item in the list.
- */
- public Bitmap getFavicon(int position) {
- return getBitmap(Browser.HISTORY_PROJECTION_FAVICON_INDEX, position);
- }
-
- public Bitmap getTouchIcon(int position) {
- return getBitmap(Browser.HISTORY_PROJECTION_TOUCH_ICON_INDEX, position);
- }
-
- private Bitmap getBitmap(int cursorIndex, int position) {
- if (position < mExtraOffset || position > mCount) {
- return null;
- }
- mCursor.moveToPosition(position - mExtraOffset);
- byte[] data = mCursor.getBlob(cursorIndex);
- if (data == null) {
- return null;
- }
- return BitmapFactory.decodeByteArray(data, 0, data.length);
- }
-
- /**
- * Return whether or not this item represents a bookmarked site.
- */
- public boolean getIsBookmark(int position) {
- if (position < mExtraOffset || position > mCount) {
- return false;
- }
- mCursor.moveToPosition(position - mExtraOffset);
- return (1 == mCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
- }
-
- /**
- * Private helper function to return the title or url.
- */
- private String getString(int cursorIndex, int position) {
- if (position < mExtraOffset || position > mCount) {
- return "";
- }
- mCursor.moveToPosition(position- mExtraOffset);
- return mCursor.getString(cursorIndex);
- }
-
- private void bind(BookmarkItem b, int position) {
- mCursor.moveToPosition(position- mExtraOffset);
-
- b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
- String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- b.setUrl(url);
- byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
- if (data != null) {
- b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ if (mCurrentView == BrowserBookmarksPage.VIEW_LIST) {
+ return mInflater.inflate(R.layout.bookmark_list, parent, false);
} else {
- b.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet()
- .getFavicon(url));
+ return mInflater.inflate(R.layout.bookmark_thumbnail, parent, false);
}
}
- private class ChangeObserver extends ContentObserver {
- public ChangeObserver() {
- super(new Handler(Looper.getMainLooper()));
- }
-
- @Override
- public boolean deliverSelfNotifications() {
- return true;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- refreshList();
+ public void selectView(int view) {
+ if (view != BrowserBookmarksPage.VIEW_LIST
+ && view != BrowserBookmarksPage.VIEW_THUMBNAILS) {
+ throw new IllegalArgumentException("Unknown view specified: " + view);
}
+ mCurrentView = view;
}
-
- private class MyDataSetObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- mDataValid = true;
- notifyDataSetChanged();
- }
- @Override
- public void onInvalidated() {
- mDataValid = false;
- notifyDataSetInvalidated();
- }
+ @Override
+ public Cursor getItem(int position) {
+ return (Cursor) super.getItem(position);
}
}
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index dd01009f8..4887f3f49 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -18,89 +18,260 @@ package com.android.browser;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.Loader;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ServiceManager;
-import android.provider.Browser;
-import android.text.IClipboard;
-import android.util.Log;
+import android.preference.PreferenceManager;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Accounts;
+import android.text.TextUtils;
import android.view.ContextMenu;
-import android.view.Menu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.view.ContextMenu.ContextMenuInfo;
import android.webkit.WebIconDatabase.IconListener;
+import android.widget.Adapter;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.GridView;
import android.widget.ListView;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnMenuItemClickListener;
+import android.widget.TextView;
import android.widget.Toast;
-/*package*/ enum BookmarkViewMode { NONE, GRID, LIST }
+interface BookmarksPageCallbacks {
+ // Return true if handled
+ boolean onBookmarkSelected(Cursor c, boolean isFolder);
+ // Return true if handled
+ boolean onOpenInNewWindow(Cursor c);
+}
+
/**
* View showing the user's bookmarks in the browser.
*/
-public class BrowserBookmarksPage extends Activity implements
- View.OnCreateContextMenuListener {
-
- private BookmarkViewMode mViewMode = BookmarkViewMode.NONE;
- private GridView mGridPage;
- private ListView mVerticalList;
- private BrowserBookmarksAdapter mBookmarksAdapter;
- private static final int BOOKMARKS_SAVE = 1;
- private boolean mDisableNewWindow;
- private BookmarkItem mContextHeader;
- private AddNewBookmark mAddHeader;
- private boolean mCanceled = false;
- private boolean mCreateShortcut;
- private boolean mMostVisited;
- private View mEmptyView;
- private int mIconSize;
- // XXX: There is no public string defining this intent so if Home changes
- // the value, we have to update this string.
- private static final String INSTALL_SHORTCUT =
- "com.android.launcher.action.INSTALL_SHORTCUT";
-
- private final static String LOGTAG = "browser";
- private final static String PREF_BOOKMARK_VIEW_MODE = "pref_bookmark_view_mode";
- private final static String PREF_MOST_VISITED_VIEW_MODE = "pref_most_visited_view_mode";
+public class BrowserBookmarksPage extends Fragment implements View.OnCreateContextMenuListener,
+ LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, IconListener,
+ OnItemSelectedListener, BreadCrumbView.Controller, OnClickListener, OnMenuItemClickListener {
+
+ static final String LOGTAG = "browser";
+
+ static final int LOADER_BOOKMARKS = 1;
+ static final int LOADER_ACCOUNTS_THEN_BOOKMARKS = 2;
+
+ static final String EXTRA_DISABLE_WINDOW = "disable_new_window";
+
+ static final String ACCOUNT_NAME_UNSYNCED = "Unsynced";
+
+ public static final String PREF_ACCOUNT_TYPE = "acct_type";
+ public static final String PREF_ACCOUNT_NAME = "acct_name";
+
+ static final String DEFAULT_ACCOUNT = "local";
+ static final int VIEW_THUMBNAILS = 1;
+ static final int VIEW_LIST = 2;
+ static final String PREF_SELECTED_VIEW = "bookmarks_view";
+
+ BookmarksPageCallbacks mCallbacks;
+ GridView mGrid;
+ ListView mList;
+ BrowserBookmarksAdapter mAdapter;
+ boolean mDisableNewWindow;
+ boolean mCanceled = false;
+ boolean mEnableContextMenu = true;
+ boolean mShowRootFolder = false;
+ View mEmptyView;
+ int mCurrentView;
+ View mHeader;
+ View mRootFolderView;
+ ViewGroup mHeaderContainer;
+ BreadCrumbView mCrumbs;
+ TextView mSelectBookmarkView;
+
+ static BrowserBookmarksPage newInstance(BookmarksPageCallbacks cb,
+ Bundle args, ViewGroup headerContainer) {
+ BrowserBookmarksPage bbp = new BrowserBookmarksPage();
+ bbp.mCallbacks = cb;
+ bbp.mHeaderContainer = headerContainer;
+ bbp.setArguments(args);
+ return bbp;
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_BOOKMARKS: {
+ String accountType = null;
+ String accountName = null;
+ if (args != null) {
+ accountType = args.getString(BookmarksLoader.ARG_ACCOUNT_TYPE);
+ accountName = args.getString(BookmarksLoader.ARG_ACCOUNT_NAME);
+ }
+ BookmarksLoader bl = new BookmarksLoader(getActivity(), accountType, accountName);
+ if (mCrumbs != null) {
+ Uri uri = (Uri) mCrumbs.getTopData();
+ if (uri != null) {
+ bl.setUri(uri);
+ }
+ }
+ return bl;
+ }
+ case LOADER_ACCOUNTS_THEN_BOOKMARKS: {
+ return new CursorLoader(getActivity(), Accounts.CONTENT_URI,
+ new String[] { Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_NAME }, null, null,
+ null);
+ }
+ }
+ throw new UnsupportedOperationException("Unknown loader id " + id);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ switch (loader.getId()) {
+ case LOADER_BOOKMARKS: {
+ // Set the visibility of the empty vs. content views
+ if (cursor == null || cursor.getCount() == 0) {
+ mEmptyView.setVisibility(View.VISIBLE);
+ mGrid.setVisibility(View.GONE);
+ mList.setVisibility(View.GONE);
+ } else {
+ mEmptyView.setVisibility(View.GONE);
+ setupBookmarkView();
+ }
+
+ // Give the new data to the adapter
+ mAdapter.changeCursor(cursor);
+ break;
+ }
+
+ case LOADER_ACCOUNTS_THEN_BOOKMARKS: {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
+ getActivity());
+ String storedAccountType = prefs.getString(PREF_ACCOUNT_TYPE, null);
+ String storedAccountName = prefs.getString(PREF_ACCOUNT_NAME, null);
+ String accountType =
+ TextUtils.isEmpty(storedAccountType) ? DEFAULT_ACCOUNT : storedAccountType;
+ String accountName =
+ TextUtils.isEmpty(storedAccountName) ? DEFAULT_ACCOUNT : storedAccountName;
+
+ Bundle args = null;
+ if (cursor == null || !cursor.moveToFirst()) {
+ // No accounts, set the prefs to the default
+ accountType = DEFAULT_ACCOUNT;
+ accountName = DEFAULT_ACCOUNT;
+ } else {
+ int accountPosition = -1;
+
+ if (!DEFAULT_ACCOUNT.equals(accountType) &&
+ !DEFAULT_ACCOUNT.equals(accountName)) {
+ // Check to see if the account in prefs still exists
+ cursor.moveToFirst();
+ do {
+ if (accountType.equals(cursor.getString(0))
+ && accountName.equals(cursor.getString(1))) {
+ accountPosition = cursor.getPosition();
+ break;
+ }
+ } while (cursor.moveToNext());
+ }
+
+ if (accountPosition == -1) {
+ if (!(DEFAULT_ACCOUNT.equals(accountType)
+ && DEFAULT_ACCOUNT.equals(accountName))) {
+ // No account is set in prefs and there is at least one,
+ // so pick the first one as the default
+ cursor.moveToFirst();
+ accountType = cursor.getString(0);
+ accountName = cursor.getString(1);
+ }
+ }
+
+ args = new Bundle();
+ args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
+ args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
+ }
+
+ // The stored account name wasn't found, update the stored account with a valid one
+ if (!accountType.equals(storedAccountType)
+ || !accountName.equals(storedAccountName)) {
+ prefs.edit()
+ .putString(PREF_ACCOUNT_TYPE, accountType)
+ .putString(PREF_ACCOUNT_NAME, accountName)
+ .apply();
+ }
+ getLoaderManager().initLoader(LOADER_BOOKMARKS, args, this);
+
+ break;
+ }
+ }
+ }
+
+ long getFolderId() {
+ LoaderManager manager = getLoaderManager();
+ BookmarksLoader loader =
+ (BookmarksLoader) ((Loader<?>)manager.getLoader(LOADER_BOOKMARKS));
+
+ Uri uri = loader.getUri();
+ if (uri != null) {
+ try {
+ return ContentUris.parseId(uri);
+ } catch (NumberFormatException nfx) {
+ return -1;
+ }
+ }
+ return -1;
+ }
+
+ public void onFolderChange(int level, Object data) {
+ Uri uri = (Uri) data;
+ if (uri == null) {
+ // top level
+ uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
+ }
+ LoaderManager manager = getLoaderManager();
+ BookmarksLoader loader =
+ (BookmarksLoader) ((Loader<?>) manager.getLoader(LOADER_BOOKMARKS));
+ loader.setUri(uri);
+ loader.forceLoad();
+
+ }
@Override
public boolean onContextItemSelected(MenuItem item) {
+ final Activity activity = getActivity();
// It is possible that the view has been canceled when we get to
// this point as back has a higher priority
if (mCanceled) {
- return true;
+ return false;
}
AdapterView.AdapterContextMenuInfo i =
(AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
// If we have no menu info, we can't tell which item was selected.
if (i == null) {
- return true;
+ return false;
}
switch (item.getItemId()) {
- case R.id.new_context_menu_id:
- saveCurrentPage();
- break;
case R.id.open_context_menu_id:
loadUrl(i.position);
break;
@@ -108,652 +279,493 @@ public class BrowserBookmarksPage extends Activity implements
editBookmark(i.position);
break;
case R.id.shortcut_context_menu_id:
- final Intent send = createShortcutIntent(i.position);
- send.setAction(INSTALL_SHORTCUT);
- sendBroadcast(send);
+ Cursor c = mAdapter.getItem(i.position);
+ activity.sendBroadcast(createShortcutIntent(getActivity(), c));
break;
case R.id.delete_context_menu_id:
- if (mMostVisited) {
- Browser.deleteFromHistory(getContentResolver(),
- getUrl(i.position));
- refreshList();
- } else {
- displayRemoveBookmarkDialog(i.position);
- }
+ displayRemoveBookmarkDialog(i.position);
break;
case R.id.new_window_context_menu_id:
openInNewWindow(i.position);
break;
- case R.id.share_link_context_menu_id:
- BrowserActivity.sharePage(BrowserBookmarksPage.this,
- mBookmarksAdapter.getTitle(i.position), getUrl(i.position),
- getFavicon(i.position),
- mBookmarksAdapter.getScreenshot(i.position));
+ case R.id.share_link_context_menu_id: {
+ Cursor cursor = mAdapter.getItem(i.position);
+ Controller.sharePage(activity,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE),
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_URL),
+ getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON),
+ getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_THUMBNAIL));
break;
+ }
case R.id.copy_url_context_menu_id:
copy(getUrl(i.position));
break;
- case R.id.homepage_context_menu_id:
- BrowserSettings.getInstance().setHomePage(this,
- getUrl(i.position));
- Toast.makeText(this, R.string.homepage_set,
- Toast.LENGTH_LONG).show();
+ case R.id.homepage_context_menu_id: {
+ BrowserSettings.getInstance().setHomePage(activity, getUrl(i.position));
+ Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
break;
+ }
// Only for the Most visited page
- case R.id.save_to_bookmarks_menu_id:
- boolean isBookmark;
- String name;
- String url;
- if (mViewMode == BookmarkViewMode.GRID) {
- isBookmark = mBookmarksAdapter.getIsBookmark(i.position);
- name = mBookmarksAdapter.getTitle(i.position);
- url = mBookmarksAdapter.getUrl(i.position);
- } else {
- HistoryItem historyItem = ((HistoryItem) i.targetView);
- isBookmark = historyItem.isBookmark();
- name = historyItem.getName();
- url = historyItem.getUrl();
- }
+ case R.id.save_to_bookmarks_menu_id: {
+ Cursor cursor = mAdapter.getItem(i.position);
+ String name = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
+ String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
// If the site is bookmarked, the item becomes remove from
// bookmarks.
- if (isBookmark) {
- Bookmarks.removeFromBookmarks(this, getContentResolver(), url, name);
- } else {
- Browser.saveBookmark(this, name, url);
- }
+ Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(), url, name);
break;
+ }
default:
return super.onContextItemSelected(item);
}
return true;
}
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- AdapterView.AdapterContextMenuInfo i =
- (AdapterView.AdapterContextMenuInfo) menuInfo;
-
- MenuInflater inflater = getMenuInflater();
- if (mMostVisited) {
- inflater.inflate(R.menu.historycontext, menu);
- } else {
- inflater.inflate(R.menu.bookmarkscontext, menu);
- }
+ static Bitmap getBitmap(Cursor cursor, int columnIndex) {
+ byte[] data = cursor.getBlob(columnIndex);
+ if (data == null) {
+ return null;
+ }
+ return BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
- if (0 == i.position && !mMostVisited) {
- menu.setGroupVisible(R.id.CONTEXT_MENU, false);
- if (mAddHeader == null) {
- mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this);
- } else if (mAddHeader.getParent() != null) {
- ((ViewGroup) mAddHeader.getParent()).
- removeView(mAddHeader);
- }
- mAddHeader.setUrl(getIntent().getStringExtra("url"));
- menu.setHeaderView(mAddHeader);
- return;
- }
- if (mMostVisited) {
- if ((mViewMode == BookmarkViewMode.LIST
- && ((HistoryItem) i.targetView).isBookmark())
- || mBookmarksAdapter.getIsBookmark(i.position)) {
- MenuItem item = menu.findItem(
- R.id.save_to_bookmarks_menu_id);
- item.setTitle(R.string.remove_from_bookmarks);
- }
- } else {
- // The historycontext menu has no ADD_MENU group.
- menu.setGroupVisible(R.id.ADD_MENU, false);
- }
+ private MenuItem.OnMenuItemClickListener mContextItemClickListener =
+ new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return onContextItemSelected(item);
+ }
+ };
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+ Cursor cursor = mAdapter.getItem(info.position);
+ boolean isFolder
+ = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
+
+ final Activity activity = getActivity();
+ MenuInflater inflater = activity.getMenuInflater();
+ inflater.inflate(R.menu.bookmarkscontext, menu);
+ if (isFolder) {
+ menu.setGroupVisible(R.id.FOLDER_CONTEXT_MENU, true);
+ } else {
+ menu.setGroupVisible(R.id.BOOKMARK_CONTEXT_MENU, true);
if (mDisableNewWindow) {
- menu.findItem(R.id.new_window_context_menu_id).setVisible(
- false);
- }
- if (mContextHeader == null) {
- mContextHeader = new BookmarkItem(BrowserBookmarksPage.this);
- } else if (mContextHeader.getParent() != null) {
- ((ViewGroup) mContextHeader.getParent()).
- removeView(mContextHeader);
+ menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
}
- if (mViewMode == BookmarkViewMode.GRID) {
- mBookmarksAdapter.populateBookmarkItem(mContextHeader,
- i.position);
- } else {
- BookmarkItem b = (BookmarkItem) i.targetView;
- b.copyTo(mContextHeader);
+ }
+ BookmarkItem header = new BookmarkItem(activity);
+ populateBookmarkItem(cursor, header, isFolder);
+ new LookupBookmarkCount(getActivity(), header)
+ .execute(cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
+ menu.setHeaderView(header);
+
+ int count = menu.size();
+ for (int i = 0; i < count; i++) {
+ menu.getItem(i).setOnMenuItemClickListener(mContextItemClickListener);
+ }
+ }
+
+ private void populateBookmarkItem(Cursor cursor, BookmarkItem item, boolean isFolder) {
+ item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
+ if (isFolder) {
+ item.setUrl(null);
+ Bitmap bitmap =
+ BitmapFactory.decodeResource(getResources(), R.drawable.ic_folder);
+ item.setFavicon(bitmap);
+ } else {
+ String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
+ item.setUrl(url);
+ Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
+ if (bitmap == null) {
+ bitmap = CombinedBookmarkHistoryView.getIconListenerSet().getFavicon(url);
}
- menu.setHeaderView(mContextHeader);
+ item.setFavicon(bitmap);
}
+ }
/**
* Create a new BrowserBookmarksPage.
*/
@Override
- protected void onCreate(Bundle icicle) {
+ public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- // Grab the app icon size as a resource.
- mIconSize = getResources().getDimensionPixelSize(
- android.R.dimen.app_icon_size);
-
- Intent intent = getIntent();
- if (Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
- mCreateShortcut = true;
- }
- mDisableNewWindow = intent.getBooleanExtra("disable_new_window",
- false);
- mMostVisited = intent.getBooleanExtra("mostVisited", false);
-
- if (mCreateShortcut) {
- setTitle(R.string.browser_bookmarks_page_bookmarks_text);
- }
-
- setContentView(R.layout.empty_history);
- mEmptyView = findViewById(R.id.empty_view);
- mEmptyView.setVisibility(View.GONE);
-
- SharedPreferences p = getPreferences(MODE_PRIVATE);
-
- // See if the user has set a preference for the view mode of their
- // bookmarks. Otherwise default to grid mode.
- BookmarkViewMode preference = BookmarkViewMode.NONE;
- if (mMostVisited) {
- // For the most visited page, only use list mode.
- preference = BookmarkViewMode.LIST;
- } else {
- preference = BookmarkViewMode.values()[p.getInt(
- PREF_BOOKMARK_VIEW_MODE, BookmarkViewMode.GRID.ordinal())];
- }
- switchViewMode(preference);
-
- final boolean createShortcut = mCreateShortcut;
- final boolean mostVisited = mMostVisited;
- final String url = intent.getStringExtra("url");
- final String title = intent.getStringExtra("title");
- final Bitmap thumbnail =
- (Bitmap) intent.getParcelableExtra("thumbnail");
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... unused) {
- BrowserBookmarksAdapter adapter = new BrowserBookmarksAdapter(
- BrowserBookmarksPage.this,
- url,
- title,
- thumbnail,
- createShortcut,
- mostVisited);
- mHandler.obtainMessage(ADAPTER_CREATED, adapter).sendToTarget();
- return null;
- }
- }.execute();
+ Bundle args = getArguments();
+ mDisableNewWindow = args == null ? false : args.getBoolean(EXTRA_DISABLE_WINDOW, false);
}
@Override
- protected void onDestroy() {
- mHandler.removeCallbacksAndMessages(null);
- super.onDestroy();
- }
-
- /**
- * Set the ContentView to be either the grid of thumbnails or the vertical
- * list.
- */
- private void switchViewMode(BookmarkViewMode viewMode) {
- if (mViewMode == viewMode) {
- return;
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ Context context = getActivity();
+
+ View root = inflater.inflate(R.layout.bookmarks, container, false);
+ mEmptyView = root.findViewById(android.R.id.empty);
+
+ mGrid = (GridView) root.findViewById(R.id.grid);
+ mGrid.setOnItemClickListener(this);
+ mGrid.setColumnWidth(Controller.getDesiredThumbnailWidth(getActivity()));
+ mList = (ListView) root.findViewById(R.id.list);
+ mList.setOnItemClickListener(this);
+ setEnableContextMenu(mEnableContextMenu);
+
+ // Prep the header
+ ViewGroup hc = mHeaderContainer;
+ if (hc == null) {
+ hc = (ViewGroup) root.findViewById(R.id.header_container);
+ hc.setVisibility(View.VISIBLE);
}
-
- mViewMode = viewMode;
-
- // Update the preferences to make the new view mode sticky.
- Editor ed = getPreferences(MODE_PRIVATE).edit();
- if (mMostVisited) {
- ed.putInt(PREF_MOST_VISITED_VIEW_MODE, mViewMode.ordinal());
+ mHeader = inflater.inflate(R.layout.bookmarks_header, hc, true);
+ mCrumbs = (BreadCrumbView) mHeader.findViewById(R.id.crumbs);
+ mCrumbs.setController(this);
+ mSelectBookmarkView = (TextView) mHeader.findViewById(R.id.select_bookmark_view);
+ mSelectBookmarkView.setOnClickListener(this);
+ mRootFolderView = mHeader.findViewById(R.id.root_folder);
+ mRootFolderView.setOnClickListener(this);
+ setShowRootFolder(mShowRootFolder);
+
+ // Start the loaders
+ LoaderManager lm = getLoaderManager();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ mCurrentView =
+ prefs.getInt(PREF_SELECTED_VIEW, BrowserBookmarksPage.VIEW_THUMBNAILS);
+ if (mCurrentView == BrowserBookmarksPage.VIEW_THUMBNAILS) {
+ mSelectBookmarkView.setText(R.string.bookmark_thumbnail_view);
} else {
- ed.putInt(PREF_BOOKMARK_VIEW_MODE, mViewMode.ordinal());
+ mSelectBookmarkView.setText(R.string.bookmark_list_view);
}
- ed.apply();
-
- if (mBookmarksAdapter != null) {
- mBookmarksAdapter.switchViewMode(viewMode);
- }
- if (mViewMode == BookmarkViewMode.GRID) {
- if (mGridPage == null) {
- mGridPage = new GridView(this);
- if (mBookmarksAdapter != null) {
- mGridPage.setAdapter(mBookmarksAdapter);
- }
- mGridPage.setOnItemClickListener(mListener);
- mGridPage.setNumColumns(GridView.AUTO_FIT);
- mGridPage.setColumnWidth(
- BrowserActivity.getDesiredThumbnailWidth(this));
- mGridPage.setFocusable(true);
- mGridPage.setFocusableInTouchMode(true);
- mGridPage.setSelector(android.R.drawable.gallery_thumb);
- float density = getResources().getDisplayMetrics().density;
- mGridPage.setVerticalSpacing((int) (14 * density));
- mGridPage.setHorizontalSpacing((int) (8 * density));
- mGridPage.setStretchMode(GridView.STRETCH_SPACING);
- mGridPage.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
- mGridPage.setDrawSelectorOnTop(true);
- if (mMostVisited) {
- mGridPage.setEmptyView(mEmptyView);
- }
- if (!mCreateShortcut) {
- mGridPage.setOnCreateContextMenuListener(this);
- }
- }
- addContentView(mGridPage, FULL_SCREEN_PARAMS);
- if (mVerticalList != null) {
- ViewGroup parent = (ViewGroup) mVerticalList.getParent();
- if (parent != null) {
- parent.removeView(mVerticalList);
- }
+ mAdapter = new BrowserBookmarksAdapter(getActivity(), mCurrentView);
+ String accountType = prefs.getString(PREF_ACCOUNT_TYPE, DEFAULT_ACCOUNT);
+ String accountName = prefs.getString(PREF_ACCOUNT_NAME, DEFAULT_ACCOUNT);
+ if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+ // There is an account set, load up that one
+ Bundle args = null;
+ if (!DEFAULT_ACCOUNT.equals(accountType) && !DEFAULT_ACCOUNT.equals(accountName)) {
+ args = new Bundle();
+ args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
+ args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
}
+ lm.restartLoader(LOADER_BOOKMARKS, args, this);
} else {
- if (null == mVerticalList) {
- ListView listView = new ListView(this);
- if (mBookmarksAdapter != null) {
- listView.setAdapter(mBookmarksAdapter);
- }
- listView.setDrawSelectorOnTop(false);
- listView.setVerticalScrollBarEnabled(true);
- listView.setOnItemClickListener(mListener);
- if (mMostVisited) {
- listView.setEmptyView(mEmptyView);
- }
- if (!mCreateShortcut) {
- listView.setOnCreateContextMenuListener(this);
- }
- mVerticalList = listView;
- }
- addContentView(mVerticalList, FULL_SCREEN_PARAMS);
- if (mGridPage != null) {
- ViewGroup parent = (ViewGroup) mGridPage.getParent();
- if (parent != null) {
- parent.removeView(mGridPage);
- }
- }
+ // No account set, load the account list first
+ lm.restartLoader(LOADER_ACCOUNTS_THEN_BOOKMARKS, null, this);
}
- }
- private static final ViewGroup.LayoutParams FULL_SCREEN_PARAMS
- = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
+ // Add our own listener in case there are favicons that have yet to be loaded.
+ CombinedBookmarkHistoryView.getIconListenerSet().addListener(this);
- private static final int SAVE_CURRENT_PAGE = 1000;
- private static final int ADAPTER_CREATED = 1001;
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SAVE_CURRENT_PAGE:
- saveCurrentPage();
- break;
- case ADAPTER_CREATED:
- mBookmarksAdapter = (BrowserBookmarksAdapter) msg.obj;
- mBookmarksAdapter.switchViewMode(mViewMode);
- if (mGridPage != null) {
- mGridPage.setAdapter(mBookmarksAdapter);
- }
- if (mVerticalList != null) {
- mVerticalList.setAdapter(mBookmarksAdapter);
- }
- // Add our own listener in case there are favicons that
- // have yet to be loaded.
- if (mMostVisited) {
- IconListener listener = new IconListener() {
- public void onReceivedIcon(String url,
- Bitmap icon) {
- if (mGridPage != null) {
- mGridPage.setAdapter(mBookmarksAdapter);
- }
- if (mVerticalList != null) {
- mVerticalList.setAdapter(mBookmarksAdapter);
- }
- }
- };
- CombinedBookmarkHistoryActivity.getIconListenerSet()
- .addListener(listener);
- }
- break;
- }
- }
- };
-
- private AdapterView.OnItemClickListener mListener = new AdapterView.OnItemClickListener() {
- public void onItemClick(AdapterView parent, View v, int position, long id) {
- // It is possible that the view has been canceled when we get to
- // this point as back has a higher priority
- if (mCanceled) {
- android.util.Log.e(LOGTAG, "item clicked when dismissing");
- return;
- }
- if (!mCreateShortcut) {
- if (0 == position && !mMostVisited) {
- // XXX: Work-around for a framework issue.
- mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
- } else {
- loadUrl(position);
- }
- } else {
- final Intent intent = createShortcutIntent(position);
- setResultToParent(RESULT_OK, intent);
- finish();
- }
- }
- };
+ return root;
+ }
- private Intent createShortcutIntent(int position) {
- String url = getUrl(position);
- String title = getBookmarkTitle(position);
- Bitmap touchIcon = getTouchIcon(position);
-
- final Intent i = new Intent();
- final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW,
- Uri.parse(url));
- long urlHash = url.hashCode();
- long uniqueId = (urlHash << 32) | shortcutIntent.hashCode();
- shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID,
- Long.toString(uniqueId));
- i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
- i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
- // Use the apple-touch-icon if available
- if (touchIcon != null) {
- // Make a copy so we can modify the pixels. We can't use
- // createScaledBitmap or copy since they will preserve the config
- // and lose the ability to add alpha.
- Bitmap bm = Bitmap.createBitmap(mIconSize, mIconSize,
- Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bm);
- Rect src = new Rect(0, 0, touchIcon.getWidth(),
- touchIcon.getHeight());
- Rect dest = new Rect(0, 0, bm.getWidth(), bm.getHeight());
-
- // Paint used for scaling the bitmap and drawing the rounded rect.
- Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- paint.setFilterBitmap(true);
- canvas.drawBitmap(touchIcon, src, dest, paint);
-
- // Construct a path from a round rect. This will allow drawing with
- // an inverse fill so we can punch a hole using the round rect.
- Path path = new Path();
- path.setFillType(Path.FillType.INVERSE_WINDING);
- RectF rect = new RectF(0, 0, bm.getWidth(), bm.getHeight());
- rect.inset(1, 1);
- path.addRoundRect(rect, 8f, 8f, Path.Direction.CW);
-
- // Reuse the paint and clear the outside of the rectangle.
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
- canvas.drawPath(path, paint);
-
- i.putExtra(Intent.EXTRA_SHORTCUT_ICON, bm);
- } else {
- Bitmap favicon = getFavicon(position);
- if (favicon == null) {
- i.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
- Intent.ShortcutIconResource.fromContext(
- BrowserBookmarksPage.this,
- R.drawable.ic_launcher_shortcut_browser_bookmark));
+ public void setShowRootFolder(boolean show) {
+ mShowRootFolder = show;
+ if (mRootFolderView != null) {
+ if (mShowRootFolder) {
+ mRootFolderView.setVisibility(View.VISIBLE);
} else {
- Bitmap icon = BitmapFactory.decodeResource(getResources(),
- R.drawable.ic_launcher_shortcut_browser_bookmark_icon);
-
- // Make a copy of the regular icon so we can modify the pixels.
- Bitmap copy = icon.copy(Bitmap.Config.ARGB_8888, true);
- Canvas canvas = new Canvas(copy);
-
- // Make a Paint for the white background rectangle and for
- // filtering the favicon.
- Paint p = new Paint(Paint.ANTI_ALIAS_FLAG
- | Paint.FILTER_BITMAP_FLAG);
- p.setStyle(Paint.Style.FILL_AND_STROKE);
- p.setColor(Color.WHITE);
-
- final float density =
- getResources().getDisplayMetrics().density;
- // Create a rectangle that is slightly wider than the favicon
- final float iconSize = 16 * density; // 16x16 favicon
- final float padding = 2 * density; // white padding around icon
- final float rectSize = iconSize + 2 * padding;
-
- final Rect iconBounds =
- new Rect(0, 0, icon.getWidth(), icon.getHeight());
- final float x = iconBounds.exactCenterX() - (rectSize / 2);
- // Note: Subtract 2 dip from the y position since the box is
- // slightly higher than center. Use padding since it is already
- // 2 * density.
- final float y = iconBounds.exactCenterY() - (rectSize / 2)
- - padding;
- RectF r = new RectF(x, y, x + rectSize, y + rectSize);
-
- // Draw a white rounded rectangle behind the favicon
- canvas.drawRoundRect(r, 2, 2, p);
-
- // Draw the favicon in the same rectangle as the rounded
- // rectangle but inset by the padding
- // (results in a 16x16 favicon).
- r.inset(padding, padding);
- canvas.drawBitmap(favicon, null, r, p);
- i.putExtra(Intent.EXTRA_SHORTCUT_ICON, copy);
+ mRootFolderView.setVisibility(View.GONE);
}
}
- // Do not allow duplicate items
- i.putExtra("duplicate", false);
- return i;
}
- private void saveCurrentPage() {
- Intent i = new Intent(BrowserBookmarksPage.this,
- AddBookmarkPage.class);
- i.putExtras(getIntent());
- startActivityForResult(i, BOOKMARKS_SAVE);
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ if (mHeaderContainer != null) {
+ mHeaderContainer.removeView(mHeader);
+ }
+ mCrumbs.setController(null);
+ mCrumbs = null;
}
- private void loadUrl(int position) {
- Intent intent = (new Intent()).setAction(getUrl(position));
- setResultToParent(RESULT_OK, intent);
- finish();
+ @Override
+ public void onReceivedIcon(String url, Bitmap icon) {
+ // A new favicon has been loaded, so let anything attached to the adapter know about it
+ // so new icons will be loaded.
+ mAdapter.notifyDataSetChanged();
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- boolean result = super.onCreateOptionsMenu(menu);
- if (!mCreateShortcut && !mMostVisited) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.bookmarks, menu);
- return true;
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ // It is possible that the view has been canceled when we get to
+ // this point as back has a higher priority
+ if (mCanceled) {
+ android.util.Log.e(LOGTAG, "item clicked when dismissing");
+ return;
+ }
+
+ Cursor cursor = mAdapter.getItem(position);
+ boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
+ if (mCallbacks != null &&
+ mCallbacks.onBookmarkSelected(cursor, isFolder)) {
+ return;
+ }
+
+ if (isFolder) {
+ String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
+ LoaderManager manager = getLoaderManager();
+ BookmarksLoader loader =
+ (BookmarksLoader) ((Loader<?>) manager.getLoader(LOADER_BOOKMARKS));
+ Uri uri = ContentUris.withAppendedId(
+ BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id);
+ if (mCrumbs != null) {
+ // update crumbs
+ mCrumbs.pushView(title, uri);
+ }
+ loader.setUri(uri);
+ loader.forceLoad();
}
- return result;
}
@Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- boolean result = super.onPrepareOptionsMenu(menu);
- if (mCreateShortcut || mMostVisited || mBookmarksAdapter == null
- || mBookmarksAdapter.getCount() == 0) {
- // No need to show the menu if there are no items.
- return result;
- }
- MenuItem switchItem = menu.findItem(R.id.switch_mode_menu_id);
- int titleResId;
- int iconResId;
- if (mViewMode == BookmarkViewMode.GRID) {
- titleResId = R.string.switch_to_list;
- iconResId = R.drawable.ic_menu_list;
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Adapter adapter = parent.getAdapter();
+ String accountType = "com.google";
+ String accountName = adapter.getItem(position).toString();
+
+ Bundle args = null;
+ if (ACCOUNT_NAME_UNSYNCED.equals(accountName)) {
+ accountType = DEFAULT_ACCOUNT;
+ accountName = DEFAULT_ACCOUNT;
} else {
- titleResId = R.string.switch_to_thumbnails;
- iconResId = R.drawable.ic_menu_thumbnail;
+ args = new Bundle();
+ args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
+ args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
}
- switchItem.setTitle(titleResId);
- switchItem.setIcon(iconResId);
- return true;
+
+ // Remember the selection for later
+ PreferenceManager.getDefaultSharedPreferences(getActivity()).edit()
+ .putString(PREF_ACCOUNT_TYPE, accountType)
+ .putString(PREF_ACCOUNT_NAME, accountName)
+ .apply();
+
+ getLoaderManager().restartLoader(LOADER_BOOKMARKS, args, this);
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.new_context_menu_id:
- saveCurrentPage();
- break;
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing
+ }
- case R.id.switch_mode_menu_id:
- if (mViewMode == BookmarkViewMode.GRID) {
- switchViewMode(BookmarkViewMode.LIST);
- } else {
- switchViewMode(BookmarkViewMode.GRID);
- }
- break;
+ /* package */ static Intent createShortcutIntent(Context context, Cursor cursor) {
+ String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
+ String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
+ Bitmap touchIcon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_TOUCH_ICON);
+ Bitmap favicon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
+ return BookmarkUtils.createAddToHomeIntent(context, url, title, touchIcon, favicon);
+ }
- default:
- return super.onOptionsItemSelected(item);
+ private void loadUrl(int position) {
+ if (mCallbacks != null) {
+ mCallbacks.onBookmarkSelected(mAdapter.getItem(position), false);
}
- return true;
}
private void openInNewWindow(int position) {
- Bundle b = new Bundle();
- b.putBoolean("new_window", true);
- setResultToParent(RESULT_OK,
- (new Intent()).setAction(getUrl(position)).putExtras(b));
-
- finish();
+ if (mCallbacks != null) {
+ mCallbacks.onOpenInNewWindow(mAdapter.getItem(position));
+ }
}
-
private void editBookmark(int position) {
- Intent intent = new Intent(BrowserBookmarksPage.this,
- AddBookmarkPage.class);
- intent.putExtra("bookmark", getRow(position));
- startActivityForResult(intent, BOOKMARKS_SAVE);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode,
- Intent data) {
- switch(requestCode) {
- case BOOKMARKS_SAVE:
- if (resultCode == RESULT_OK) {
- Bundle extras;
- if (data != null && (extras = data.getExtras()) != null) {
- // If there are extras, then we need to save
- // the edited bookmark. This is done in updateRow()
- String title = extras.getString("title");
- String url = extras.getString("url");
- if (title != null && url != null) {
- mBookmarksAdapter.updateRow(extras);
- }
- } else {
- // extras == null then a new bookmark was added to
- // the database.
- refreshList();
- }
- }
- break;
- default:
- break;
+ Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
+ Cursor cursor = mAdapter.getItem(position);
+ Bundle item = new Bundle();
+ item.putString(BrowserContract.Bookmarks.TITLE,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
+ item.putString(BrowserContract.Bookmarks.URL,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
+ byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
+ if (data != null) {
+ item.putParcelable(BrowserContract.Bookmarks.FAVICON,
+ BitmapFactory.decodeByteArray(data, 0, data.length));
}
+ item.putLong(BrowserContract.Bookmarks._ID,
+ cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
+ item.putLong(BrowserContract.Bookmarks.PARENT,
+ cursor.getLong(BookmarksLoader.COLUMN_INDEX_PARENT));
+ intent.putExtra(AddBookmarkPage.EXTRA_EDIT_BOOKMARK, item);
+ intent.putExtra(AddBookmarkPage.EXTRA_IS_FOLDER,
+ cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1);
+ startActivity(intent);
}
- private void displayRemoveBookmarkDialog(int position) {
+ private void displayRemoveBookmarkDialog(final int position) {
// Put up a dialog asking if the user really wants to
// delete the bookmark
- final int deletePos = position;
- new AlertDialog.Builder(this)
+ Cursor cursor = mAdapter.getItem(position);
+ Context context = getActivity();
+ final ContentResolver resolver = context.getContentResolver();
+ final Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
+ cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
+
+ new AlertDialog.Builder(context)
.setTitle(R.string.delete_bookmark)
.setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(getText(R.string.delete_bookmark_warning).toString().replace(
- "%s", getBookmarkTitle(deletePos)))
+ .setMessage(context.getString(R.string.delete_bookmark_warning,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)))
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialog, int whichButton) {
- deleteBookmark(deletePos);
+ resolver.delete(uri, null, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
- /**
- * Refresh the shown list after the database has changed.
- */
- private void refreshList() {
- if (mBookmarksAdapter == null) return;
- mBookmarksAdapter.refreshList();
+ private String getUrl(int position) {
+ return getUrl(mAdapter.getItem(position));
}
- /**
- * Return a hashmap representing the currently highlighted row.
- */
- public Bundle getRow(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getRow(position);
+ /* package */ static String getUrl(Cursor c) {
+ return c.getString(BookmarksLoader.COLUMN_INDEX_URL);
}
- /**
- * Return the url of the currently highlighted row.
- */
- public String getUrl(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getUrl(position);
+ private void copy(CharSequence text) {
+ ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
+ Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(ClipData.newRawUri(null, null, Uri.parse(text.toString())));
+ }
+
+ void selectView(int view) {
+ if (view == mCurrentView) {
+ return;
+ }
+ mCurrentView = view;
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ Editor edit = prefs.edit();
+ edit.putInt(PREF_SELECTED_VIEW, mCurrentView);
+ edit.apply();
+ if (mEmptyView.getVisibility() == View.VISIBLE) {
+ return;
+ }
+ setupBookmarkView();
+ }
+
+ private void setupBookmarkView() {
+ mAdapter.selectView(mCurrentView);
+ switch (mCurrentView) {
+ case VIEW_THUMBNAILS:
+ mList.setAdapter(null);
+ mGrid.setAdapter(mAdapter);
+ mGrid.setVisibility(View.VISIBLE);
+ mList.setVisibility(View.GONE);
+ break;
+ case VIEW_LIST:
+ mGrid.setAdapter(null);
+ mList.setAdapter(mAdapter);
+ mGrid.setVisibility(View.GONE);
+ mList.setVisibility(View.VISIBLE);
+ break;
+ }
+ }
+
+ public BreadCrumbView getBreadCrumb() {
+ return mCrumbs;
}
/**
- * Return the favicon of the currently highlighted row.
+ * BreadCrumb controller callback
*/
- public Bitmap getFavicon(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getFavicon(position);
+ @Override
+ public void onTop(int level, Object data) {
+ onFolderChange(level, data);
}
- private Bitmap getTouchIcon(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getTouchIcon(position);
+ @Override
+ public void onClick(View view) {
+ if (mSelectBookmarkView == view) {
+ PopupMenu popup = new PopupMenu(getActivity(), mSelectBookmarkView);
+ popup.getMenuInflater().inflate(R.menu.bookmark_view,
+ popup.getMenu());
+ popup.setOnMenuItemClickListener(this);
+ popup.show();
+ } else if (mRootFolderView == view) {
+ mCrumbs.clear();
+ }
}
- private void copy(CharSequence text) {
- try {
- IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
- if (clip != null) {
- clip.setClipboardText(text);
- }
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Copy failed", e);
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.list_view:
+ mSelectBookmarkView.setText(R.string.bookmark_list_view);
+ selectView(BrowserBookmarksPage.VIEW_LIST);
+ return true;
+ case R.id.thumbnail_view:
+ mSelectBookmarkView.setText(R.string.bookmark_thumbnail_view);
+ selectView(BrowserBookmarksPage.VIEW_THUMBNAILS);
+ return true;
}
+ return false;
}
- public String getBookmarkTitle(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getTitle(position);
+ public boolean onBackPressed() {
+ if (mCrumbs != null &&
+ mCrumbs.size() > 0) {
+ mCrumbs.popView();
+ return true;
+ }
+ return false;
}
- /**
- * Delete the currently highlighted row.
- */
- public void deleteBookmark(int position) {
- if (mBookmarksAdapter == null) return;
- mBookmarksAdapter.deleteRow(position);
+ public void setCallbackListener(BookmarksPageCallbacks callbackListener) {
+ mCallbacks = callbackListener;
}
- @Override
- public void onBackPressed() {
- setResultToParent(RESULT_CANCELED, null);
- mCanceled = true;
- super.onBackPressed();
- }
-
- // This Activity is generally a sub-Activity of
- // CombinedBookmarkHistoryActivity. In that situation, we need to pass our
- // result code up to our parent. However, if someone calls this Activity
- // directly, then this has no parent, and it needs to set it on itself.
- private void setResultToParent(int resultCode, Intent data) {
- Activity parent = getParent();
- if (parent == null) {
- setResult(resultCode, data);
- } else {
- ((CombinedBookmarkHistoryActivity) parent).setResultFromChild(
- resultCode, data);
+ public void setEnableContextMenu(boolean enable) {
+ mEnableContextMenu = enable;
+ if (mGrid != null) {
+ if (mEnableContextMenu) {
+ registerForContextMenu(mGrid);
+ } else {
+ unregisterForContextMenu(mGrid);
+ mGrid.setLongClickable(false);
+ }
+ }
+ if (mList != null) {
+ if (mEnableContextMenu) {
+ registerForContextMenu(mList);
+ } else {
+ unregisterForContextMenu(mList);
+ mList.setLongClickable(false);
+ }
+ }
+ }
+
+ private static class LookupBookmarkCount extends AsyncTask<Long, Void, Integer> {
+ Context mContext;
+ BookmarkItem mHeader;
+
+ public LookupBookmarkCount(Context context, BookmarkItem header) {
+ mContext = context;
+ mHeader = header;
+ }
+
+ @Override
+ protected Integer doInBackground(Long... params) {
+ if (params.length != 1) {
+ throw new IllegalArgumentException("Missing folder id!");
+ }
+ Uri uri = BookmarkUtils.getBookmarksUri(mContext);
+ Cursor c = mContext.getContentResolver().query(uri,
+ null, BrowserContract.Bookmarks.PARENT + "=?",
+ new String[] {params[0].toString()}, null);
+ return c.getCount();
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ if (result > 0) {
+ mHeader.setUrl(mContext.getString(R.string.contextheader_folder_bookmarkcount,
+ result));
+ } else if (result == 0) {
+ mHeader.setUrl(mContext.getString(R.string.contextheader_folder_empty));
+ }
}
}
}
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java
index 23080f86b..a3ce0b446 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -17,198 +17,252 @@
package com.android.browser;
import android.app.Activity;
-import android.app.ExpandableListActivity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ClipboardManager;
import android.content.Context;
+import android.content.CursorLoader;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.net.Uri;
import android.os.Bundle;
-import android.os.ServiceManager;
import android.provider.Browser;
-import android.text.IClipboard;
-import android.util.Log;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.History;
import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.ViewStub;
import android.webkit.WebIconDatabase.IconListener;
-import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+import android.widget.ExpandableListView.OnChildClickListener;
+import android.widget.TextView;
import android.widget.Toast;
/**
* Activity for displaying the browser's history, divided into
* days of viewing.
*/
-public class BrowserHistoryPage extends ExpandableListActivity {
- private HistoryAdapter mAdapter;
- private boolean mDisableNewWindow;
- private HistoryItem mContextHeader;
+public class BrowserHistoryPage extends Fragment
+ implements LoaderCallbacks<Cursor>, OnChildClickListener {
+
+ static final int LOADER_HISTORY = 1;
+ static final int LOADER_MOST_VISITED = 2;
- private final static String LOGTAG = "browser";
+ BookmarksHistoryCallbacks mCallbacks;
+ ExpandableListView mList;
+ View mEmptyView;
+ HistoryAdapter mAdapter;
+ boolean mDisableNewWindow;
+ HistoryItem mContextHeader;
+ String mMostVisitsLimit;
// Implementation of WebIconDatabase.IconListener
- private class IconReceiver implements IconListener {
+ class IconReceiver implements IconListener {
+ @Override
public void onReceivedIcon(String url, Bitmap icon) {
- setListAdapter(mAdapter);
+ mAdapter.notifyDataSetChanged();
}
}
+
// Instance of IconReceiver
- private final IconReceiver mIconReceiver = new IconReceiver();
-
- /**
- * Report back to the calling activity to load a site.
- * @param url Site to load.
- * @param newWindow True if the URL should be loaded in a new window
- */
- private void loadUrl(String url, boolean newWindow) {
- Intent intent = new Intent().setAction(url);
- if (newWindow) {
- Bundle b = new Bundle();
- b.putBoolean("new_window", true);
- intent.putExtras(b);
- }
- setResultToParent(RESULT_OK, intent);
- finish();
+ final IconReceiver mIconReceiver = new IconReceiver();
+
+ static interface HistoryQuery {
+ static final String[] PROJECTION = new String[] {
+ History._ID, // 0
+ History.DATE_LAST_VISITED, // 1
+ History.TITLE, // 2
+ History.URL, // 3
+ History.FAVICON, // 4
+ History.VISITS // 5
+ };
+
+ static final int INDEX_ID = 0;
+ static final int INDEX_DATE_LAST_VISITED = 1;
+ static final int INDEX_TITE = 2;
+ static final int INDEX_URL = 3;
+ static final int INDEX_FAVICON = 4;
+ static final int INDEX_VISITS = 5;
}
-
+
private void copy(CharSequence text) {
- try {
- IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
- if (clip != null) {
- clip.setClipboardText(text);
+ ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
+ Context.CLIPBOARD_SERVICE);
+ cm.setText(text);
+ }
+
+ static BrowserHistoryPage newInstance(BookmarksHistoryCallbacks cb, Bundle args) {
+ BrowserHistoryPage bhp = new BrowserHistoryPage();
+ bhp.mCallbacks = cb;
+ bhp.setArguments(args);
+ return bhp;
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_HISTORY: {
+ CursorLoader loader = new CursorLoader(getActivity(), History.CONTENT_URI,
+ HistoryQuery.PROJECTION, null, null, null);
+ return loader;
+ }
+
+ case LOADER_MOST_VISITED: {
+ Uri uri = History.CONTENT_URI
+ .buildUpon()
+ .appendQueryParameter(BrowserContract.PARAM_LIMIT, mMostVisitsLimit)
+ .build();
+ CursorLoader loader = new CursorLoader(getActivity(), uri,
+ HistoryQuery.PROJECTION, null, null, History.VISITS + " DESC");
+ return loader;
+ }
+
+ default: {
+ throw new IllegalArgumentException();
}
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Copy failed", e);
}
}
@Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setTitle(R.string.browser_history);
-
- final String whereClause = Browser.BookmarkColumns.VISITS + " > 0"
- // In AddBookmarkPage, where we save new bookmarks, we add
- // three visits to newly created bookmarks, so that
- // bookmarks that have not been visited will show up in the
- // most visited, and higher in the goto search box.
- // However, this puts the site in the history, unless we
- // ignore sites with a DATE of 0, which the next line does.
- + " AND " + Browser.BookmarkColumns.DATE + " > 0";
- final String orderBy = Browser.BookmarkColumns.DATE + " DESC";
-
- Cursor cursor = managedQuery(
- Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- whereClause, null, orderBy);
-
- mAdapter = new HistoryAdapter(this, cursor,
- Browser.HISTORY_PROJECTION_DATE_INDEX);
- setListAdapter(mAdapter);
- final ExpandableListView list = getExpandableListView();
- list.setOnCreateContextMenuListener(this);
- View v = new ViewStub(this, R.layout.empty_history);
- addContentView(v, new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- list.setEmptyView(v);
- // Do not post the runnable if there is nothing in the list.
- if (list.getExpandableListAdapter().getGroupCount() > 0) {
- list.post(new Runnable() {
- public void run() {
- // In case the history gets cleared before this event
- // happens.
- if (list.getExpandableListAdapter().getGroupCount() > 0) {
- list.expandGroup(0);
- }
- }
- });
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ switch (loader.getId()) {
+ case LOADER_HISTORY: {
+ mAdapter.changeCursor(data);
+
+ // Add an empty view late, so it does not claim an empty
+ // history before the adapter is present
+ mList.setEmptyView(mEmptyView);
+ break;
+ }
+
+ case LOADER_MOST_VISITED: {
+ mAdapter.changeMostVisitedCursor(data);
+
+ // Add an empty view late, so it does not claim an empty
+ // history before the adapter is present
+ mList.setEmptyView(mEmptyView);
+ break;
+ }
+
+ default: {
+ throw new IllegalArgumentException();
+ }
}
- mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window",
- false);
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setHasOptionsMenu(true);
+
+ Bundle args = getArguments();
+ mDisableNewWindow = args.getBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW, false);
+ int mvlimit = getResources().getInteger(R.integer.most_visits_limit);
+ mMostVisitsLimit = Integer.toString(mvlimit);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.history, container, false);
+ mList = (ExpandableListView) root.findViewById(android.R.id.list);
+ mList.setCacheColorHint(0);
+ mList.setOnCreateContextMenuListener(this);
+ mList.setOnChildClickListener(this);
+ mAdapter = new HistoryAdapter(getActivity());
+ mList.setAdapter(mAdapter);
+
+ mEmptyView = root.findViewById(android.R.id.empty);
+
+ // Start the loader
+ getLoaderManager().initLoader(LOADER_HISTORY, null, this);
+ getLoaderManager().initLoader(LOADER_MOST_VISITED, null, this);
// Register to receive icons in case they haven't all been loaded.
- CombinedBookmarkHistoryActivity.getIconListenerSet()
- .addListener(mIconReceiver);
-
- Activity parent = getParent();
- if (null == parent
- || !(parent instanceof CombinedBookmarkHistoryActivity)) {
- throw new AssertionError("history page can only be viewed as a tab"
- + "in CombinedBookmarkHistoryActivity");
- }
- // initialize the result to canceled, so that if the user just presses
- // back then it will have the correct result
- setResultToParent(RESULT_CANCELED, null);
+ CombinedBookmarkHistoryView.getIconListenerSet().addListener(mIconReceiver);
+ return root;
}
@Override
- protected void onDestroy() {
+ public void onDestroy() {
super.onDestroy();
- CombinedBookmarkHistoryActivity.getIconListenerSet()
- .removeListener(mIconReceiver);
+ CombinedBookmarkHistoryView.getIconListenerSet().removeListener(mIconReceiver);
+ getLoaderManager().stopLoader(LOADER_HISTORY);
+ getLoaderManager().stopLoader(LOADER_MOST_VISITED);
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- MenuInflater inflater = getMenuInflater();
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.history, menu);
- return true;
}
@Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- menu.findItem(R.id.clear_history_menu_id).setVisible(Browser.canClearHistory(this.getContentResolver()));
- return true;
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.clear_history_menu_id).setVisible(
+ Browser.canClearHistory(getActivity().getContentResolver()));
}
-
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.clear_history_menu_id:
- Browser.clearHistory(getContentResolver());
- // BrowserHistoryPage is always a child of
- // CombinedBookmarkHistoryActivity
- ((CombinedBookmarkHistoryActivity) getParent())
- .removeParentChildRelationShips();
- mAdapter.refreshData();
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.clear)
+ .setMessage(R.string.pref_privacy_clear_history_dlg)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ Browser.clearHistory(getActivity().getContentResolver());
+ mCallbacks.onRemoveParentChildRelationships();
+ }
+ }
+ });
+ final Dialog dialog = builder.create();
+ dialog.show();
return true;
-
+
default:
break;
- }
+ }
return super.onOptionsItemSelected(item);
}
@Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- ExpandableListContextMenuInfo i =
- (ExpandableListContextMenuInfo) menuInfo;
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ ExpandableListContextMenuInfo i = (ExpandableListContextMenuInfo) menuInfo;
// Do not allow a context menu to come up from the group views.
if (!(i.targetView instanceof HistoryItem)) {
return;
}
// Inflate the menu
- MenuInflater inflater = getMenuInflater();
+ Activity parent = getActivity();
+ MenuInflater inflater = parent.getMenuInflater();
inflater.inflate(R.menu.historycontext, menu);
HistoryItem historyItem = (HistoryItem) i.targetView;
// Setup the header
if (mContextHeader == null) {
- mContextHeader = new HistoryItem(this);
+ mContextHeader = new HistoryItem(parent);
} else if (mContextHeader.getParent() != null) {
((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader);
}
@@ -225,88 +279,173 @@ public class BrowserHistoryPage extends ExpandableListActivity {
item.setTitle(R.string.remove_from_bookmarks);
}
// decide whether to show the share link option
- PackageManager pm = getPackageManager();
+ PackageManager pm = parent.getPackageManager();
Intent send = new Intent(Intent.ACTION_SEND);
send.setType("text/plain");
ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
-
+
super.onCreateContextMenu(menu, v, menuInfo);
}
-
+
@Override
public boolean onContextItemSelected(MenuItem item) {
- ExpandableListContextMenuInfo i =
+ ExpandableListContextMenuInfo i =
(ExpandableListContextMenuInfo) item.getMenuInfo();
+ if (i == null) {
+ return false;
+ }
HistoryItem historyItem = (HistoryItem) i.targetView;
String url = historyItem.getUrl();
String title = historyItem.getName();
+ Activity activity = getActivity();
switch (item.getItemId()) {
case R.id.open_context_menu_id:
- loadUrl(url, false);
+ mCallbacks.onUrlSelected(url, false);
return true;
case R.id.new_window_context_menu_id:
- loadUrl(url, true);
+ mCallbacks.onUrlSelected(url, true);
return true;
case R.id.save_to_bookmarks_menu_id:
if (historyItem.isBookmark()) {
- Bookmarks.removeFromBookmarks(this, getContentResolver(),
+ Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(),
url, title);
} else {
- Browser.saveBookmark(this, title, url);
+ Browser.saveBookmark(activity, title, url);
}
return true;
case R.id.share_link_context_menu_id:
- Browser.sendString(this, url,
- getText(R.string.choosertitle_sharevia).toString());
+ Browser.sendString(activity, url,
+ activity.getText(R.string.choosertitle_sharevia).toString());
return true;
case R.id.copy_url_context_menu_id:
copy(url);
return true;
case R.id.delete_context_menu_id:
- Browser.deleteFromHistory(getContentResolver(), url);
- mAdapter.refreshData();
+ Browser.deleteFromHistory(activity.getContentResolver(), url);
return true;
case R.id.homepage_context_menu_id:
- BrowserSettings.getInstance().setHomePage(this, url);
- Toast.makeText(this, R.string.homepage_set,
- Toast.LENGTH_LONG).show();
+ BrowserSettings.getInstance().setHomePage(activity, url);
+ Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
return true;
default:
break;
}
return super.onContextItemSelected(item);
}
-
+
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
if (v instanceof HistoryItem) {
- loadUrl(((HistoryItem) v).getUrl(), false);
+ mCallbacks.onUrlSelected(((HistoryItem) v).getUrl(), false);
return true;
}
return false;
}
- // This Activity is always a sub-Activity of
- // CombinedBookmarkHistoryActivity. Therefore, we need to pass our
- // result code up to our parent.
- private void setResultToParent(int resultCode, Intent data) {
- ((CombinedBookmarkHistoryActivity) getParent()).setResultFromChild(
- resultCode, data);
- }
-
private class HistoryAdapter extends DateSortedExpandableListAdapter {
- HistoryAdapter(Context context, Cursor cursor, int index) {
- super(context, cursor, index);
-
+ private Cursor mMostVisited, mHistoryCursor;
+
+ HistoryAdapter(Context context) {
+ super(context, HistoryQuery.INDEX_DATE_LAST_VISITED);
}
+ @Override
+ public void changeCursor(Cursor cursor) {
+ mHistoryCursor = cursor;
+ super.changeCursor(cursor);
+ }
+
+ void changeMostVisitedCursor(Cursor cursor) {
+ if (mMostVisited == cursor) {
+ return;
+ }
+ if (mMostVisited != null) {
+ mMostVisited.unregisterDataSetObserver(mDataSetObserver);
+ mMostVisited.close();
+ }
+ mMostVisited = cursor;
+ mMostVisited.registerDataSetObserver(mDataSetObserver);
+ }
+
+ @Override
+ public long getChildId(int groupPosition, int childPosition) {
+ if (!mDataValid) return 0;
+ if (moveCursorToChildPosition(groupPosition, childPosition)) {
+ Cursor cursor = getCursor(groupPosition);
+ return cursor.getLong(HistoryQuery.INDEX_ID);
+ }
+ return 0;
+ }
+
+ @Override
+ public int getGroupCount() {
+ return super.getGroupCount() + (mMostVisited != null ? 1 : 0);
+ }
+
+ @Override
+ public int getChildrenCount(int groupPosition) {
+ if (groupPosition >= super.getGroupCount()) {
+ return mMostVisited.getCount();
+ }
+ return super.getChildrenCount(groupPosition);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ if (!super.isEmpty()) {
+ return false;
+ }
+ return mMostVisited == null
+ || mMostVisited.isClosed()
+ || mMostVisited.getCount() == 0;
+ }
+
+ Cursor getCursor(int groupPosition) {
+ if (groupPosition >= super.getGroupCount()) {
+ return mMostVisited;
+ }
+ return mHistoryCursor;
+ }
+
+ @Override
+ public View getGroupView(int groupPosition, boolean isExpanded,
+ View convertView, ViewGroup parent) {
+ if (groupPosition >= super.getGroupCount()) {
+ if (!mDataValid) throw new IllegalStateException("Data is not valid");
+ TextView item;
+ if (null == convertView || !(convertView instanceof TextView)) {
+ LayoutInflater factory = LayoutInflater.from(getContext());
+ item = (TextView) factory.inflate(R.layout.history_header, null);
+ } else {
+ item = (TextView) convertView;
+ }
+ item.setText(R.string.tab_most_visited);
+ return item;
+ }
+ return super.getGroupView(groupPosition, isExpanded, convertView, parent);
+ }
+
+ @Override
+ boolean moveCursorToChildPosition(
+ int groupPosition, int childPosition) {
+ if (groupPosition >= super.getGroupCount()) {
+ if (mDataValid && !mMostVisited.isClosed()) {
+ mMostVisited.moveToPosition(childPosition);
+ return true;
+ }
+ return false;
+ }
+ return super.moveCursorToChildPosition(groupPosition, childPosition);
+ }
+
+ @Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View convertView, ViewGroup parent) {
HistoryItem item;
if (null == convertView || !(convertView instanceof HistoryItem)) {
- item = new HistoryItem(BrowserHistoryPage.this);
+ item = new HistoryItem(getContext());
// Add padding on the left so it will be indented from the
// arrows on the group views.
item.setPadding(item.getPaddingLeft() + 10,
@@ -316,23 +455,24 @@ public class BrowserHistoryPage extends ExpandableListActivity {
} else {
item = (HistoryItem) convertView;
}
+
// Bail early if the Cursor is closed.
if (!moveCursorToChildPosition(groupPosition, childPosition)) {
return item;
}
- item.setName(getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
- String url = getString(Browser.HISTORY_PROJECTION_URL_INDEX);
+
+ Cursor cursor = getCursor(groupPosition);
+ item.setName(cursor.getString(HistoryQuery.INDEX_TITE));
+ String url = cursor.getString(HistoryQuery.INDEX_URL);
item.setUrl(url);
- byte[] data = getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ byte[] data = cursor.getBlob(HistoryQuery.INDEX_FAVICON);
if (data != null) {
item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
data.length));
} else {
- item.setFavicon(CombinedBookmarkHistoryActivity
+ item.setFavicon(CombinedBookmarkHistoryView
.getIconListenerSet().getFavicon(url));
}
- item.setIsBookmark(1 ==
- getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
return item;
}
}
diff --git a/src/com/android/browser/BrowserHomepagePreference.java b/src/com/android/browser/BrowserHomepagePreference.java
index 4f18bd5db..988605320 100644
--- a/src/com/android/browser/BrowserHomepagePreference.java
+++ b/src/com/android/browser/BrowserHomepagePreference.java
@@ -18,32 +18,38 @@ package com.android.browser;
import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.EditTextPreference;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
+import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.util.AttributeSet;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Toast;
public class BrowserHomepagePreference extends EditTextPreference {
private String mCurrentPage;
+ private AlertDialog mSetHomepageTo;
public BrowserHomepagePreference(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
+ createSetHomepageToDialog();
}
public BrowserHomepagePreference(Context context, AttributeSet attrs) {
super(context, attrs);
+ createSetHomepageToDialog();
}
public BrowserHomepagePreference(Context context) {
super(context);
+ createSetHomepageToDialog();
}
@Override
@@ -54,10 +60,10 @@ public class BrowserHomepagePreference extends EditTextPreference {
// page.
ViewGroup parent = (ViewGroup) editText.getParent();
Button button = new Button(getContext());
- button.setText(R.string.pref_use_current);
+ button.setText(R.string.pref_set_homepage_to);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
- getEditText().setText(mCurrentPage);
+ mSetHomepageTo.show();
}
});
if (parent instanceof LinearLayout) {
@@ -67,24 +73,48 @@ public class BrowserHomepagePreference extends EditTextPreference {
ViewGroup.LayoutParams.WRAP_CONTENT);
}
+ private void createSetHomepageToDialog() {
+ Context context = getContext();
+ CharSequence[] setToChoices = new CharSequence[] {
+ context.getText(R.string.pref_use_current),
+ context.getText(R.string.pref_use_blank),
+ context.getText(R.string.pref_use_default),
+ };
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.pref_set_homepage_to);
+ builder.setItems(setToChoices, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == 0) {
+ getEditText().setText(mCurrentPage);
+ } else if (which == 1) {
+ getEditText().setText("about:blank");
+ } else if (which == 2) {
+ getEditText().setText(BrowserSettings
+ .getFactoryResetHomeUrl(getContext()));
+ }
+ }
+ });
+ mSetHomepageTo = builder.create();
+ }
+
@Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
String url = getEditText().getText().toString();
if (url.length() > 0
- && !BrowserActivity.ACCEPTED_URI_SCHEMA.matcher(url)
+ && !UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url)
.matches()) {
int colon = url.indexOf(':');
int space = url.indexOf(' ');
- if (colon == -1 && space == -1) {
+ if (colon == -1 && space == -1 && url.length() > 0) {
// if no colon, no space, add "http://" to make it a url
getEditText().setText("http://" + url);
} else {
- // show an error dialog and change the positiveResult to
+ // show an error toast and change the positiveResult to
// false so that the bad url will not override the old url
- new AlertDialog.Builder(this.getContext()).setMessage(
- R.string.bookmark_url_not_valid).setPositiveButton(
- R.string.ok, null).show();
+ Toast.makeText(getContext(), R.string.bookmark_url_not_valid,
+ Toast.LENGTH_SHORT).show();
positiveResult = false;
}
}
@@ -97,7 +127,7 @@ public class BrowserHomepagePreference extends EditTextPreference {
* @param currentPage This String will replace the text in the EditText
* when the user clicks the "Use current page" button.
*/
- /* package */ void setCurrentPage(String currentPage) {
+ public void setCurrentPage(String currentPage) {
mCurrentPage = currentPage;
}
diff --git a/src/com/android/browser/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java
index 9af66f1fa..d93e70f24 100644
--- a/src/com/android/browser/BrowserPreferencesPage.java
+++ b/src/com/android/browser/BrowserPreferencesPage.java
@@ -16,96 +16,30 @@
package com.android.browser;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
+import com.android.browser.preferences.DebugPreferencesFragment;
+
import android.preference.PreferenceActivity;
-import android.preference.PreferenceScreen;
-import android.webkit.GeolocationPermissions;
-import android.webkit.ValueCallback;
-import android.webkit.WebStorage;
+import android.preference.PreferenceManager;
-import java.util.Map;
-import java.util.Set;
+import java.util.List;
-public class BrowserPreferencesPage extends PreferenceActivity
- implements Preference.OnPreferenceChangeListener {
+public class BrowserPreferencesPage extends PreferenceActivity {
- private String LOGTAG = "BrowserPreferencesPage";
- /* package */ static final String CURRENT_PAGE = "currentPage";
+ public static final String CURRENT_PAGE = "currentPage";
+ /**
+ * Populate the activity with the top-level headers.
+ */
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Load the XML preferences file
- addPreferencesFromResource(R.xml.browser_preferences);
-
- Preference e = findPreference(BrowserSettings.PREF_HOMEPAGE);
- e.setOnPreferenceChangeListener(this);
- e.setSummary(getPreferenceScreen().getSharedPreferences()
- .getString(BrowserSettings.PREF_HOMEPAGE, null));
- ((BrowserHomepagePreference) e).setCurrentPage(
- getIntent().getStringExtra(CURRENT_PAGE));
-
- e = findPreference(BrowserSettings.PREF_EXTRAS_RESET_DEFAULTS);
- e.setOnPreferenceChangeListener(this);
-
- e = findPreference(BrowserSettings.PREF_TEXT_SIZE);
- e.setOnPreferenceChangeListener(this);
- e.setSummary(getVisualTextSizeName(
- getPreferenceScreen().getSharedPreferences()
- .getString(BrowserSettings.PREF_TEXT_SIZE, null)) );
-
- e = findPreference(BrowserSettings.PREF_DEFAULT_ZOOM);
- e.setOnPreferenceChangeListener(this);
- e.setSummary(getVisualDefaultZoomName(
- getPreferenceScreen().getSharedPreferences()
- .getString(BrowserSettings.PREF_DEFAULT_ZOOM, null)) );
-
- e = findPreference(BrowserSettings.PREF_DEFAULT_TEXT_ENCODING);
- e.setOnPreferenceChangeListener(this);
-
- e = findPreference(BrowserSettings.PREF_CLEAR_HISTORY);
- e.setOnPreferenceChangeListener(this);
+ public void onBuildHeaders(List<Header> target) {
+ loadHeadersFromResource(R.xml.preference_headers, target);
if (BrowserSettings.getInstance().showDebugSettings()) {
- addPreferencesFromResource(R.xml.debug_preferences);
+ Header debug = new Header();
+ debug.title = getText(R.string.pref_development_title);
+ debug.fragment = DebugPreferencesFragment.class.getName();
+ target.add(debug);
}
-
- PreferenceScreen websiteSettings = (PreferenceScreen)
- findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
- Intent intent = new Intent(this, WebsiteSettingsActivity.class);
- websiteSettings.setIntent(intent);
- }
-
- /*
- * We need to set the PreferenceScreen state in onResume(), as the number of
- * origins with active features (WebStorage, Geolocation etc) could have
- * changed after calling the WebsiteSettingsActivity.
- */
- @Override
- protected void onResume() {
- super.onResume();
- final PreferenceScreen websiteSettings = (PreferenceScreen)
- findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
- websiteSettings.setEnabled(false);
- WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
- public void onReceiveValue(Map webStorageOrigins) {
- if ((webStorageOrigins != null) && !webStorageOrigins.isEmpty()) {
- websiteSettings.setEnabled(true);
- }
- }
- });
- GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set<String> >() {
- public void onReceiveValue(Set<String> geolocationOrigins) {
- if ((geolocationOrigins != null) && !geolocationOrigins.isEmpty()) {
- websiteSettings.setEnabled(true);
- }
- }
- });
}
@Override
@@ -115,98 +49,6 @@ public class BrowserPreferencesPage extends PreferenceActivity
// sync the shared preferences back to BrowserSettings
BrowserSettings.getInstance().syncSharedPreferences(
getApplicationContext(),
- getPreferenceScreen().getSharedPreferences());
- }
-
- public boolean onPreferenceChange(Preference pref, Object objValue) {
- if (pref.getKey().equals(BrowserSettings.PREF_EXTRAS_RESET_DEFAULTS)) {
- Boolean value = (Boolean) objValue;
- if (value.booleanValue() == true) {
- finish();
- }
- } else if (pref.getKey().equals(BrowserSettings.PREF_HOMEPAGE)) {
- String value = (String) objValue;
- boolean needUpdate = value.indexOf(' ') != -1;
- if (needUpdate) {
- value = value.trim().replace(" ", "%20");
- }
- if (value.length() != 0 && Uri.parse(value).getScheme() == null) {
- value = "http://" + value;
- needUpdate = true;
- }
- // Set the summary value.
- pref.setSummary(value);
- if (needUpdate) {
- // Update through the EditText control as it has a cached copy
- // of the string and it will handle persisting the value
- ((EditTextPreference) pref).setText(value);
-
- // as we update the value above, we need to return false
- // here so that setText() is not called by EditTextPref
- // with the old value.
- return false;
- } else {
- return true;
- }
- } else if (pref.getKey().equals(BrowserSettings.PREF_TEXT_SIZE)) {
- pref.setSummary(getVisualTextSizeName((String) objValue));
- return true;
- } else if (pref.getKey().equals(BrowserSettings.PREF_DEFAULT_ZOOM)) {
- pref.setSummary(getVisualDefaultZoomName((String) objValue));
- return true;
- } else if (pref.getKey().equals(
- BrowserSettings.PREF_DEFAULT_TEXT_ENCODING)) {
- pref.setSummary((String) objValue);
- return true;
- } else if (pref.getKey().equals(BrowserSettings.PREF_CLEAR_HISTORY)
- && ((Boolean) objValue).booleanValue() == true) {
- // Need to tell the browser to remove the parent/child relationship
- // between tabs
- setResult(RESULT_OK, (new Intent()).putExtra(Intent.EXTRA_TEXT,
- pref.getKey()));
- return true;
- }
-
- return false;
- }
-
- private CharSequence getVisualTextSizeName(String enumName) {
- CharSequence[] visualNames = getResources().getTextArray(
- R.array.pref_text_size_choices);
- CharSequence[] enumNames = getResources().getTextArray(
- R.array.pref_text_size_values);
-
- // Sanity check
- if (visualNames.length != enumNames.length) {
- return "";
- }
-
- for (int i = 0; i < enumNames.length; i++) {
- if (enumNames[i].equals(enumName)) {
- return visualNames[i];
- }
- }
-
- return "";
- }
-
- private CharSequence getVisualDefaultZoomName(String enumName) {
- CharSequence[] visualNames = getResources().getTextArray(
- R.array.pref_default_zoom_choices);
- CharSequence[] enumNames = getResources().getTextArray(
- R.array.pref_default_zoom_values);
-
- // Sanity check
- if (visualNames.length != enumNames.length) {
- return "";
- }
-
- for (int i = 0; i < enumNames.length; i++) {
- if (enumNames[i].equals(enumName)) {
- return visualNames[i];
- }
- }
-
- return "";
+ PreferenceManager.getDefaultSharedPreferences(this));
}
}
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index 72ec81937..cba16a00c 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -29,8 +29,10 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.UriMatcher;
+import android.content.res.Configuration;
import android.database.AbstractCursor;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
@@ -38,6 +40,7 @@ import android.os.Process;
import android.preference.PreferenceManager;
import android.provider.Browser;
import android.provider.Browser.BookmarkColumns;
+import android.provider.Settings;
import android.speech.RecognizerResultsIntent;
import android.text.TextUtils;
import android.util.Log;
@@ -84,6 +87,17 @@ public class BrowserProvider extends ContentProvider {
private static final int SUGGEST_COLUMN_QUERY_ID = 8;
private static final int SUGGEST_COLUMN_INTENT_EXTRA_DATA = 9;
+ // how many suggestions will be shown in dropdown
+ // 0..SHORT: filled by browser db
+ private static final int MAX_SUGGEST_SHORT_SMALL = 3;
+ // SHORT..LONG: filled by search suggestions
+ private static final int MAX_SUGGEST_LONG_SMALL = 6;
+
+ // large screen size shows more
+ private static final int MAX_SUGGEST_SHORT_LARGE = 6;
+ private static final int MAX_SUGGEST_LONG_LARGE = 9;
+
+
// shared suggestion columns
private static final String[] COLUMNS = new String[] {
"_id",
@@ -97,10 +111,6 @@ public class BrowserProvider extends ContentProvider {
SearchManager.SUGGEST_COLUMN_QUERY,
SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA};
- private static final int MAX_SUGGESTION_SHORT_ENTRIES = 3;
- private static final int MAX_SUGGESTION_LONG_ENTRIES = 6;
- private static final String MAX_SUGGESTION_LONG_ENTRIES_STRING =
- Integer.valueOf(MAX_SUGGESTION_LONG_ENTRIES).toString();
// make sure that these match the index of TABLE_NAMES
private static final int URI_MATCH_BOOKMARKS = 0;
@@ -161,6 +171,9 @@ public class BrowserProvider extends ContentProvider {
private BrowserSettings mSettings;
+ private int mMaxSuggestionShortSize;
+ private int mMaxSuggestionLongSize;
+
public BrowserProvider() {
}
@@ -365,6 +378,20 @@ public class BrowserProvider extends ContentProvider {
@Override
public boolean onCreate() {
final Context context = getContext();
+ boolean xlargeScreenSize = (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK)
+ == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ boolean isPortrait = (context.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_PORTRAIT);
+
+
+ if (xlargeScreenSize && isPortrait) {
+ mMaxSuggestionLongSize = MAX_SUGGEST_LONG_LARGE;
+ mMaxSuggestionShortSize = MAX_SUGGEST_SHORT_LARGE;
+ } else {
+ mMaxSuggestionLongSize = MAX_SUGGEST_LONG_SMALL;
+ mMaxSuggestionShortSize = MAX_SUGGEST_SHORT_SMALL;
+ }
mOpenHelper = new DatabaseHelper(context);
mBackupManager = new BackupManager(context);
// we added "picasa web album" into default bookmarks for version 19.
@@ -431,10 +458,10 @@ public class BrowserProvider extends ContentProvider {
public MySuggestionCursor(Cursor hc, Cursor sc, String string) {
mHistoryCursor = hc;
mSuggestCursor = sc;
- mHistoryCount = hc.getCount();
+ mHistoryCount = hc != null ? hc.getCount() : 0;
mSuggestionCount = sc != null ? sc.getCount() : 0;
- if (mSuggestionCount > (MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount)) {
- mSuggestionCount = MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount;
+ if (mSuggestionCount > (mMaxSuggestionLongSize - mHistoryCount)) {
+ mSuggestionCount = mMaxSuggestionLongSize - mHistoryCount;
}
mString = string;
mIncludeWebSearch = string.length() > 0;
@@ -648,6 +675,7 @@ public class BrowserProvider extends ContentProvider {
}
// TODO Temporary change, finalize after jq's changes go in
+ @Override
public void deactivate() {
if (mHistoryCursor != null) {
mHistoryCursor.deactivate();
@@ -658,12 +686,14 @@ public class BrowserProvider extends ContentProvider {
super.deactivate();
}
+ @Override
public boolean requery() {
return (mHistoryCursor != null ? mHistoryCursor.requery() : false) |
(mSuggestCursor != null ? mSuggestCursor.requery() : false);
}
// TODO Temporary change, finalize after jq's changes go in
+ @Override
public void close() {
super.close();
if (mHistoryCursor != null) {
@@ -728,12 +758,15 @@ public class BrowserProvider extends ContentProvider {
public ResultsCursor(ArrayList<String> results) {
mResults = results;
}
+ @Override
public int getCount() { return mResults.size(); }
+ @Override
public String[] getColumnNames() {
return RESULTS_COLUMNS;
}
+ @Override
public String getString(int column) {
switch (column) {
case RESULT_ACTION_ID:
@@ -755,30 +788,39 @@ public class BrowserProvider extends ContentProvider {
return null;
}
}
+ @Override
public short getShort(int column) {
throw new UnsupportedOperationException();
}
+ @Override
public int getInt(int column) {
throw new UnsupportedOperationException();
}
+ @Override
public long getLong(int column) {
if ((mPos != -1) && column == 0) {
return mPos; // use row# as the _id
}
throw new UnsupportedOperationException();
}
+ @Override
public float getFloat(int column) {
throw new UnsupportedOperationException();
}
+ @Override
public double getDouble(int column) {
throw new UnsupportedOperationException();
}
+ @Override
public boolean isNull(int column) {
throw new UnsupportedOperationException();
}
}
+ /** Contains custom suggestions results set by the UI */
private ResultsCursor mResultsCursor;
+ /** Locks access to {@link #mResultsCursor} */
+ private Object mResultsCursorLock = new Object();
/**
* Provide a set of results to be returned to query, intended to be used
@@ -786,10 +828,12 @@ public class BrowserProvider extends ContentProvider {
* @param results Strings to display in the dropdown from the SearchDialog
*/
/* package */ void setQueryResults(ArrayList<String> results) {
- if (results == null) {
- mResultsCursor = null;
- } else {
- mResultsCursor = new ResultsCursor(results);
+ synchronized (mResultsCursorLock) {
+ if (results == null) {
+ mResultsCursor = null;
+ } else {
+ mResultsCursor = new ResultsCursor(results);
+ }
}
}
@@ -801,57 +845,19 @@ public class BrowserProvider extends ContentProvider {
if (match == -1) {
throw new IllegalArgumentException("Unknown URL");
}
- if (match == URI_MATCH_SUGGEST && mResultsCursor != null) {
- Cursor results = mResultsCursor;
- mResultsCursor = null;
- return results;
- }
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- if (match == URI_MATCH_SUGGEST || match == URI_MATCH_BOOKMARKS_SUGGEST) {
- String suggestSelection;
- String [] myArgs;
- if (selectionArgs[0] == null || selectionArgs[0].equals("")) {
- suggestSelection = null;
- myArgs = null;
- } else {
- String like = selectionArgs[0] + "%";
- if (selectionArgs[0].startsWith("http")
- || selectionArgs[0].startsWith("file")) {
- myArgs = new String[1];
- myArgs[0] = like;
- suggestSelection = selection;
- } else {
- SUGGEST_ARGS[0] = "http://" + like;
- SUGGEST_ARGS[1] = "http://www." + like;
- SUGGEST_ARGS[2] = "https://" + like;
- SUGGEST_ARGS[3] = "https://www." + like;
- // To match against titles.
- SUGGEST_ARGS[4] = like;
- myArgs = SUGGEST_ARGS;
- suggestSelection = SUGGEST_SELECTION;
- }
+ // If results for the suggestion are already ready just return them directly
+ synchronized (mResultsCursorLock) {
+ if (match == URI_MATCH_SUGGEST && mResultsCursor != null) {
+ Cursor results = mResultsCursor;
+ mResultsCursor = null;
+ return results;
}
+ }
- Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
- SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
- ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING);
-
- if (match == URI_MATCH_BOOKMARKS_SUGGEST
- || Patterns.WEB_URL.matcher(selectionArgs[0]).matches()) {
- return new MySuggestionCursor(c, null, "");
- } else {
- // get search suggestions if there is still space in the list
- if (myArgs != null && myArgs.length > 1
- && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) {
- 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]);
- }
+ if (match == URI_MATCH_SUGGEST || match == URI_MATCH_BOOKMARKS_SUGGEST) {
+ // Handle suggestions
+ return doSuggestQuery(selection, selectionArgs, match == URI_MATCH_BOOKMARKS_SUGGEST);
}
String[] projection = null;
@@ -861,27 +867,60 @@ public class BrowserProvider extends ContentProvider {
projection[projectionIn.length] = "_id AS _id";
}
- StringBuilder whereClause = new StringBuilder(256);
+ String whereClause = null;
if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
- whereClause.append("(_id = ").append(url.getPathSegments().get(1))
- .append(")");
+ whereClause = "_id = " + url.getPathSegments().get(1);
}
- // Tack on the user's selection, if present
- if (selection != null && selection.length() > 0) {
- if (whereClause.length() > 0) {
- whereClause.append(" AND ");
+ Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_NAMES[match % 10], projection,
+ DatabaseUtils.concatenateWhere(whereClause, selection), selectionArgs,
+ null, null, sortOrder, null);
+ c.setNotificationUri(getContext().getContentResolver(), url);
+ return c;
+ }
+
+ private Cursor doSuggestQuery(String selection, String[] selectionArgs, boolean bookmarksOnly) {
+ String suggestSelection;
+ String [] myArgs;
+ if (selectionArgs[0] == null || selectionArgs[0].equals("")) {
+ return new MySuggestionCursor(null, null, "");
+ } else {
+ String like = selectionArgs[0] + "%";
+ if (selectionArgs[0].startsWith("http")
+ || selectionArgs[0].startsWith("file")) {
+ myArgs = new String[1];
+ myArgs[0] = like;
+ suggestSelection = selection;
+ } else {
+ SUGGEST_ARGS[0] = "http://" + like;
+ SUGGEST_ARGS[1] = "http://www." + like;
+ SUGGEST_ARGS[2] = "https://" + like;
+ SUGGEST_ARGS[3] = "https://www." + like;
+ // To match against titles.
+ SUGGEST_ARGS[4] = like;
+ myArgs = SUGGEST_ARGS;
+ suggestSelection = SUGGEST_SELECTION;
}
+ }
+
+ Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
+ SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
+ ORDER_BY, Integer.toString(mMaxSuggestionLongSize));
- whereClause.append('(');
- whereClause.append(selection);
- whereClause.append(')');
+ if (bookmarksOnly || Patterns.WEB_URL.matcher(selectionArgs[0]).matches()) {
+ return new MySuggestionCursor(c, null, "");
+ } else {
+ // get search suggestions if there is still space in the list
+ if (myArgs != null && myArgs.length > 1
+ && c.getCount() < (MAX_SUGGEST_SHORT_SMALL - 1)) {
+ 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]);
}
- Cursor c = db.query(TABLE_NAMES[match % 10], projection,
- whereClause.toString(), selectionArgs, null, null, sortOrder,
- null);
- c.setNotificationUri(getContext().getContentResolver(), url);
- return c;
}
@Override
@@ -1089,4 +1128,10 @@ public class BrowserProvider extends ContentProvider {
}
}
+ public static Cursor getBookmarksSuggestions(ContentResolver cr, String constraint) {
+ Uri uri = Uri.parse("content://browser/" + SearchManager.SUGGEST_URI_PATH_QUERY);
+ return cr.query(uri, SUGGEST_PROJECTION, SUGGEST_SELECTION,
+ new String[] { constraint }, ORDER_BY);
+ }
+
}
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 3791eb07f..ba2f3fe85 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -28,9 +28,15 @@ import android.content.pm.ActivityInfo;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Handler;
+import android.os.Message;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
+import android.provider.Browser;
import android.provider.Settings;
import android.util.Log;
import android.webkit.CookieManager;
@@ -40,9 +46,9 @@ import android.webkit.WebView;
import android.webkit.WebViewDatabase;
import android.webkit.WebIconDatabase;
import android.webkit.WebSettings;
+import android.webkit.WebSettings.AutoFillProfile;
import android.webkit.WebStorage;
-import android.preference.PreferenceManager;
-import android.provider.Browser;
+import android.widget.Toast;
import java.util.HashMap;
import java.util.Map;
@@ -62,7 +68,7 @@ import java.util.Observable;
* To remove an observer:
* s.deleteObserver(webView.getSettings());
*/
-class BrowserSettings extends Observable {
+public class BrowserSettings extends Observable {
// Private variables for settings
// NOTE: these defaults need to be kept in sync with the XML
@@ -76,12 +82,12 @@ class BrowserSettings extends Observable {
private boolean showSecurityWarnings;
private boolean rememberPasswords;
private boolean saveFormData;
+ private boolean autoFillEnabled;
private boolean openInBackground;
private String defaultTextEncodingName;
private String homeUrl = "";
private SearchEngine searchEngine;
private boolean autoFitPage;
- private boolean landscapeOnly;
private boolean loadsPageInOverviewMode;
private boolean showDebugSettings;
// HTML5 API flags
@@ -109,14 +115,15 @@ class BrowserSettings extends Observable {
private boolean tracing = false;
private boolean lightTouch = false;
private boolean navDump = false;
+ private boolean hardwareAccelerated = true;
// By default the error console is shown once the user navigates to about:debug.
// The setting can be then toggled from the settings menu.
private boolean showConsole = true;
// Private preconfigured values
- private static int minimumFontSize = 8;
- private static int minimumLogicalFontSize = 8;
+ private static int minimumFontSize = 1;
+ private static int minimumLogicalFontSize = 1;
private static int defaultFontSize = 16;
private static int defaultFixedFontSize = 13;
private static WebSettings.TextSize textSize =
@@ -125,6 +132,15 @@ class BrowserSettings extends Observable {
WebSettings.ZoomDensity.MEDIUM;
private static int pageCacheCapacity;
+
+ private AutoFillProfile autoFillProfile;
+ // Default to zero. In the case no profile is set up, the initial
+ // value will come from the AutoFillSettingsFragment when the user
+ // creates a profile. Otherwise, we'll read the ID of the last used
+ // profile from the prefs db.
+ private int autoFillActiveProfileId;
+ private static final int NO_AUTOFILL_PROFILE_SET = 0;
+
// Preference keys that are used outside this class
public final static String PREF_CLEAR_CACHE = "privacy_clear_cache";
public final static String PREF_CLEAR_COOKIES = "privacy_clear_cookies";
@@ -145,6 +161,9 @@ class BrowserSettings extends Observable {
"default_text_encoding";
public final static String PREF_CLEAR_GEOLOCATION_ACCESS =
"privacy_clear_geolocation_access";
+ public final static String PREF_AUTOFILL_ENABLED = "autofill_enabled";
+ public final static String PREF_AUTOFILL_PROFILE = "autofill_profile";
+ public final static String PREF_AUTOFILL_ACTIVE_PROFILE_ID = "autofill_active_profile_id";
private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (Macintosh; " +
"U; Intel Mac OS X 10_6_3; en-us) AppleWebKit/533.16 (KHTML, " +
@@ -166,7 +185,11 @@ class BrowserSettings extends Observable {
// a ListView
public final static int MAX_TEXTVIEW_LEN = 80;
- private TabControl mTabControl;
+ public static final String RLZ_PROVIDER = "com.google.android.partnersetup.rlzappprovider";
+
+ public static final Uri RLZ_PROVIDER_URI = Uri.parse("content://" + RLZ_PROVIDER + "/");
+
+ private Controller mController;
// Single instance of the BrowserSettings for use in the Browser app.
private static BrowserSettings sSingleton;
@@ -176,6 +199,18 @@ class BrowserSettings extends Observable {
private HashMap<WebSettings,Observer> mWebSettingsToObservers =
new HashMap<WebSettings,Observer>();
+ private boolean mLoadFromDbComplete;
+
+ public void waitForLoadFromDbToComplete() {
+ synchronized (sSingleton) {
+ while (!mLoadFromDbComplete) {
+ try {
+ sSingleton.wait();
+ } catch (InterruptedException e) { }
+ }
+ }
+ }
+
/*
* An observer wrapper for updating a WebSettings object with the new
* settings after a call to BrowserSettings.update().
@@ -221,6 +256,7 @@ class BrowserSettings extends Observable {
s.setDefaultZoom(b.zoomDensity);
s.setLightTouchEnabled(b.lightTouch);
s.setSaveFormData(b.saveFormData);
+ s.setAutoFillEnabled(b.autoFillEnabled);
s.setSavePassword(b.rememberPasswords);
s.setLoadWithOverviewMode(b.loadsPageInOverviewMode);
s.setPageCacheCapacity(pageCacheCapacity);
@@ -229,6 +265,9 @@ class BrowserSettings extends Observable {
s.setNeedInitialFocus(false);
// Browser supports multiple windows
s.setSupportMultipleWindows(true);
+ // enable smooth transition for better performance during panning or
+ // zooming
+ s.setEnableSmoothTransition(true);
// HTML5 API flags
s.setAppCacheEnabled(b.appCacheEnabled);
@@ -243,56 +282,122 @@ class BrowserSettings extends Observable {
s.setDatabasePath(b.databasePath);
s.setGeolocationDatabasePath(b.geolocationDatabasePath);
+ // Active AutoFill profile data.
+ s.setAutoFillProfile(b.autoFillProfile);
+
b.updateTabControlSettings();
}
}
/**
- * Load settings from the browser app's database.
+ * Load settings from the browser app's database. It is performed in
+ * an AsyncTask as it involves plenty of slow disk IO.
* NOTE: Strings used for the preferences must match those specified
- * in the browser_preferences.xml
+ * in the various preference XML files.
* @param ctx A Context object used to query the browser's settings
* database. If the database exists, the saved settings will be
* stored in this BrowserSettings object. This will update all
* observers of this object.
*/
- public void loadFromDb(final Context ctx) {
- SharedPreferences p =
- PreferenceManager.getDefaultSharedPreferences(ctx);
- // Set the default value for the Application Caches path.
- appCachePath = ctx.getDir("appcache", 0).getPath();
- // Determine the maximum size of the application cache.
- webStorageSizeManager = new WebStorageSizeManager(
- ctx,
- new WebStorageSizeManager.StatFsDiskInfo(appCachePath),
- new WebStorageSizeManager.WebKitAppCacheInfo(appCachePath));
- appCacheMaxSize = webStorageSizeManager.getAppCacheMaxSize();
- // Set the default value for the Database path.
- databasePath = ctx.getDir("databases", 0).getPath();
- // Set the default value for the Geolocation database path.
- geolocationDatabasePath = ctx.getDir("geolocation", 0).getPath();
-
- if (p.getString(PREF_HOMEPAGE, "") == "") {
- // No home page preferences is set, set it to default.
- setHomePage(ctx, getFactoryResetHomeUrl(ctx));
- }
+ public void asyncLoadFromDb(final Context ctx) {
+ mLoadFromDbComplete = false;
+ // Run the initial settings load in an AsyncTask as it hits the
+ // disk multiple times through SharedPreferences and SQLite. We
+ // need to be certain though that this has completed before we start
+ // to load pages though, so in the worst case we will block waiting
+ // for it to finish in BrowserActivity.onCreate().
+ new LoadFromDbTask(ctx).execute();
+ }
- // the cost of one cached page is ~3M (measured using nytimes.com). For
- // low end devices, we only cache one page. For high end devices, we try
- // to cache more pages, currently choose 5.
- ActivityManager am = (ActivityManager) ctx
- .getSystemService(Context.ACTIVITY_SERVICE);
- if (am.getMemoryClass() > 16) {
- pageCacheCapacity = 5;
- } else {
- pageCacheCapacity = 1;
+ private class LoadFromDbTask extends AsyncTask<Void, Void, Void> {
+ private Context mContext;
+
+ public LoadFromDbTask(Context context) {
+ mContext = context;
}
- // 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(ctx, p);
+ protected Void doInBackground(Void... unused) {
+ SharedPreferences p =
+ PreferenceManager.getDefaultSharedPreferences(mContext);
+ // Set the default value for the Application Caches path.
+ appCachePath = mContext.getDir("appcache", 0).getPath();
+ // Determine the maximum size of the application cache.
+ webStorageSizeManager = new WebStorageSizeManager(
+ mContext,
+ new WebStorageSizeManager.StatFsDiskInfo(appCachePath),
+ new WebStorageSizeManager.WebKitAppCacheInfo(appCachePath));
+ appCacheMaxSize = webStorageSizeManager.getAppCacheMaxSize();
+ // Set the default value for the Database path.
+ databasePath = mContext.getDir("databases", 0).getPath();
+ // Set the default value for the Geolocation database path.
+ geolocationDatabasePath = mContext.getDir("geolocation", 0).getPath();
+
+ if (p.getString(PREF_HOMEPAGE, "") == "") {
+ // No home page preferences is set, set it to default.
+ setHomePage(mContext, getFactoryResetHomeUrl(mContext));
+ }
+
+ // the cost of one cached page is ~3M (measured using nytimes.com). For
+ // low end devices, we only cache one page. For high end devices, we try
+ // to cache more pages, currently choose 5.
+ ActivityManager am = (ActivityManager) mContext
+ .getSystemService(Context.ACTIVITY_SERVICE);
+ if (am.getMemoryClass() > 16) {
+ pageCacheCapacity = 5;
+ } else {
+ pageCacheCapacity = 1;
+ }
+
+ // Read the last active AutoFill profile id.
+ autoFillActiveProfileId = p.getInt(
+ PREF_AUTOFILL_ACTIVE_PROFILE_ID, autoFillActiveProfileId);
+
+ // Load the autofill profile data from the database. We use a database separate
+ // to the browser preference DB to make it easier to support multiple profiles
+ // and switching between them.
+ AutoFillProfileDatabase autoFillDb = AutoFillProfileDatabase.getInstance(mContext);
+ Cursor c = autoFillDb.getProfile(autoFillActiveProfileId);
+
+ if (c.getCount() > 0) {
+ c.moveToFirst();
+
+ String fullName = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.FULL_NAME));
+ String email = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.EMAIL_ADDRESS));
+ String company = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.COMPANY_NAME));
+ String addressLine1 = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.ADDRESS_LINE_1));
+ String addressLine2 = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.ADDRESS_LINE_2));
+ String city = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.CITY));
+ String state = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.STATE));
+ String zip = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.ZIP_CODE));
+ String country = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.COUNTRY));
+ String phone = c.getString(c.getColumnIndex(
+ AutoFillProfileDatabase.Profiles.PHONE_NUMBER));
+ autoFillProfile = new AutoFillProfile(autoFillActiveProfileId,
+ fullName, email, company, addressLine1, addressLine2, city,
+ state, zip, country, phone);
+ }
+ c.close();
+ autoFillDb.close();
+
+ // PreferenceManager.setDefaultValues is TOO SLOW, need to manually keep
+ // the defaults in sync
+ syncSharedPreferences(mContext, p);
+
+ synchronized (sSingleton) {
+ mLoadFromDbComplete = true;
+ sSingleton.notify();
+ }
+ return null;
+ }
}
/* package */ void syncSharedPreferences(Context ctx, SharedPreferences p) {
@@ -307,8 +412,8 @@ class BrowserSettings extends Observable {
// 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();
+ for (int i = 0; i < mController.getTabControl().getTabCount(); i++) {
+ mController.getTabControl().getTab(i).revertVoiceSearchMode();
}
}
searchEngine.close();
@@ -332,6 +437,7 @@ class BrowserSettings extends Observable {
rememberPasswords);
saveFormData = p.getBoolean("save_formdata",
saveFormData);
+ autoFillEnabled = p.getBoolean("autofill_enabled", autoFillEnabled);
boolean accept_cookies = p.getBoolean("accept_cookies",
CookieManager.getInstance().acceptCookie());
CookieManager.getInstance().setAcceptCookie(accept_cookies);
@@ -343,11 +449,6 @@ class BrowserSettings extends Observable {
autoFitPage = p.getBoolean("autofit_pages", autoFitPage);
loadsPageInOverviewMode = p.getBoolean("load_page",
loadsPageInOverviewMode);
- boolean landscapeOnlyTemp =
- p.getBoolean("landscape_only", landscapeOnly);
- if (landscapeOnlyTemp != landscapeOnly) {
- landscapeOnly = landscapeOnlyTemp;
- }
useWideViewPort = true; // use wide view port for either setting
if (autoFitPage) {
layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS;
@@ -383,6 +484,12 @@ class BrowserSettings extends Observable {
navDump = p.getBoolean("enable_nav_dump", navDump);
userAgent = Integer.parseInt(p.getString("user_agent", "0"));
}
+
+ // This setting can only be modified when the debug settings have been
+ // enabled but it is read and used by the browser at startup so we must
+ // initialize it regardless of the status of the debug settings.
+ hardwareAccelerated = p.getBoolean("enable_hardware_accel", hardwareAccelerated);
+
// JS flags is loaded from DB even if showDebugSettings is false,
// so that it can be set once and be effective all the time.
jsFlags = p.getString("js_engine_flags", "");
@@ -456,6 +563,10 @@ class BrowserSettings extends Observable {
return navDump;
}
+ public boolean isHardwareAccelerated() {
+ return hardwareAccelerated;
+ }
+
public boolean showDebugSettings() {
return showDebugSettings;
}
@@ -466,6 +577,40 @@ class BrowserSettings extends Observable {
update();
}
+ public void setAutoFillProfile(Context ctx, AutoFillProfile profile, Message msg) {
+ if (profile != null) {
+ setActiveAutoFillProfileId(ctx, profile.getUniqueId());
+ // Update the AutoFill DB with the new profile.
+ new SaveProfileToDbTask(ctx, msg).execute(profile);
+ } else {
+ // Delete the current profile.
+ if (autoFillProfile != null) {
+ new DeleteProfileFromDbTask(ctx, msg).execute(autoFillProfile.getUniqueId());
+ setActiveAutoFillProfileId(ctx, NO_AUTOFILL_PROFILE_SET);
+ }
+ }
+ autoFillProfile = profile;
+ }
+
+ public AutoFillProfile getAutoFillProfile() {
+ return autoFillProfile;
+ }
+
+ private void setActiveAutoFillProfileId(Context context, int activeProfileId) {
+ autoFillActiveProfileId = activeProfileId;
+ Editor ed = PreferenceManager.
+ getDefaultSharedPreferences(context).edit();
+ ed.putInt(PREF_AUTOFILL_ACTIVE_PROFILE_ID, activeProfileId);
+ ed.apply();
+ }
+
+ /* package */ void disableAutoFill(Context ctx) {
+ autoFillEnabled = false;
+ Editor ed = PreferenceManager.getDefaultSharedPreferences(ctx).edit();
+ ed.putBoolean(PREF_AUTOFILL_ENABLED, false);
+ ed.apply();
+ }
+
/**
* Add a WebSettings object to the list of observers that will be updated
* when update() is called.
@@ -510,8 +655,8 @@ class BrowserSettings extends Observable {
/*
* Package level method for associating the BrowserSettings with TabControl
*/
- /* package */void setTabControl(TabControl tabControl) {
- mTabControl = tabControl;
+ /* package */void setController(Controller ctrl) {
+ mController = ctrl;
updateTabControlSettings();
}
@@ -525,8 +670,8 @@ class BrowserSettings extends Observable {
/*package*/ void clearCache(Context context) {
WebIconDatabase.getInstance().removeAllIcons();
- if (mTabControl != null) {
- WebView current = mTabControl.getCurrentWebView();
+ if (mController != null) {
+ WebView current = mController.getCurrentWebView();
if (current != null) {
current.clearCache(true);
}
@@ -545,8 +690,8 @@ class BrowserSettings extends Observable {
/* package */ void clearFormData(Context context) {
WebViewDatabase.getInstance(context).clearFormData();
- if (mTabControl != null) {
- WebView currentTopView = mTabControl.getCurrentTopWebView();
+ if (mController!= null) {
+ WebView currentTopView = mController.getCurrentTopWebView();
if (currentTopView != null) {
currentTopView.clearFormData();
}
@@ -561,59 +706,35 @@ class BrowserSettings extends Observable {
private void updateTabControlSettings() {
// Enable/disable the error console.
- mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+ mController.setShouldShowErrorConsole(
showDebugSettings && showConsole);
- mTabControl.getBrowserActivity().setRequestedOrientation(
- landscapeOnly ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
- : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- }
-
- private void maybeDisableWebsiteSettings(Context context) {
- PreferenceActivity activity = (PreferenceActivity) context;
- final PreferenceScreen screen = (PreferenceScreen)
- activity.findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
- screen.setEnabled(false);
- WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
- public void onReceiveValue(Map webStorageOrigins) {
- if ((webStorageOrigins != null) && !webStorageOrigins.isEmpty()) {
- screen.setEnabled(true);
- }
- }
- });
-
- GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set<String> >() {
- public void onReceiveValue(Set<String> geolocationOrigins) {
- if ((geolocationOrigins != null) && !geolocationOrigins.isEmpty()) {
- screen.setEnabled(true);
- }
- }
- });
}
/*package*/ void clearDatabases(Context context) {
WebStorage.getInstance().deleteAllData();
- maybeDisableWebsiteSettings(context);
}
/*package*/ void clearLocationAccess(Context context) {
GeolocationPermissions.getInstance().clearAll();
- maybeDisableWebsiteSettings(context);
}
/*package*/ void resetDefaultPreferences(Context ctx) {
reset();
- SharedPreferences p =
- PreferenceManager.getDefaultSharedPreferences(ctx);
+ SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(ctx);
p.edit().clear().apply();
- PreferenceManager.setDefaultValues(ctx, R.xml.browser_preferences,
- true);
+ PreferenceManager.setDefaultValues(ctx, R.xml.page_content_preferences, true);
+ PreferenceManager.setDefaultValues(ctx, R.xml.personal_preferences, true);
+ PreferenceManager.setDefaultValues(ctx, R.xml.privacy_preferences, true);
+ PreferenceManager.setDefaultValues(ctx, R.xml.security_preferences, true);
+ PreferenceManager.setDefaultValues(ctx, R.xml.advanced_preferences, true);
// reset homeUrl
setHomePage(ctx, getFactoryResetHomeUrl(ctx));
// reset appcache max size
appCacheMaxSize = webStorageSizeManager.getAppCacheMaxSize();
+ setActiveAutoFillProfileId(ctx, NO_AUTOFILL_PROFILE_SET);
}
- private String getFactoryResetHomeUrl(Context context) {
+ /*package*/ static String getFactoryResetHomeUrl(Context context) {
String url = context.getResources().getString(R.string.homepage_base);
if (url.indexOf("{CID}") != -1) {
url = url.replace("{CID}",
@@ -639,9 +760,9 @@ class BrowserSettings extends Observable {
showSecurityWarnings = true;
rememberPasswords = true;
saveFormData = true;
+ autoFillEnabled = true;
openInBackground = false;
autoFitPage = true;
- landscapeOnly = false;
loadsPageInOverviewMode = true;
showDebugSettings = false;
// HTML5 API flags
@@ -651,4 +772,53 @@ class BrowserSettings extends Observable {
geolocationEnabled = true;
workersEnabled = true; // only affects V8. JSC does not have a similar setting
}
+
+ private abstract class AutoFillProfileDbTask<T> extends AsyncTask<T, Void, Void> {
+ Context mContext;
+ AutoFillProfileDatabase mAutoFillProfileDb;
+ Message mCompleteMessage;
+
+ public AutoFillProfileDbTask(Context ctx, Message msg) {
+ mContext = ctx;
+ mCompleteMessage = msg;
+ }
+
+ protected void onPostExecute(Void result) {
+ if (mCompleteMessage != null) {
+ mCompleteMessage.sendToTarget();
+ }
+ mAutoFillProfileDb.close();
+ }
+
+ abstract protected Void doInBackground(T... values);
+ }
+
+
+ private class SaveProfileToDbTask extends AutoFillProfileDbTask<AutoFillProfile> {
+ public SaveProfileToDbTask(Context ctx, Message msg) {
+ super(ctx, msg);
+ }
+
+ protected Void doInBackground(AutoFillProfile... values) {
+ mAutoFillProfileDb = AutoFillProfileDatabase.getInstance(mContext);
+ assert autoFillActiveProfileId != NO_AUTOFILL_PROFILE_SET;
+ AutoFillProfile newProfile = values[0];
+ mAutoFillProfileDb.addOrUpdateProfile(autoFillActiveProfileId, newProfile);
+ return null;
+ }
+ }
+
+ private class DeleteProfileFromDbTask extends AutoFillProfileDbTask<Integer> {
+ public DeleteProfileFromDbTask(Context ctx, Message msg) {
+ super(ctx, msg);
+ }
+
+ protected Void doInBackground(Integer... values) {
+ mAutoFillProfileDb = AutoFillProfileDatabase.getInstance(mContext);
+ int id = values[0];
+ assert id > 0;
+ mAutoFillProfileDb.dropProfile(id);
+ return null;
+ }
+ }
}
diff --git a/src/com/android/browser/CombinedBookmarkHistoryActivity.java b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
deleted file mode 100644
index 194956f14..000000000
--- a/src/com/android/browser/CombinedBookmarkHistoryActivity.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2009 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.Activity;
-import android.app.TabActivity;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.Browser;
-import android.webkit.WebIconDatabase;
-import android.webkit.WebIconDatabase.IconListener;
-import android.widget.TabHost;
-
-import java.util.HashMap;
-import java.util.Vector;
-
-public class CombinedBookmarkHistoryActivity extends TabActivity
- implements TabHost.OnTabChangeListener {
- /**
- * Used to inform BrowserActivity to remove the parent/child relationships
- * from all the tabs.
- */
- private String mExtraData;
- /**
- * Intent to be passed to calling Activity when finished. Keep a pointer to
- * it locally so mExtraData can be added.
- */
- private Intent mResultData;
- /**
- * Result code to pass back to calling Activity when finished.
- */
- private int mResultCode;
-
- /* package */ static String BOOKMARKS_TAB = "bookmark";
- /* package */ static String VISITED_TAB = "visited";
- /* package */ static String HISTORY_TAB = "history";
- /* package */ static String STARTING_TAB = "tab";
-
- static class IconListenerSet implements IconListener {
- // Used to store favicons as we get them from the database
- // FIXME: We use a different method to get the Favicons in
- // BrowserBookmarksAdapter. They should probably be unified.
- private HashMap<String, Bitmap> mUrlsToIcons;
- private Vector<IconListener> mListeners;
-
- public IconListenerSet() {
- mUrlsToIcons = new HashMap<String, Bitmap>();
- mListeners = new Vector<IconListener>();
- }
- public void onReceivedIcon(String url, Bitmap icon) {
- mUrlsToIcons.put(url, icon);
- for (IconListener listener : mListeners) {
- listener.onReceivedIcon(url, icon);
- }
- }
- public void addListener(IconListener listener) {
- mListeners.add(listener);
- }
- public void removeListener(IconListener listener) {
- mListeners.remove(listener);
- }
- public Bitmap getFavicon(String url) {
- return (Bitmap) mUrlsToIcons.get(url);
- }
- }
- private static IconListenerSet sIconListenerSet;
- static IconListenerSet getIconListenerSet() {
- if (null == sIconListenerSet) {
- sIconListenerSet = new IconListenerSet();
- }
- return sIconListenerSet;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tabs);
-
- setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
-
- getTabHost().setOnTabChangedListener(this);
-
- Bundle extras = getIntent().getExtras();
-
- Intent bookmarksIntent = new Intent(this, BrowserBookmarksPage.class);
- if (extras != null) {
- bookmarksIntent.putExtras(extras);
- }
- createTab(bookmarksIntent, R.string.tab_bookmarks,
- R.drawable.browser_bookmark_tab, BOOKMARKS_TAB);
-
- Intent visitedIntent = new Intent(this, BrowserBookmarksPage.class);
- // Need to copy extras so the bookmarks activity and this one will be
- // different
- Bundle visitedExtras = extras == null ? new Bundle() : new Bundle(extras);
- visitedExtras.putBoolean("mostVisited", true);
- visitedIntent.putExtras(visitedExtras);
- createTab(visitedIntent, R.string.tab_most_visited,
- R.drawable.browser_visited_tab, VISITED_TAB);
-
- Intent historyIntent = new Intent(this, BrowserHistoryPage.class);
- String defaultTab = null;
- if (extras != null) {
- historyIntent.putExtras(extras);
- defaultTab = extras.getString(STARTING_TAB);
- }
- createTab(historyIntent, R.string.tab_history,
- R.drawable.browser_history_tab, HISTORY_TAB);
-
- if (defaultTab != null) {
- getTabHost().setCurrentTab(2);
- }
-
- // XXX: Must do this before launching the AsyncTask to avoid a
- // potential crash if the icon database has not been created.
- WebIconDatabase.getInstance();
- // Do this every time we launch the activity in case a new favicon was
- // added to the webkit db.
- (new AsyncTask<Void, Void, Void>() {
- public Void doInBackground(Void... v) {
- Browser.requestAllIcons(getContentResolver(),
- Browser.BookmarkColumns.FAVICON + " is NULL",
- getIconListenerSet());
- return null;
- }
- }).execute();
- }
-
- private void createTab(Intent intent, int labelResId, int iconResId,
- String tab) {
- Resources resources = getResources();
- TabHost tabHost = getTabHost();
- tabHost.addTab(tabHost.newTabSpec(tab).setIndicator(
- resources.getText(labelResId), resources.getDrawable(iconResId))
- .setContent(intent));
- }
- // Copied from DialTacts Activity
- /** {@inheritDoc} */
- public void onTabChanged(String tabId) {
- Activity activity = getLocalActivityManager().getActivity(tabId);
- if (activity != null) {
- activity.onWindowFocusChanged(true);
- }
- }
-
- /**
- * Store extra data in the Intent to return to the calling Activity to tell
- * it to clear the parent/child relationships from all tabs.
- */
- /* package */ void removeParentChildRelationShips() {
- mExtraData = BrowserSettings.PREF_CLEAR_HISTORY;
- }
-
- /**
- * Custom setResult() method so that the Intent can have extra data attached
- * if necessary.
- * @param resultCode Uses same codes as Activity.setResult
- * @param data Intent returned to onActivityResult.
- */
- /* package */ void setResultFromChild(int resultCode, Intent data) {
- mResultCode = resultCode;
- mResultData = data;
- }
-
- @Override
- public void finish() {
- if (mExtraData != null) {
- mResultCode = RESULT_OK;
- if (mResultData == null) mResultData = new Intent();
- mResultData.putExtra(Intent.EXTRA_TEXT, mExtraData);
- }
- setResult(mResultCode, mResultData);
- super.finish();
- }
-}
diff --git a/src/com/android/browser/CombinedBookmarkHistoryView.java b/src/com/android/browser/CombinedBookmarkHistoryView.java
new file mode 100644
index 000000000..15f31f603
--- /dev/null
+++ b/src/com/android/browser/CombinedBookmarkHistoryView.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2009 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.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.Browser;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.webkit.WebIconDatabase;
+import android.webkit.WebIconDatabase.IconListener;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.HashMap;
+import java.util.Vector;
+
+interface BookmarksHistoryCallbacks {
+ public void onUrlSelected(String url, boolean newWindow);
+ public void onRemoveParentChildRelationships();
+ public void onComboCanceled();
+}
+
+public class CombinedBookmarkHistoryView extends LinearLayout
+ implements OnClickListener {
+
+ final static String STARTING_FRAGMENT = "fragment";
+
+ final static int FRAGMENT_ID_BOOKMARKS = 1;
+ final static int FRAGMENT_ID_HISTORY = 2;
+
+ private UiController mUiController;
+ private Activity mActivity;
+
+ private Bundle mExtras;
+
+ long mCurrentFragment;
+
+ View mTabs;
+ TextView mTabBookmarks;
+ TextView mTabHistory;
+ TextView mAddBookmark;
+ View mSeperateSelectAdd;
+ ViewGroup mBookmarksHeader;
+
+ BrowserBookmarksPage mBookmarks;
+ BrowserHistoryPage mHistory;
+
+ static class IconListenerSet implements IconListener {
+ // Used to store favicons as we get them from the database
+ // FIXME: We use a different method to get the Favicons in
+ // BrowserBookmarksAdapter. They should probably be unified.
+ private HashMap<String, Bitmap> mUrlsToIcons;
+ private Vector<IconListener> mListeners;
+
+ public IconListenerSet() {
+ mUrlsToIcons = new HashMap<String, Bitmap>();
+ mListeners = new Vector<IconListener>();
+ }
+ @Override
+ public void onReceivedIcon(String url, Bitmap icon) {
+ mUrlsToIcons.put(url, icon);
+ for (IconListener listener : mListeners) {
+ listener.onReceivedIcon(url, icon);
+ }
+ }
+ public void addListener(IconListener listener) {
+ mListeners.add(listener);
+ }
+ public void removeListener(IconListener listener) {
+ mListeners.remove(listener);
+ }
+ public Bitmap getFavicon(String url) {
+ return mUrlsToIcons.get(url);
+ }
+ }
+
+ private static IconListenerSet sIconListenerSet;
+ static IconListenerSet getIconListenerSet() {
+ if (null == sIconListenerSet) {
+ sIconListenerSet = new IconListenerSet();
+ }
+ return sIconListenerSet;
+ }
+
+ public CombinedBookmarkHistoryView(Activity activity, UiController controller,
+ int startingFragment, Bundle extras) {
+ super(activity);
+ mUiController = controller;
+ mActivity = activity;
+ mExtras = extras;
+ View v = LayoutInflater.from(activity).inflate(R.layout.bookmarks_history, this);
+ Resources res = activity.getResources();
+
+// setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+
+ mTabs = findViewById(R.id.tabs);
+ mBookmarksHeader = (ViewGroup) findViewById(R.id.header_container);
+
+ mTabBookmarks = (TextView) findViewById(R.id.bmtab);
+ mTabHistory = (TextView) findViewById(R.id.historytab);
+ mAddBookmark = (TextView) findViewById(R.id.addbm);
+ mSeperateSelectAdd = findViewById(R.id.seperate_select_add);
+ mAddBookmark.setOnClickListener(this);
+ mTabHistory.setOnClickListener(this);
+ mTabBookmarks.setOnClickListener(this);
+ // Start up the default fragment
+ initFragments(mExtras);
+ loadFragment(startingFragment, mExtras, false);
+
+ // XXX: Must do this before launching the AsyncTask to avoid a
+ // potential crash if the icon database has not been created.
+ WebIconDatabase.getInstance();
+
+ // Do this every time the view is created in case a new favicon was
+ // added to the webkit db.
+ (new AsyncTask<Void, Void, Void>() {
+ @Override
+ public Void doInBackground(Void... v) {
+ Browser.requestAllIcons(mActivity.getContentResolver(),
+ Browser.BookmarkColumns.FAVICON + " is NULL", getIconListenerSet());
+ return null;
+ }
+ }).execute();
+
+ }
+
+ private BookmarksPageCallbacks mBookmarkCallbackWrapper = new BookmarksPageCallbacks() {
+ @Override
+ public boolean onOpenInNewWindow(Cursor c) {
+ mUiController.onUrlSelected(BrowserBookmarksPage.getUrl(c), true);
+ return true;
+ }
+
+ @Override
+ public boolean onBookmarkSelected(Cursor c, boolean isFolder) {
+ if (isFolder) {
+ return false;
+ }
+ mUiController.onUrlSelected(BrowserBookmarksPage.getUrl(c), false);
+ return true;
+ }
+ };
+
+ private void initFragments(Bundle extras) {
+ mBookmarks = BrowserBookmarksPage.newInstance(mBookmarkCallbackWrapper,
+ extras, mBookmarksHeader);
+ mHistory = BrowserHistoryPage.newInstance(mUiController, extras);
+ }
+
+ private void loadFragment(int id, Bundle extras, boolean notify) {
+ String fragmentClassName;
+ Fragment fragment = null;
+ switch (id) {
+ case FRAGMENT_ID_BOOKMARKS:
+ fragment = mBookmarks;
+ mSeperateSelectAdd.setVisibility(View.VISIBLE);
+ mBookmarksHeader.setVisibility(View.VISIBLE);
+ break;
+ case FRAGMENT_ID_HISTORY:
+ fragment = mHistory;
+ mBookmarksHeader.setVisibility(View.INVISIBLE);
+ mSeperateSelectAdd.setVisibility(View.INVISIBLE);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ mCurrentFragment = id;
+
+ FragmentManager fm = mActivity.getFragmentManager();
+ FragmentTransaction transaction = fm.openTransaction();
+ transaction.replace(R.id.fragment, fragment);
+ transaction.commit();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ FragmentManager fm = mActivity.getFragmentManager();
+ FragmentTransaction transaction = fm.openTransaction();
+ if (mCurrentFragment == FRAGMENT_ID_BOOKMARKS) {
+ transaction.remove(mBookmarks);
+ } else if (mCurrentFragment == FRAGMENT_ID_HISTORY) {
+ transaction.remove(mHistory);
+ }
+ transaction.commit();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if ((mTabHistory == view) && (mCurrentFragment != FRAGMENT_ID_HISTORY)) {
+ loadFragment(FRAGMENT_ID_HISTORY, mExtras, false);
+ } else if (mTabBookmarks == view) {
+ if (mCurrentFragment != FRAGMENT_ID_BOOKMARKS) {
+ loadFragment(FRAGMENT_ID_BOOKMARKS, mExtras, true);
+ } else {
+ BreadCrumbView crumbs = mBookmarks.getBreadCrumb();
+ if (crumbs != null) {
+ crumbs.clear();
+ }
+ }
+ } else if (mAddBookmark == view) {
+ mUiController.bookmarkCurrentPage(mBookmarks.getFolderId());
+ }
+ }
+
+ /**
+ * callback for back key presses
+ */
+ boolean onBackPressed() {
+ if (mCurrentFragment == FRAGMENT_ID_BOOKMARKS) {
+ return mBookmarks.onBackPressed();
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
new file mode 100644
index 000000000..acd76ddf7
--- /dev/null
+++ b/src/com/android/browser/Controller.java
@@ -0,0 +1,2453 @@
+/*
+ * 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 com.android.browser.IntentHandler.UrlData;
+import com.android.browser.search.SearchEngine;
+import com.android.common.Search;
+
+import android.app.Activity;
+import android.app.DownloadManager;
+import android.app.SearchManager;
+import android.content.ClipboardManager;
+import android.content.ContentProvider;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Picture;
+import android.net.Uri;
+import android.net.http.SslError;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.preference.PreferenceActivity;
+import android.provider.Browser;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.History;
+import android.provider.BrowserContract.Images;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Intents.Insert;
+import android.speech.RecognizerResultsIntent;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.View;
+import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebIconDatabase;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.widget.TextView;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.net.URLEncoder;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Controller for browser
+ */
+public class Controller
+ implements WebViewController, UiController {
+
+ private static final String LOGTAG = "Controller";
+
+ // public message ids
+ public final static int LOAD_URL = 1001;
+ public final static int STOP_LOAD = 1002;
+
+ // Message Ids
+ private static final int FOCUS_NODE_HREF = 102;
+ private static final int RELEASE_WAKELOCK = 107;
+
+ static final int UPDATE_BOOKMARK_THUMBNAIL = 108;
+
+ private static final int OPEN_BOOKMARKS = 201;
+
+ private static final int EMPTY_MENU = -1;
+
+ // Keep this initial progress in sync with initialProgressValue (* 100)
+ // in ProgressTracker.cpp
+ private final static int INITIAL_PROGRESS = 10;
+
+ // activity requestCode
+ final static int PREFERENCES_PAGE = 3;
+ final static int FILE_SELECTED = 4;
+ final static int AUTOFILL_SETUP = 5;
+
+ private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
+
+ // As the ids are dynamically created, we can't guarantee that they will
+ // be in sequence, so this static array maps ids to a window number.
+ final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
+ { R.id.window_one_menu_id, R.id.window_two_menu_id,
+ R.id.window_three_menu_id, R.id.window_four_menu_id,
+ R.id.window_five_menu_id, R.id.window_six_menu_id,
+ R.id.window_seven_menu_id, R.id.window_eight_menu_id };
+
+ // "source" parameter for Google search through search key
+ final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
+ // "source" parameter for Google search through simplily type
+ final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
+
+ private Activity mActivity;
+ private UI mUi;
+ private TabControl mTabControl;
+ private BrowserSettings mSettings;
+ private WebViewFactory mFactory;
+
+ private WakeLock mWakeLock;
+
+ private UrlHandler mUrlHandler;
+ private UploadHandler mUploadHandler;
+ private IntentHandler mIntentHandler;
+ private PageDialogsHandler mPageDialogsHandler;
+ private NetworkStateHandler mNetworkHandler;
+
+ private Message mAutoFillSetupMessage;
+
+ private boolean mShouldShowErrorConsole;
+
+ private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins;
+
+ // FIXME, temp address onPrepareMenu performance problem.
+ // When we move everything out of view, we should rewrite this.
+ private int mCurrentMenuState = 0;
+ private int mMenuState = R.id.MAIN_MENU;
+ private int mOldMenuState = EMPTY_MENU;
+ private Menu mCachedMenu;
+
+ // Used to prevent chording to result in firing two shortcuts immediately
+ // one after another. Fixes bug 1211714.
+ boolean mCanChord;
+ private boolean mMenuIsDown;
+
+ // For select and find, we keep track of the ActionMode so that
+ // finish() can be called as desired.
+ private ActionMode mActionMode;
+
+ /**
+ * Only meaningful when mOptionsMenuOpen is true. This variable keeps track
+ * of whether the configuration has changed. The first onMenuOpened call
+ * after a configuration change is simply a reopening of the same menu
+ * (i.e. mIconView did not change).
+ */
+ private boolean mConfigChanged;
+
+ /**
+ * Keeps track of whether the options menu is open. This is important in
+ * determining whether to show or hide the title bar overlay
+ */
+ private boolean mOptionsMenuOpen;
+
+ /**
+ * Whether or not the options menu is in its bigger, popup menu form. When
+ * true, we want the title bar overlay to be gone. When false, we do not.
+ * Only meaningful if mOptionsMenuOpen is true.
+ */
+ private boolean mExtendedMenuOpen;
+
+ private boolean mInLoad;
+
+ private boolean mActivityPaused = true;
+ private boolean mLoadStopped;
+
+ private Handler mHandler;
+
+ private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
+ @Override
+ public Void doInBackground(File... files) {
+ if (files != null) {
+ for (File f : files) {
+ if (!f.delete()) {
+ Log.e(LOGTAG, f.getPath() + " was not deleted");
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ public Controller(Activity browser) {
+ mActivity = browser;
+ mSettings = BrowserSettings.getInstance();
+ mTabControl = new TabControl(this);
+ mSettings.setController(this);
+
+ mUrlHandler = new UrlHandler(this);
+ mIntentHandler = new IntentHandler(mActivity, this);
+ mPageDialogsHandler = new PageDialogsHandler(mActivity, this);
+
+ PowerManager pm = (PowerManager) mActivity
+ .getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
+
+ startHandler();
+
+ mNetworkHandler = new NetworkStateHandler(mActivity, this);
+ // Start watching the default geolocation permissions
+ mSystemAllowGeolocationOrigins =
+ new SystemAllowGeolocationOrigins(mActivity.getApplicationContext());
+ mSystemAllowGeolocationOrigins.start();
+
+ retainIconsOnStartup();
+ }
+
+ void start(Bundle icicle, Intent intent) {
+ // Unless the last browser usage was within 24 hours, destroy any
+ // remaining incognito tabs.
+
+ Calendar lastActiveDate = icicle != null ?
+ (Calendar) icicle.getSerializable("lastActiveDate") : null;
+ Calendar today = Calendar.getInstance();
+ Calendar yesterday = Calendar.getInstance();
+ yesterday.add(Calendar.DATE, -1);
+
+ boolean restoreIncognitoTabs = !(lastActiveDate == null
+ || lastActiveDate.before(yesterday)
+ || lastActiveDate.after(today));
+
+ if (!mTabControl.restoreState(icicle, restoreIncognitoTabs,
+ mUi.needsRestoreAllTabs())) {
+ // there is no quit on Android. But if we can't restore the state,
+ // we can treat it as a new Browser, remove the old session cookies.
+ CookieManager.getInstance().removeSessionCookie();
+ // remove any incognito files
+ WebView.cleanupPrivateBrowsingFiles();
+ final Bundle extra = intent.getExtras();
+ // Create an initial tab.
+ // If the intent is ACTION_VIEW and data is not null, the Browser is
+ // invoked to view the content by another application. In this case,
+ // the tab will be close when exit.
+ UrlData urlData = mIntentHandler.getUrlDataFromIntent(intent);
+
+ String action = intent.getAction();
+ final Tab t = mTabControl.createNewTab(
+ (Intent.ACTION_VIEW.equals(action) &&
+ intent.getData() != null)
+ || RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
+ .equals(action),
+ intent.getStringExtra(Browser.EXTRA_APPLICATION_ID),
+ urlData.mUrl, false);
+ addTab(t);
+ setActiveTab(t);
+ WebView webView = t.getWebView();
+ if (extra != null) {
+ int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
+ if (scale > 0 && scale <= 1000) {
+ webView.setInitialScale(scale);
+ }
+ }
+
+ if (urlData.isEmpty()) {
+ loadUrl(webView, mSettings.getHomePage());
+ } else {
+ loadUrlDataIn(t, urlData);
+ }
+ } else {
+ mUi.updateTabs(mTabControl.getTabs());
+ if (!restoreIncognitoTabs) {
+ WebView.cleanupPrivateBrowsingFiles();
+ }
+ // TabControl.restoreState() will create a new tab even if
+ // restoring the state fails.
+ setActiveTab(mTabControl.getCurrentTab());
+ }
+ // clear up the thumbnail directory, which is no longer used;
+ // ideally this should only be run once after an upgrade from
+ // a previous version of the browser
+ new ClearThumbnails().execute(mTabControl.getThumbnailDir()
+ .listFiles());
+ // Read JavaScript flags if it exists.
+ String jsFlags = getSettings().getJsFlags();
+ if (jsFlags.trim().length() != 0) {
+ getCurrentWebView().setJsFlags(jsFlags);
+ }
+ }
+
+ void setWebViewFactory(WebViewFactory factory) {
+ mFactory = factory;
+ }
+
+ @Override
+ public WebViewFactory getWebViewFactory() {
+ return mFactory;
+ }
+
+ @Override
+ public void createSubWindow(Tab tab) {
+ endActionMode();
+ WebView mainView = tab.getWebView();
+ WebView subView = mFactory.createWebView((mainView == null)
+ ? false
+ : mainView.isPrivateBrowsingEnabled());
+ mUi.createSubWindow(tab, subView);
+ }
+
+ @Override
+ public Activity getActivity() {
+ return mActivity;
+ }
+
+ void setUi(UI ui) {
+ mUi = ui;
+ }
+
+ BrowserSettings getSettings() {
+ return mSettings;
+ }
+
+ IntentHandler getIntentHandler() {
+ return mIntentHandler;
+ }
+
+ @Override
+ public UI getUi() {
+ return mUi;
+ }
+
+ int getMaxTabs() {
+ return mActivity.getResources().getInteger(R.integer.max_tabs);
+ }
+
+ @Override
+ public TabControl getTabControl() {
+ return mTabControl;
+ }
+
+ @Override
+ public List<Tab> getTabs() {
+ return mTabControl.getTabs();
+ }
+
+ // Open the icon database and retain all the icons for visited sites.
+ // This is done on a background thread so as not to stall startup.
+ private void retainIconsOnStartup() {
+ // WebIconDatabase needs to be retrieved on the UI thread so that if
+ // it has not been created successfully yet the Handler is started on the
+ // UI thread.
+ new RetainIconsOnStartupTask(WebIconDatabase.getInstance()).execute();
+ }
+
+ private class RetainIconsOnStartupTask extends AsyncTask<Void, Void, Void> {
+ private WebIconDatabase mDb;
+
+ public RetainIconsOnStartupTask(WebIconDatabase db) {
+ mDb = db;
+ }
+
+ protected Void doInBackground(Void... unused) {
+ mDb.open(mActivity.getDir("icons", 0).getPath());
+ Cursor c = null;
+ try {
+ c = Browser.getAllBookmarks(mActivity.getContentResolver());
+ if (c.moveToFirst()) {
+ int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
+ do {
+ String url = c.getString(urlIndex);
+ mDb.retainIconForPageUrl(url);
+ } while (c.moveToNext());
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "retainIconsOnStartup", e);
+ } finally {
+ if (c != null) c.close();
+ }
+
+ return null;
+ }
+ }
+
+ private void startHandler() {
+ mHandler = new Handler() {
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case OPEN_BOOKMARKS:
+ bookmarksOrHistoryPicker(false);
+ break;
+ case FOCUS_NODE_HREF:
+ {
+ String url = (String) msg.getData().get("url");
+ String title = (String) msg.getData().get("title");
+ if (TextUtils.isEmpty(url)) {
+ break;
+ }
+ HashMap focusNodeMap = (HashMap) msg.obj;
+ WebView view = (WebView) focusNodeMap.get("webview");
+ // Only apply the action if the top window did not change.
+ if (getCurrentTopWebView() != view) {
+ break;
+ }
+ switch (msg.arg1) {
+ case R.id.open_context_menu_id:
+ case R.id.view_image_context_menu_id:
+ loadUrlFromContext(getCurrentTopWebView(), url);
+ break;
+ case R.id.open_newtab_context_menu_id:
+ final Tab parent = mTabControl.getCurrentTab();
+ final Tab newTab = openTab(url, false);
+ if (newTab != null && newTab != parent) {
+ parent.addChildTab(newTab);
+ }
+ break;
+ case R.id.bookmark_context_menu_id:
+ Intent intent = new Intent(mActivity,
+ AddBookmarkPage.class);
+ intent.putExtra(BrowserContract.Bookmarks.URL, url);
+ intent.putExtra(BrowserContract.Bookmarks.TITLE,
+ title);
+ mActivity.startActivity(intent);
+ break;
+ case R.id.share_link_context_menu_id:
+ sharePage(mActivity, title, url, null,
+ null);
+ break;
+ case R.id.copy_link_context_menu_id:
+ copy(url);
+ break;
+ case R.id.save_link_context_menu_id:
+ case R.id.download_context_menu_id:
+ DownloadHandler.onDownloadStartNoStream(
+ mActivity, url, null, null, null);
+ break;
+ }
+ break;
+ }
+
+ case LOAD_URL:
+ loadUrlFromContext(getCurrentTopWebView(), (String) msg.obj);
+ break;
+
+ case STOP_LOAD:
+ stopLoading();
+ break;
+
+ case RELEASE_WAKELOCK:
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ // if we reach here, Browser should be still in the
+ // background loading after WAKELOCK_TIMEOUT (5-min).
+ // To avoid burning the battery, stop loading.
+ mTabControl.stopAllLoading();
+ }
+ break;
+
+ case UPDATE_BOOKMARK_THUMBNAIL:
+ WebView view = (WebView) msg.obj;
+ if (view != null) {
+ updateScreenshot(view);
+ }
+ break;
+ }
+ }
+ };
+
+ }
+
+ /**
+ * Share a page, providing the title, url, favicon, and a screenshot. Uses
+ * an {@link Intent} to launch the Activity chooser.
+ * @param c Context used to launch a new Activity.
+ * @param title Title of the page. Stored in the Intent with
+ * {@link Intent#EXTRA_SUBJECT}
+ * @param url URL of the page. Stored in the Intent with
+ * {@link Intent#EXTRA_TEXT}
+ * @param favicon Bitmap of the favicon for the page. Stored in the Intent
+ * with {@link Browser#EXTRA_SHARE_FAVICON}
+ * @param screenshot Bitmap of a screenshot of the page. Stored in the
+ * Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT}
+ */
+ static final void sharePage(Context c, String title, String url,
+ Bitmap favicon, Bitmap screenshot) {
+ Intent send = new Intent(Intent.ACTION_SEND);
+ send.setType("text/plain");
+ send.putExtra(Intent.EXTRA_TEXT, url);
+ send.putExtra(Intent.EXTRA_SUBJECT, title);
+ send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon);
+ send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot);
+ try {
+ c.startActivity(Intent.createChooser(send, c.getString(
+ R.string.choosertitle_sharevia)));
+ } catch(android.content.ActivityNotFoundException ex) {
+ // if no app handles it, do nothing
+ }
+ }
+
+ private void copy(CharSequence text) {
+ ClipboardManager cm = (ClipboardManager) mActivity
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setText(text);
+ }
+
+ // lifecycle
+
+ protected void onConfgurationChanged(Configuration config) {
+ mConfigChanged = true;
+ if (mPageDialogsHandler != null) {
+ mPageDialogsHandler.onConfigurationChanged(config);
+ }
+ mUi.onConfigurationChanged(config);
+ }
+
+ @Override
+ public void handleNewIntent(Intent intent) {
+ mIntentHandler.onNewIntent(intent);
+ }
+
+ protected void onPause() {
+ if (mActivityPaused) {
+ Log.e(LOGTAG, "BrowserActivity is already paused.");
+ return;
+ }
+ mTabControl.pauseCurrentTab();
+ mActivityPaused = true;
+ if (mTabControl.getCurrentIndex() >= 0 &&
+ !pauseWebViewTimers(mActivityPaused)) {
+ mWakeLock.acquire();
+ mHandler.sendMessageDelayed(mHandler
+ .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
+ }
+ mUi.onPause();
+ mNetworkHandler.onPause();
+
+ WebView.disablePlatformNotifications();
+ }
+
+ void onSaveInstanceState(Bundle outState) {
+ // the default implementation requires each view to have an id. As the
+ // browser handles the state itself and it doesn't use id for the views,
+ // don't call the default implementation. Otherwise it will trigger the
+ // warning like this, "couldn't save which view has focus because the
+ // focused view XXX has no id".
+
+ // Save all the tabs
+ mTabControl.saveState(outState);
+ // Save time so that we know how old incognito tabs (if any) are.
+ outState.putSerializable("lastActiveDate", Calendar.getInstance());
+ }
+
+ void onResume() {
+ if (!mActivityPaused) {
+ Log.e(LOGTAG, "BrowserActivity is already resumed.");
+ return;
+ }
+ mTabControl.resumeCurrentTab();
+ mActivityPaused = false;
+ resumeWebViewTimers();
+
+ if (mWakeLock.isHeld()) {
+ mHandler.removeMessages(RELEASE_WAKELOCK);
+ mWakeLock.release();
+ }
+ mUi.onResume();
+ mNetworkHandler.onResume();
+ WebView.enablePlatformNotifications();
+ }
+
+ private void resumeWebViewTimers() {
+ Tab tab = mTabControl.getCurrentTab();
+ if (tab == null) return; // monkey can trigger this
+ boolean inLoad = tab.inPageLoad();
+ if ((!mActivityPaused && !inLoad) || (mActivityPaused && inLoad)) {
+ CookieSyncManager.getInstance().startSync();
+ WebView w = tab.getWebView();
+ if (w != null) {
+ w.resumeTimers();
+ }
+ }
+ }
+
+ private boolean pauseWebViewTimers(boolean activityPaused) {
+ Tab tab = mTabControl.getCurrentTab();
+ boolean inLoad = tab.inPageLoad();
+ if (activityPaused && !inLoad) {
+ CookieSyncManager.getInstance().stopSync();
+ WebView w = getCurrentWebView();
+ if (w != null) {
+ w.pauseTimers();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ void onDestroy() {
+ if (mUploadHandler != null) {
+ mUploadHandler.onResult(Activity.RESULT_CANCELED, null);
+ mUploadHandler = null;
+ }
+ if (mTabControl == null) return;
+ mUi.onDestroy();
+ // Remove the current tab and sub window
+ Tab t = mTabControl.getCurrentTab();
+ if (t != null) {
+ dismissSubWindow(t);
+ removeTab(t);
+ }
+ // Destroy all the tabs
+ mTabControl.destroy();
+ WebIconDatabase.getInstance().close();
+ // Stop watching the default geolocation permissions
+ mSystemAllowGeolocationOrigins.stop();
+ mSystemAllowGeolocationOrigins = null;
+ }
+
+ protected boolean isActivityPaused() {
+ return mActivityPaused;
+ }
+
+ protected void onLowMemory() {
+ mTabControl.freeMemory();
+ }
+
+ @Override
+ public boolean shouldShowErrorConsole() {
+ return mShouldShowErrorConsole;
+ }
+
+ protected void setShouldShowErrorConsole(boolean show) {
+ if (show == mShouldShowErrorConsole) {
+ // Nothing to do.
+ return;
+ }
+ mShouldShowErrorConsole = show;
+ Tab t = mTabControl.getCurrentTab();
+ if (t == null) {
+ // There is no current tab so we cannot toggle the error console
+ return;
+ }
+ mUi.setShouldShowErrorConsole(t, show);
+ }
+
+ @Override
+ public void stopLoading() {
+ mLoadStopped = true;
+ Tab tab = mTabControl.getCurrentTab();
+ resetTitleAndRevertLockIcon(tab);
+ WebView w = getCurrentTopWebView();
+ w.stopLoading();
+ // FIXME: before refactor, it is using mWebViewClient. So I keep the
+ // same logic here. But for subwindow case, should we call into the main
+ // WebView's onPageFinished as we never call its onPageStarted and if
+ // the page finishes itself, we don't call onPageFinished.
+ mTabControl.getCurrentWebView().getWebViewClient().onPageFinished(w,
+ w.getUrl());
+ mUi.onPageStopped(tab);
+ }
+
+ boolean didUserStopLoading() {
+ return mLoadStopped;
+ }
+
+ // WebViewController
+
+ @Override
+ public void onPageStarted(Tab tab, WebView view, String url, Bitmap favicon) {
+
+ // We've started to load a new page. If there was a pending message
+ // to save a screenshot then we will now take the new page and save
+ // an incorrect screenshot. Therefore, remove any pending thumbnail
+ // messages from the queue.
+ mHandler.removeMessages(Controller.UPDATE_BOOKMARK_THUMBNAIL,
+ view);
+
+ // reset sync timer to avoid sync starts during loading a page
+ CookieSyncManager.getInstance().resetSync();
+
+ if (!mNetworkHandler.isNetworkUp()) {
+ view.setNetworkAvailable(false);
+ }
+
+ // when BrowserActivity just starts, onPageStarted may be called before
+ // onResume as it is triggered from onCreate. Call resumeWebViewTimers
+ // to start the timer. As we won't switch tabs while an activity is in
+ // pause state, we can ensure calling resume and pause in pair.
+ if (mActivityPaused) {
+ resumeWebViewTimers();
+ }
+ mLoadStopped = false;
+ if (!mNetworkHandler.isNetworkUp()) {
+ mNetworkHandler.createAndShowNetworkDialog();
+ }
+ endActionMode();
+
+ mUi.onPageStarted(tab, url, favicon);
+
+ // Show some progress so that the user knows the page is beginning to
+ // load
+ onProgressChanged(tab, INITIAL_PROGRESS);
+
+ // update the bookmark database for favicon
+ maybeUpdateFavicon(tab, null, url, favicon);
+
+ Performance.tracePageStart(url);
+
+ // Performance probe
+ if (false) {
+ Performance.onPageStarted();
+ }
+
+ }
+
+ @Override
+ public void onPageFinished(Tab tab, String url) {
+ mUi.onPageFinished(tab, url);
+ if (!tab.isPrivateBrowsingEnabled()) {
+ if (tab.inForeground() && !didUserStopLoading()
+ || !tab.inForeground()) {
+ // Only update the bookmark screenshot if the user did not
+ // cancel the load early.
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ UPDATE_BOOKMARK_THUMBNAIL, 0, 0, tab.getWebView()),
+ 500);
+ }
+ }
+ // pause the WebView timer and release the wake lock if it is finished
+ // while BrowserActivity is in pause state.
+ if (mActivityPaused && pauseWebViewTimers(mActivityPaused)) {
+ if (mWakeLock.isHeld()) {
+ mHandler.removeMessages(RELEASE_WAKELOCK);
+ mWakeLock.release();
+ }
+ }
+ // Performance probe
+ if (false) {
+ Performance.onPageFinished(url);
+ }
+
+ Performance.tracePageFinished();
+ }
+
+ @Override
+ public void onProgressChanged(Tab tab, int newProgress) {
+
+ if (newProgress == 100) {
+ CookieSyncManager.getInstance().sync();
+ // onProgressChanged() may continue to be called after the main
+ // frame has finished loading, as any remaining sub frames continue
+ // to load. We'll only get called once though with newProgress as
+ // 100 when everything is loaded. (onPageFinished is called once
+ // when the main frame completes loading regardless of the state of
+ // any sub frames so calls to onProgressChanges may continue after
+ // onPageFinished has executed)
+ if (mInLoad) {
+ mInLoad = false;
+ updateInLoadMenuItems(mCachedMenu);
+ }
+ } else {
+ if (!mInLoad) {
+ // onPageFinished may have already been called but a subframe is
+ // still loading and updating the progress. Reset mInLoad and
+ // update the menu items.
+ mInLoad = true;
+ updateInLoadMenuItems(mCachedMenu);
+ }
+ }
+ mUi.onProgressChanged(tab, newProgress);
+ }
+
+ @Override
+ public void onReceivedTitle(Tab tab, final String title) {
+ final String pageUrl = tab.getWebView().getUrl();
+ setUrlTitle(tab, pageUrl, title);
+ if (pageUrl == null || pageUrl.length()
+ >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
+ return;
+ }
+ // Update the title in the history database if not in private browsing mode
+ if (!tab.isPrivateBrowsingEnabled()) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ // See if we can find the current url in our history
+ // database and add the new title to it.
+ String url = pageUrl;
+ if (url.startsWith("http://www.")) {
+ url = url.substring(11);
+ } else if (url.startsWith("http://")) {
+ url = url.substring(4);
+ }
+ // Escape wildcards for LIKE operator.
+ url = url.replace("\\", "\\\\").replace("%", "\\%")
+ .replace("_", "\\_");
+ Cursor c = null;
+ try {
+ final ContentResolver cr =
+ getActivity().getContentResolver();
+ String selection = History.URL + " LIKE ? ESCAPE '\\'";
+ String [] selectionArgs = new String[] { "%" + url };
+ ContentValues values = new ContentValues();
+ values.put(History.TITLE, title);
+ cr.update(History.CONTENT_URI, values, selection,
+ selectionArgs);
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "Tab onReceived title", e);
+ } catch (SQLiteException ex) {
+ Log.e(LOGTAG,
+ "onReceivedTitle() caught SQLiteException: ",
+ ex);
+ } finally {
+ if (c != null) c.close();
+ }
+ return null;
+ }
+ }.execute();
+ }
+ }
+
+ @Override
+ public void onFavicon(Tab tab, WebView view, Bitmap icon) {
+ mUi.setFavicon(tab, icon);
+ maybeUpdateFavicon(tab, view.getOriginalUrl(), view.getUrl(), icon);
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ return mUrlHandler.shouldOverrideUrlLoading(view, url);
+ }
+
+ @Override
+ public boolean shouldOverrideKeyEvent(KeyEvent event) {
+ if (mMenuIsDown) {
+ // only check shortcut key when MENU is held
+ return mActivity.getWindow().isShortcutKey(event.getKeyCode(),
+ event);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onUnhandledKeyEvent(KeyEvent event) {
+ if (!isActivityPaused()) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ mActivity.onKeyDown(event.getKeyCode(), event);
+ } else {
+ mActivity.onKeyUp(event.getKeyCode(), event);
+ }
+ }
+ }
+
+ @Override
+ public void doUpdateVisitedHistory(Tab tab, String url,
+ boolean isReload) {
+ // Don't save anything in private browsing mode
+ if (tab.isPrivateBrowsingEnabled()) return;
+
+ if (url.regionMatches(true, 0, "about:", 0, 6)) {
+ return;
+ }
+ // remove "client" before updating it to the history so that it wont
+ // show up in the auto-complete list.
+ int index = url.indexOf("client=ms-");
+ if (index > 0 && url.contains(".google.")) {
+ int end = url.indexOf('&', index);
+ if (end > 0) {
+ url = url.substring(0, index)
+ .concat(url.substring(end + 1));
+ } else {
+ // the url.charAt(index-1) should be either '?' or '&'
+ url = url.substring(0, index-1);
+ }
+ }
+ final ContentResolver cr = getActivity().getContentResolver();
+ final String newUrl = url;
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ Browser.updateVisitedHistory(cr, newUrl, true);
+ return null;
+ }
+ }.execute();
+ WebIconDatabase.getInstance().retainIconForPageUrl(url);
+ }
+
+ @Override
+ public void getVisitedHistory(final ValueCallback<String[]> callback) {
+ AsyncTask<Void, Void, String[]> task =
+ new AsyncTask<Void, Void, String[]>() {
+ @Override
+ public String[] doInBackground(Void... unused) {
+ return Browser.getVisitedHistory(mActivity.getContentResolver());
+ }
+ @Override
+ public void onPostExecute(String[] result) {
+ callback.onReceiveValue(result);
+ }
+ };
+ task.execute();
+ }
+
+ @Override
+ public void onReceivedHttpAuthRequest(Tab tab, WebView view,
+ final HttpAuthHandler handler, final String host,
+ final String realm) {
+ String username = null;
+ String password = null;
+
+ boolean reuseHttpAuthUsernamePassword
+ = handler.useHttpAuthUsernamePassword();
+
+ if (reuseHttpAuthUsernamePassword && view != null) {
+ String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
+ if (credentials != null && credentials.length == 2) {
+ username = credentials[0];
+ password = credentials[1];
+ }
+ }
+
+ if (username != null && password != null) {
+ handler.proceed(username, password);
+ } else {
+ if (tab.inForeground()) {
+ mPageDialogsHandler.showHttpAuthentication(tab, handler, host, realm);
+ } else {
+ handler.cancel();
+ }
+ }
+ }
+
+ @Override
+ public void onDownloadStart(Tab tab, String url, String userAgent,
+ String contentDisposition, String mimetype, long contentLength) {
+ DownloadHandler.onDownloadStart(mActivity, url, userAgent,
+ contentDisposition, mimetype);
+ if (tab.getWebView().copyBackForwardList().getSize() == 0) {
+ // This Tab was opened for the sole purpose of downloading a
+ // file. Remove it.
+ if (tab == mTabControl.getCurrentTab()) {
+ // In this case, the Tab is still on top.
+ goBackOnePageOrQuit();
+ } else {
+ // In this case, it is not.
+ closeTab(tab);
+ }
+ }
+ }
+
+ @Override
+ public Bitmap getDefaultVideoPoster() {
+ return mUi.getDefaultVideoPoster();
+ }
+
+ @Override
+ public View getVideoLoadingProgressView() {
+ return mUi.getVideoLoadingProgressView();
+ }
+
+ @Override
+ public void showSslCertificateOnError(WebView view, SslErrorHandler handler,
+ SslError error) {
+ mPageDialogsHandler.showSSLCertificateOnError(view, handler, error);
+ }
+
+ // helper method
+
+ /*
+ * Update the favorites icon if the private browsing isn't enabled and the
+ * icon is valid.
+ */
+ private void maybeUpdateFavicon(Tab tab, final String originalUrl,
+ final String url, Bitmap favicon) {
+ if (favicon == null) {
+ return;
+ }
+ if (!tab.isPrivateBrowsingEnabled()) {
+ Bookmarks.updateFavicon(mActivity
+ .getContentResolver(), originalUrl, url, favicon);
+ }
+ }
+
+ // end WebViewController
+
+ protected void pageUp() {
+ getCurrentTopWebView().pageUp(false);
+ }
+
+ protected void pageDown() {
+ getCurrentTopWebView().pageDown(false);
+ }
+
+ // callback from phone title bar
+ public void editUrl() {
+ if (mOptionsMenuOpen) mActivity.closeOptionsMenu();
+ String url = (getCurrentTopWebView() == null) ? null : getCurrentTopWebView().getUrl();
+ startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
+ null, false);
+ }
+
+ public void activateVoiceSearchMode(String title) {
+ mUi.showVoiceTitleBar(title);
+ }
+
+ public void revertVoiceSearchMode(Tab tab) {
+ mUi.revertVoiceTitleBar(tab);
+ }
+
+ public void showCustomView(Tab tab, View view,
+ WebChromeClient.CustomViewCallback callback) {
+ if (tab.inForeground()) {
+ if (mUi.isCustomViewShowing()) {
+ callback.onCustomViewHidden();
+ return;
+ }
+ mUi.showCustomView(view, callback);
+ // Save the menu state and set it to empty while the custom
+ // view is showing.
+ mOldMenuState = mMenuState;
+ mMenuState = EMPTY_MENU;
+ }
+ }
+
+ @Override
+ public void hideCustomView() {
+ if (mUi.isCustomViewShowing()) {
+ mUi.onHideCustomView();
+ // Reset the old menu state.
+ mMenuState = mOldMenuState;
+ mOldMenuState = EMPTY_MENU;
+ }
+ }
+
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent intent) {
+ if (getCurrentTopWebView() == null) return;
+ switch (requestCode) {
+ case PREFERENCES_PAGE:
+ if (resultCode == Activity.RESULT_OK && intent != null) {
+ String action = intent.getStringExtra(Intent.EXTRA_TEXT);
+ if (BrowserSettings.PREF_CLEAR_HISTORY.equals(action)) {
+ mTabControl.removeParentChildRelationShips();
+ }
+ }
+ break;
+ case FILE_SELECTED:
+ // Choose a file from the file picker.
+ if (null == mUploadHandler) break;
+ mUploadHandler.onResult(resultCode, intent);
+ mUploadHandler = null;
+ break;
+ case AUTOFILL_SETUP:
+ // Determine whether a profile was actually set up or not
+ // and if so, send the message back to the WebTextView to
+ // fill the form with the new profile.
+ if (getSettings().getAutoFillProfile() != null) {
+ mAutoFillSetupMessage.sendToTarget();
+ mAutoFillSetupMessage = null;
+ }
+ break;
+ default:
+ break;
+ }
+ getCurrentTopWebView().requestFocus();
+ }
+
+ /**
+ * Open the Go page.
+ * @param startWithHistory If true, open starting on the history tab.
+ * Otherwise, start with the bookmarks tab.
+ */
+ @Override
+ public void bookmarksOrHistoryPicker(boolean startWithHistory) {
+ if (mTabControl.getCurrentWebView() == null) {
+ return;
+ }
+ Bundle extras = new Bundle();
+ // Disable opening in a new window if we have maxed out the windows
+ extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW,
+ !mTabControl.canCreateNewTab());
+ mUi.showComboView(startWithHistory, extras);
+ }
+
+ // combo view callbacks
+
+ /**
+ * callback from ComboPage when clear history is requested
+ */
+ public void onRemoveParentChildRelationships() {
+ mTabControl.removeParentChildRelationShips();
+ }
+
+ /**
+ * callback from ComboPage when bookmark/history selection
+ */
+ @Override
+ public void onUrlSelected(String url, boolean newTab) {
+ removeComboView();
+ if (!TextUtils.isEmpty(url)) {
+ if (newTab) {
+ openTab(url, false);
+ } else {
+ final Tab currentTab = mTabControl.getCurrentTab();
+ dismissSubWindow(currentTab);
+ loadUrl(getCurrentTopWebView(), url);
+ }
+ }
+ }
+
+ /**
+ * callback from ComboPage when dismissed
+ */
+ @Override
+ public void onComboCanceled() {
+ removeComboView();
+ }
+
+ /**
+ * dismiss the ComboPage
+ */
+ @Override
+ public void removeComboView() {
+ mUi.hideComboView();
+ }
+
+ // active tabs page handling
+
+ protected void showActiveTabsPage() {
+ mMenuState = EMPTY_MENU;
+ mUi.showActiveTabsPage();
+ }
+
+ /**
+ * Remove the active tabs page.
+ * @param needToAttach If true, the active tabs page did not attach a tab
+ * to the content view, so we need to do that here.
+ */
+ @Override
+ public void removeActiveTabsPage(boolean needToAttach) {
+ mMenuState = R.id.MAIN_MENU;
+ mUi.removeActiveTabsPage();
+ if (needToAttach) {
+ setActiveTab(mTabControl.getCurrentTab());
+ }
+ getCurrentTopWebView().requestFocus();
+ }
+
+ // key handling
+ protected void onBackKey() {
+ if (!mUi.onBackKey()) {
+ WebView subwindow = mTabControl.getCurrentSubWindow();
+ if (subwindow != null) {
+ if (subwindow.canGoBack()) {
+ subwindow.goBack();
+ } else {
+ dismissSubWindow(mTabControl.getCurrentTab());
+ }
+ } else {
+ goBackOnePageOrQuit();
+ }
+ }
+ }
+
+ // menu handling and state
+ // TODO: maybe put into separate handler
+
+ protected boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = mActivity.getMenuInflater();
+ inflater.inflate(R.menu.browser, menu);
+ updateInLoadMenuItems(menu);
+ // hold on to the menu reference here; it is used by the page callbacks
+ // to update the menu based on loading state
+ mCachedMenu = menu;
+ return true;
+ }
+
+ protected void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ if (v instanceof TitleBarBase) {
+ return;
+ }
+ if (!(v instanceof WebView)) {
+ return;
+ }
+ final WebView webview = (WebView) v;
+ WebView.HitTestResult result = webview.getHitTestResult();
+ if (result == null) {
+ return;
+ }
+
+ int type = result.getType();
+ if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
+ Log.w(LOGTAG,
+ "We should not show context menu when nothing is touched");
+ return;
+ }
+ if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
+ // let TextView handles context menu
+ return;
+ }
+
+ // Note, http://b/issue?id=1106666 is requesting that
+ // an inflated menu can be used again. This is not available
+ // yet, so inflate each time (yuk!)
+ MenuInflater inflater = mActivity.getMenuInflater();
+ inflater.inflate(R.menu.browsercontext, menu);
+
+ // Show the correct menu group
+ final String extra = result.getExtra();
+ menu.setGroupVisible(R.id.PHONE_MENU,
+ type == WebView.HitTestResult.PHONE_TYPE);
+ menu.setGroupVisible(R.id.EMAIL_MENU,
+ type == WebView.HitTestResult.EMAIL_TYPE);
+ menu.setGroupVisible(R.id.GEO_MENU,
+ type == WebView.HitTestResult.GEO_TYPE);
+ menu.setGroupVisible(R.id.IMAGE_MENU,
+ type == WebView.HitTestResult.IMAGE_TYPE
+ || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+ menu.setGroupVisible(R.id.ANCHOR_MENU,
+ type == WebView.HitTestResult.SRC_ANCHOR_TYPE
+ || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+ boolean hitText = type == WebView.HitTestResult.SRC_ANCHOR_TYPE
+ || type == WebView.HitTestResult.PHONE_TYPE
+ || type == WebView.HitTestResult.EMAIL_TYPE
+ || type == WebView.HitTestResult.GEO_TYPE;
+ menu.setGroupVisible(R.id.SELECT_TEXT_MENU, hitText);
+ if (hitText) {
+ menu.findItem(R.id.select_text_menu_id)
+ .setOnMenuItemClickListener(new SelectText(webview));
+ }
+ // Setup custom handling depending on the type
+ switch (type) {
+ case WebView.HitTestResult.PHONE_TYPE:
+ menu.setHeaderTitle(Uri.decode(extra));
+ menu.findItem(R.id.dial_context_menu_id).setIntent(
+ new Intent(Intent.ACTION_VIEW, Uri
+ .parse(WebView.SCHEME_TEL + extra)));
+ Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
+ addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
+ menu.findItem(R.id.add_contact_context_menu_id).setIntent(
+ addIntent);
+ menu.findItem(R.id.copy_phone_context_menu_id)
+ .setOnMenuItemClickListener(
+ new Copy(extra));
+ break;
+
+ case WebView.HitTestResult.EMAIL_TYPE:
+ menu.setHeaderTitle(extra);
+ menu.findItem(R.id.email_context_menu_id).setIntent(
+ new Intent(Intent.ACTION_VIEW, Uri
+ .parse(WebView.SCHEME_MAILTO + extra)));
+ menu.findItem(R.id.copy_mail_context_menu_id)
+ .setOnMenuItemClickListener(
+ new Copy(extra));
+ break;
+
+ case WebView.HitTestResult.GEO_TYPE:
+ menu.setHeaderTitle(extra);
+ menu.findItem(R.id.map_context_menu_id).setIntent(
+ new Intent(Intent.ACTION_VIEW, Uri
+ .parse(WebView.SCHEME_GEO
+ + URLEncoder.encode(extra))));
+ menu.findItem(R.id.copy_geo_context_menu_id)
+ .setOnMenuItemClickListener(
+ new Copy(extra));
+ break;
+
+ case WebView.HitTestResult.SRC_ANCHOR_TYPE:
+ case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
+ TextView titleView = (TextView) LayoutInflater.from(mActivity)
+ .inflate(android.R.layout.browser_link_context_header,
+ null);
+ titleView.setText(extra);
+ menu.setHeaderView(titleView);
+ // decide whether to show the open link in new tab option
+ boolean showNewTab = mTabControl.canCreateNewTab();
+ MenuItem newTabItem
+ = menu.findItem(R.id.open_newtab_context_menu_id);
+ newTabItem.setVisible(showNewTab);
+ if (showNewTab) {
+ if (WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE == type) {
+ newTabItem.setOnMenuItemClickListener(
+ new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ final HashMap<String, WebView> hrefMap =
+ new HashMap<String, WebView>();
+ hrefMap.put("webview", webview);
+ final Message msg = mHandler.obtainMessage(
+ FOCUS_NODE_HREF,
+ R.id.open_newtab_context_menu_id,
+ 0, hrefMap);
+ webview.requestFocusNodeHref(msg);
+ return true;
+ }
+ });
+ } else {
+ newTabItem.setOnMenuItemClickListener(
+ new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ final Tab parent = mTabControl.getCurrentTab();
+ final Tab newTab = openTab(extra, false);
+ if (newTab != parent) {
+ parent.addChildTab(newTab);
+ }
+ return true;
+ }
+ });
+ }
+ }
+ menu.findItem(R.id.bookmark_context_menu_id).setVisible(
+ Bookmarks.urlHasAcceptableScheme(extra));
+ PackageManager pm = mActivity.getPackageManager();
+ Intent send = new Intent(Intent.ACTION_SEND);
+ send.setType("text/plain");
+ ResolveInfo ri = pm.resolveActivity(send,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ menu.findItem(R.id.share_link_context_menu_id)
+ .setVisible(ri != null);
+ if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
+ break;
+ }
+ // otherwise fall through to handle image part
+ case WebView.HitTestResult.IMAGE_TYPE:
+ if (type == WebView.HitTestResult.IMAGE_TYPE) {
+ menu.setHeaderTitle(extra);
+ }
+ menu.findItem(R.id.view_image_context_menu_id).setIntent(
+ new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
+ menu.findItem(R.id.download_context_menu_id).
+ setOnMenuItemClickListener(new Download(mActivity, extra));
+ menu.findItem(R.id.set_wallpaper_context_menu_id).
+ setOnMenuItemClickListener(new WallpaperHandler(mActivity,
+ extra));
+ break;
+
+ default:
+ Log.w(LOGTAG, "We should not get here.");
+ break;
+ }
+ //update the ui
+ mUi.onContextMenuCreated(menu);
+ }
+
+ /**
+ * As the menu can be open when loading state changes
+ * we must manually update the state of the stop/reload menu
+ * item
+ */
+ private void updateInLoadMenuItems(Menu menu) {
+ if (menu == null) {
+ return;
+ }
+ MenuItem dest = menu.findItem(R.id.stop_reload_menu_id);
+ MenuItem src = mInLoad ?
+ menu.findItem(R.id.stop_menu_id):
+ menu.findItem(R.id.reload_menu_id);
+ if (src != null) {
+ dest.setIcon(src.getIcon());
+ dest.setTitle(src.getTitle());
+ }
+ }
+
+ boolean prepareOptionsMenu(Menu menu) {
+ // This happens when the user begins to hold down the menu key, so
+ // allow them to chord to get a shortcut.
+ mCanChord = true;
+ // Note: setVisible will decide whether an item is visible; while
+ // setEnabled() will decide whether an item is enabled, which also means
+ // whether the matching shortcut key will function.
+ switch (mMenuState) {
+ case EMPTY_MENU:
+ if (mCurrentMenuState != mMenuState) {
+ menu.setGroupVisible(R.id.MAIN_MENU, false);
+ menu.setGroupEnabled(R.id.MAIN_MENU, false);
+ menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
+ }
+ break;
+ default:
+ if (mCurrentMenuState != mMenuState) {
+ menu.setGroupVisible(R.id.MAIN_MENU, true);
+ menu.setGroupEnabled(R.id.MAIN_MENU, true);
+ menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
+ }
+ final WebView w = getCurrentTopWebView();
+ boolean canGoBack = false;
+ boolean canGoForward = false;
+ boolean isHome = false;
+ if (w != null) {
+ canGoBack = w.canGoBack();
+ canGoForward = w.canGoForward();
+ isHome = mSettings.getHomePage().equals(w.getUrl());
+ }
+ final MenuItem back = menu.findItem(R.id.back_menu_id);
+ back.setEnabled(canGoBack);
+
+ final MenuItem home = menu.findItem(R.id.homepage_menu_id);
+ home.setEnabled(!isHome);
+
+ final MenuItem forward = menu.findItem(R.id.forward_menu_id);
+ forward.setEnabled(canGoForward);
+
+ // decide whether to show the share link option
+ PackageManager pm = mActivity.getPackageManager();
+ Intent send = new Intent(Intent.ACTION_SEND);
+ send.setType("text/plain");
+ ResolveInfo ri = pm.resolveActivity(send,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
+
+ boolean isNavDump = mSettings.isNavDump();
+ final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
+ nav.setVisible(isNavDump);
+ nav.setEnabled(isNavDump);
+
+ boolean showDebugSettings = mSettings.showDebugSettings();
+ final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id);
+ counter.setVisible(showDebugSettings);
+ counter.setEnabled(showDebugSettings);
+
+ // allow the ui to adjust state based settings
+ mUi.onPrepareOptionsMenu(menu);
+
+ break;
+ }
+ mCurrentMenuState = mMenuState;
+ return true;
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getGroupId() != R.id.CONTEXT_MENU) {
+ // menu remains active, so ensure comboview is dismissed
+ // if main menu option is selected
+ removeComboView();
+ }
+ // check the action bar button before mCanChord check, as the prepare call
+ // doesn't come for action bar buttons
+ if (item.getItemId() == R.id.newtab) {
+ openTabToHomePage();
+ return true;
+ }
+ if (!mCanChord) {
+ // The user has already fired a shortcut with this hold down of the
+ // menu key.
+ return false;
+ }
+ if (null == getCurrentTopWebView()) {
+ return false;
+ }
+ if (mMenuIsDown) {
+ // The shortcut action consumes the MENU. Even if it is still down,
+ // it won't trigger the next shortcut action. In the case of the
+ // shortcut action triggering a new activity, like Bookmarks, we
+ // won't get onKeyUp for MENU. So it is important to reset it here.
+ mMenuIsDown = false;
+ }
+ switch (item.getItemId()) {
+ // -- Main menu
+ case R.id.new_tab_menu_id:
+ openTabToHomePage();
+ break;
+
+ case R.id.incognito_menu_id:
+ openIncognitoTab();
+ break;
+
+ case R.id.goto_menu_id:
+ editUrl();
+ break;
+
+ case R.id.bookmarks_menu_id:
+ bookmarksOrHistoryPicker(false);
+ break;
+
+ case R.id.active_tabs_menu_id:
+ showActiveTabsPage();
+ break;
+
+ case R.id.add_bookmark_menu_id:
+ bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID);
+ break;
+
+ case R.id.stop_reload_menu_id:
+ if (mInLoad) {
+ stopLoading();
+ } else {
+ getCurrentTopWebView().reload();
+ }
+ break;
+
+ case R.id.back_menu_id:
+ getCurrentTopWebView().goBack();
+ break;
+
+ case R.id.forward_menu_id:
+ getCurrentTopWebView().goForward();
+ break;
+
+ case R.id.close_menu_id:
+ // Close the subwindow if it exists.
+ if (mTabControl.getCurrentSubWindow() != null) {
+ dismissSubWindow(mTabControl.getCurrentTab());
+ break;
+ }
+ closeCurrentTab();
+ break;
+
+ case R.id.homepage_menu_id:
+ Tab current = mTabControl.getCurrentTab();
+ if (current != null) {
+ dismissSubWindow(current);
+ loadUrl(current.getWebView(), mSettings.getHomePage());
+ }
+ break;
+
+ case R.id.preferences_menu_id:
+ Intent intent = new Intent(mActivity, BrowserPreferencesPage.class);
+ intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE,
+ getCurrentTopWebView().getUrl());
+ mActivity.startActivityForResult(intent, PREFERENCES_PAGE);
+ break;
+
+ case R.id.find_menu_id:
+ getCurrentTopWebView().showFindDialog(null);
+ break;
+
+ case R.id.page_info_menu_id:
+ mPageDialogsHandler.showPageInfo(mTabControl.getCurrentTab(),
+ false);
+ break;
+
+ case R.id.classic_history_menu_id:
+ bookmarksOrHistoryPicker(true);
+ break;
+
+ case R.id.title_bar_share_page_url:
+ case R.id.share_page_menu_id:
+ Tab currentTab = mTabControl.getCurrentTab();
+ if (null == currentTab) {
+ mCanChord = false;
+ return false;
+ }
+ currentTab.populatePickerData();
+ sharePage(mActivity, currentTab.getTitle(),
+ currentTab.getUrl(), currentTab.getFavicon(),
+ createScreenshot(currentTab.getWebView(),
+ getDesiredThumbnailWidth(mActivity),
+ getDesiredThumbnailHeight(mActivity)));
+ break;
+
+ case R.id.dump_nav_menu_id:
+ getCurrentTopWebView().debugDump();
+ break;
+
+ case R.id.dump_counters_menu_id:
+ getCurrentTopWebView().dumpV8Counters();
+ break;
+
+ case R.id.zoom_in_menu_id:
+ getCurrentTopWebView().zoomIn();
+ break;
+
+ case R.id.zoom_out_menu_id:
+ getCurrentTopWebView().zoomOut();
+ break;
+
+ case R.id.view_downloads_menu_id:
+ viewDownloads();
+ break;
+
+ case R.id.window_one_menu_id:
+ case R.id.window_two_menu_id:
+ case R.id.window_three_menu_id:
+ case R.id.window_four_menu_id:
+ case R.id.window_five_menu_id:
+ case R.id.window_six_menu_id:
+ case R.id.window_seven_menu_id:
+ case R.id.window_eight_menu_id:
+ {
+ int menuid = item.getItemId();
+ for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
+ if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
+ Tab desiredTab = mTabControl.getTab(id);
+ if (desiredTab != null &&
+ desiredTab != mTabControl.getCurrentTab()) {
+ switchToTab(id);
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ default:
+ return false;
+ }
+ mCanChord = false;
+ return true;
+ }
+
+ public boolean onContextItemSelected(MenuItem item) {
+ // Let the History and Bookmark fragments handle menus they created.
+ if (item.getGroupId() == R.id.CONTEXT_MENU) {
+ return false;
+ }
+
+ // chording is not an issue with context menus, but we use the same
+ // options selector, so set mCanChord to true so we can access them.
+ mCanChord = true;
+ int id = item.getItemId();
+ boolean result = true;
+ switch (id) {
+ // For the context menu from the title bar
+ case R.id.title_bar_copy_page_url:
+ Tab currentTab = mTabControl.getCurrentTab();
+ if (null == currentTab) {
+ result = false;
+ break;
+ }
+ WebView mainView = currentTab.getWebView();
+ if (null == mainView) {
+ result = false;
+ break;
+ }
+ copy(mainView.getUrl());
+ break;
+ // -- Browser context menu
+ case R.id.open_context_menu_id:
+ case R.id.bookmark_context_menu_id:
+ case R.id.save_link_context_menu_id:
+ case R.id.share_link_context_menu_id:
+ case R.id.copy_link_context_menu_id:
+ final WebView webView = getCurrentTopWebView();
+ if (null == webView) {
+ result = false;
+ break;
+ }
+ final HashMap<String, WebView> hrefMap =
+ new HashMap<String, WebView>();
+ hrefMap.put("webview", webView);
+ final Message msg = mHandler.obtainMessage(
+ FOCUS_NODE_HREF, id, 0, hrefMap);
+ webView.requestFocusNodeHref(msg);
+ break;
+
+ default:
+ // For other context menus
+ result = onOptionsItemSelected(item);
+ }
+ mCanChord = false;
+ return result;
+ }
+
+ /**
+ * support programmatically opening the context menu
+ */
+ public void openContextMenu(View view) {
+ mActivity.openContextMenu(view);
+ }
+
+ /**
+ * programmatically open the options menu
+ */
+ public void openOptionsMenu() {
+ mActivity.openOptionsMenu();
+ }
+
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ if (mOptionsMenuOpen) {
+ if (mConfigChanged) {
+ // We do not need to make any changes to the state of the
+ // title bar, since the only thing that happened was a
+ // change in orientation
+ mConfigChanged = false;
+ } else {
+ if (!mExtendedMenuOpen) {
+ mExtendedMenuOpen = true;
+ mUi.onExtendedMenuOpened();
+ } else {
+ // Switching the menu back to icon view, so show the
+ // title bar once again.
+ mExtendedMenuOpen = false;
+ mUi.onExtendedMenuClosed(mInLoad);
+ mUi.onOptionsMenuOpened();
+ }
+ }
+ } else {
+ // The options menu is closed, so open it, and show the title
+ mOptionsMenuOpen = true;
+ mConfigChanged = false;
+ mExtendedMenuOpen = false;
+ mUi.onOptionsMenuOpened();
+ }
+ return true;
+ }
+
+ public void onOptionsMenuClosed(Menu menu) {
+ mOptionsMenuOpen = false;
+ mUi.onOptionsMenuClosed(mInLoad);
+ }
+
+ public void onContextMenuClosed(Menu menu) {
+ mUi.onContextMenuClosed(menu, mInLoad);
+ }
+
+ // Helper method for getting the top window.
+ @Override
+ public WebView getCurrentTopWebView() {
+ return mTabControl.getCurrentTopWebView();
+ }
+
+ @Override
+ public WebView getCurrentWebView() {
+ return mTabControl.getCurrentWebView();
+ }
+
+ /*
+ * This method is called as a result of the user selecting the options
+ * menu to see the download window. It shows the download window on top of
+ * the current window.
+ */
+ void viewDownloads() {
+ Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+ mActivity.startActivity(intent);
+ }
+
+ // action mode
+
+ void onActionModeStarted(ActionMode mode) {
+ mUi.onActionModeStarted(mode);
+ mActionMode = mode;
+ }
+
+ /*
+ * True if a custom ActionMode (i.e. find or select) is in use.
+ */
+ @Override
+ public boolean isInCustomActionMode() {
+ return mActionMode != null;
+ }
+
+ /*
+ * End the current ActionMode.
+ */
+ @Override
+ public void endActionMode() {
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
+ }
+
+ /*
+ * Called by find and select when they are finished. Replace title bars
+ * as necessary.
+ */
+ public void onActionModeFinished(ActionMode mode) {
+ if (!isInCustomActionMode()) return;
+ mUi.onActionModeFinished(mInLoad);
+ mActionMode = null;
+ }
+
+ boolean isInLoad() {
+ return mInLoad;
+ }
+
+ // bookmark handling
+
+ /**
+ * add the current page as a bookmark to the given folder id
+ * @param folderId use -1 for the default folder
+ */
+ @Override
+ public void bookmarkCurrentPage(long folderId) {
+ Intent i = new Intent(mActivity,
+ AddBookmarkPage.class);
+ WebView w = getCurrentTopWebView();
+ i.putExtra(BrowserContract.Bookmarks.URL, w.getUrl());
+ i.putExtra(BrowserContract.Bookmarks.TITLE, w.getTitle());
+ String touchIconUrl = w.getTouchIconUrl();
+ if (touchIconUrl != null) {
+ i.putExtra(AddBookmarkPage.TOUCH_ICON_URL, touchIconUrl);
+ WebSettings settings = w.getSettings();
+ if (settings != null) {
+ i.putExtra(AddBookmarkPage.USER_AGENT,
+ settings.getUserAgentString());
+ }
+ }
+ i.putExtra(BrowserContract.Bookmarks.THUMBNAIL,
+ createScreenshot(w, getDesiredThumbnailWidth(mActivity),
+ getDesiredThumbnailHeight(mActivity)));
+ i.putExtra(BrowserContract.Bookmarks.FAVICON, w.getFavicon());
+ i.putExtra(BrowserContract.Bookmarks.PARENT,
+ folderId);
+ // Put the dialog at the upper right of the screen, covering the
+ // star on the title bar.
+ i.putExtra("gravity", Gravity.RIGHT | Gravity.TOP);
+ mActivity.startActivity(i);
+ }
+
+ // file chooser
+ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
+ mUploadHandler = new UploadHandler(this);
+ mUploadHandler.openFileChooser(uploadMsg, acceptType);
+ }
+
+ // thumbnails
+
+ /**
+ * Return the desired width for thumbnail screenshots, which are stored in
+ * the database, and used on the bookmarks screen.
+ * @param context Context for finding out the density of the screen.
+ * @return desired width for thumbnail screenshot.
+ */
+ static int getDesiredThumbnailWidth(Context context) {
+ return context.getResources().getDimensionPixelOffset(
+ R.dimen.bookmarkThumbnailWidth);
+ }
+
+ /**
+ * Return the desired height for thumbnail screenshots, which are stored in
+ * the database, and used on the bookmarks screen.
+ * @param context Context for finding out the density of the screen.
+ * @return desired height for thumbnail screenshot.
+ */
+ static int getDesiredThumbnailHeight(Context context) {
+ return context.getResources().getDimensionPixelOffset(
+ R.dimen.bookmarkThumbnailHeight);
+ }
+
+ private static Bitmap createScreenshot(WebView view, int width, int height) {
+ Picture thumbnail = view.capturePicture();
+ if (thumbnail == null) {
+ return null;
+ }
+ Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+ Canvas canvas = new Canvas(bm);
+ // May need to tweak these values to determine what is the
+ // best scale factor
+ int thumbnailWidth = thumbnail.getWidth();
+ int thumbnailHeight = thumbnail.getHeight();
+ float scaleFactor = 1.0f;
+ if (thumbnailWidth > 0) {
+ scaleFactor = (float) width / (float)thumbnailWidth;
+ } else {
+ return null;
+ }
+
+ if (view.getWidth() > view.getHeight() &&
+ thumbnailHeight < view.getHeight() && thumbnailHeight > 0) {
+ // If the device is in landscape and the page is shorter
+ // than the height of the view, center the thumnail and crop the sides
+ scaleFactor = (float) height / (float)thumbnailHeight;
+ float wx = (thumbnailWidth * scaleFactor) - width;
+ canvas.translate((int) -(wx / 2), 0);
+ }
+
+ canvas.scale(scaleFactor, scaleFactor);
+
+ thumbnail.draw(canvas);
+ return bm;
+ }
+
+ private void updateScreenshot(WebView view) {
+ // If this is a bookmarked site, add a screenshot to the database.
+ // FIXME: When should we update? Every time?
+ // FIXME: Would like to make sure there is actually something to
+ // draw, but the API for that (WebViewCore.pictureReady()) is not
+ // currently accessible here.
+
+ final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(mActivity),
+ getDesiredThumbnailHeight(mActivity));
+ if (bm == null) {
+ return;
+ }
+
+ final ContentResolver cr = mActivity.getContentResolver();
+ final String url = view.getUrl();
+ final String originalUrl = view.getOriginalUrl();
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ Cursor cursor = null;
+ try {
+ cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
+ if (cursor != null && cursor.moveToFirst()) {
+ final ByteArrayOutputStream os =
+ new ByteArrayOutputStream();
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+ ContentValues values = new ContentValues();
+ values.put(Images.THUMBNAIL, os.toByteArray());
+ values.put(Images.URL, cursor.getString(0));
+
+ do {
+ cr.update(Images.CONTENT_URI, values, null, null);
+ } while (cursor.moveToNext());
+ }
+ } catch (IllegalStateException e) {
+ // Ignore
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return null;
+ }
+ }.execute();
+ }
+
+ private class Copy implements OnMenuItemClickListener {
+ private CharSequence mText;
+
+ public boolean onMenuItemClick(MenuItem item) {
+ copy(mText);
+ return true;
+ }
+
+ public Copy(CharSequence toCopy) {
+ mText = toCopy;
+ }
+ }
+
+ private static class Download implements OnMenuItemClickListener {
+ private Activity mActivity;
+ private String mText;
+
+ public boolean onMenuItemClick(MenuItem item) {
+ DownloadHandler.onDownloadStartNoStream(mActivity, mText, null,
+ null, null);
+ return true;
+ }
+
+ public Download(Activity activity, String toDownload) {
+ mActivity = activity;
+ mText = toDownload;
+ }
+ }
+
+ private static class SelectText implements OnMenuItemClickListener {
+ private WebView mWebView;
+
+ public boolean onMenuItemClick(MenuItem item) {
+ if (mWebView != null) {
+ return mWebView.selectText();
+ }
+ return false;
+ }
+
+ public SelectText(WebView webView) {
+ mWebView = webView;
+ }
+
+ }
+
+ /********************** TODO: UI stuff *****************************/
+
+ // these methods have been copied, they still need to be cleaned up
+
+ /****************** tabs ***************************************************/
+
+ // basic tab interactions:
+
+ // it is assumed that tabcontrol already knows about the tab
+ protected void addTab(Tab tab) {
+ mUi.addTab(tab);
+ }
+
+ protected void removeTab(Tab tab) {
+ mUi.removeTab(tab);
+ mTabControl.removeTab(tab);
+ }
+
+ protected void setActiveTab(Tab tab) {
+ mTabControl.setCurrentTab(tab);
+ // the tab is guaranteed to have a webview after setCurrentTab
+ mUi.setActiveTab(tab);
+ }
+
+ protected void closeEmptyChildTab() {
+ Tab current = mTabControl.getCurrentTab();
+ if (current != null
+ && current.getWebView().copyBackForwardList().getSize() == 0) {
+ Tab parent = current.getParentTab();
+ if (parent != null) {
+ switchToTab(mTabControl.getTabIndex(parent));
+ closeTab(current);
+ }
+ }
+ }
+
+ protected void reuseTab(Tab appTab, String appId, UrlData urlData) {
+ Log.i(LOGTAG, "Reusing tab for " + appId);
+ // Dismiss the subwindow if applicable.
+ dismissSubWindow(appTab);
+ // Since we might kill the WebView, remove it from the
+ // content view first.
+ mUi.detachTab(appTab);
+ // Recreate the main WebView after destroying the old one.
+ // If the WebView has the same original url and is on that
+ // page, it can be reused.
+ boolean needsLoad =
+ mTabControl.recreateWebView(appTab, urlData);
+ // TODO: analyze why the remove and add are necessary
+ mUi.attachTab(appTab);
+ if (mTabControl.getCurrentTab() != appTab) {
+ switchToTab(mTabControl.getTabIndex(appTab));
+ if (needsLoad) {
+ loadUrlDataIn(appTab, urlData);
+ }
+ } else {
+ // If the tab was the current tab, we have to attach
+ // it to the view system again.
+ setActiveTab(appTab);
+ if (needsLoad) {
+ loadUrlDataIn(appTab, urlData);
+ }
+ }
+ }
+
+ // Remove the sub window if it exists. Also called by TabControl when the
+ // user clicks the 'X' to dismiss a sub window.
+ public void dismissSubWindow(Tab tab) {
+ removeSubWindow(tab);
+ // dismiss the subwindow. This will destroy the WebView.
+ tab.dismissSubWindow();
+ getCurrentTopWebView().requestFocus();
+ }
+
+ @Override
+ public void removeSubWindow(Tab t) {
+ if (t.getSubWebView() != null) {
+ mUi.removeSubWindow(t.getSubViewContainer());
+ }
+ }
+
+ @Override
+ public void attachSubWindow(Tab tab) {
+ if (tab.getSubWebView() != null) {
+ mUi.attachSubWindow(tab.getSubViewContainer());
+ getCurrentTopWebView().requestFocus();
+ }
+ }
+
+ // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
+ // that accepts url as string.
+
+ protected Tab openTabAndShow(String url, boolean closeOnExit, String appId) {
+ return openTabAndShow(new UrlData(url), closeOnExit, appId);
+ }
+
+ // This method does a ton of stuff. It will attempt to create a new tab
+ // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
+ // url isn't null, it will load the given url.
+
+ public Tab openTabAndShow(UrlData urlData, boolean closeOnExit,
+ String appId) {
+ final Tab currentTab = mTabControl.getCurrentTab();
+ if (mTabControl.canCreateNewTab()) {
+ final Tab tab = mTabControl.createNewTab(closeOnExit, appId,
+ urlData.mUrl, false);
+ WebView webview = tab.getWebView();
+ // We must set the new tab as the current tab to reflect the old
+ // animation behavior.
+ addTab(tab);
+ setActiveTab(tab);
+ if (!urlData.isEmpty()) {
+ loadUrlDataIn(tab, urlData);
+ }
+ return tab;
+ } else {
+ // Get rid of the subwindow if it exists
+ dismissSubWindow(currentTab);
+ if (!urlData.isEmpty()) {
+ // Load the given url.
+ loadUrlDataIn(currentTab, urlData);
+ }
+ return currentTab;
+ }
+ }
+
+ protected Tab openTab(String url, boolean forceForeground) {
+ if (mSettings.openInBackground() && !forceForeground) {
+ Tab tab = mTabControl.createNewTab();
+ if (tab != null) {
+ addTab(tab);
+ WebView view = tab.getWebView();
+ loadUrl(view, url);
+ }
+ return tab;
+ } else {
+ return openTabAndShow(url, false, null);
+ }
+ }
+
+ @Override
+ public Tab openIncognitoTab() {
+ if (mTabControl.canCreateNewTab()) {
+ Tab currentTab = mTabControl.getCurrentTab();
+ Tab tab = mTabControl.createNewTab(false, null, null, true);
+ addTab(tab);
+ setActiveTab(tab);
+ return tab;
+ }
+ return null;
+ }
+
+ /**
+ * @param index Index of the tab to change to, as defined by
+ * mTabControl.getTabIndex(Tab t).
+ * @return boolean True if we successfully switched to a different tab. If
+ * the indexth tab is null, or if that tab is the same as
+ * the current one, return false.
+ */
+ @Override
+ public boolean switchToTab(int index) {
+ Tab tab = mTabControl.getTab(index);
+ Tab currentTab = mTabControl.getCurrentTab();
+ if (tab == null || tab == currentTab) {
+ return false;
+ }
+ setActiveTab(tab);
+ return true;
+ }
+
+ @Override
+ public Tab openTabToHomePage() {
+ return openTabAndShow(mSettings.getHomePage(), false, null);
+ }
+
+ @Override
+ public void closeCurrentTab() {
+ final Tab current = mTabControl.getCurrentTab();
+ if (mTabControl.getTabCount() == 1) {
+ // This is the last tab. Open a new one, with the home
+ // page and close the current one.
+ openTabToHomePage();
+ closeTab(current);
+ return;
+ }
+ final Tab parent = current.getParentTab();
+ int indexToShow = -1;
+ if (parent != null) {
+ indexToShow = mTabControl.getTabIndex(parent);
+ } else {
+ final int currentIndex = mTabControl.getCurrentIndex();
+ // Try to move to the tab to the right
+ indexToShow = currentIndex + 1;
+ if (indexToShow > mTabControl.getTabCount() - 1) {
+ // Try to move to the tab to the left
+ indexToShow = currentIndex - 1;
+ }
+ }
+ if (switchToTab(indexToShow)) {
+ // Close window
+ closeTab(current);
+ }
+ }
+
+ /**
+ * Close the tab, remove its associated title bar, and adjust mTabControl's
+ * current tab to a valid value.
+ */
+ @Override
+ public void closeTab(Tab tab) {
+ int currentIndex = mTabControl.getCurrentIndex();
+ int removeIndex = mTabControl.getTabIndex(tab);
+ removeTab(tab);
+ if (currentIndex >= removeIndex && currentIndex != 0) {
+ currentIndex--;
+ }
+ Tab newtab = mTabControl.getTab(currentIndex);
+ setActiveTab(newtab);
+ if (!mTabControl.hasAnyOpenIncognitoTabs()) {
+ WebView.cleanupPrivateBrowsingFiles();
+ }
+ }
+
+ /**************** TODO: Url loading clean up *******************************/
+
+ // Called when loading from context menu or LOAD_URL message
+ protected void loadUrlFromContext(WebView view, String url) {
+ // In case the user enters nothing.
+ if (url != null && url.length() != 0 && view != null) {
+ url = UrlUtils.smartUrlFilter(url);
+ if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) {
+ loadUrl(view, url);
+ }
+ }
+ }
+
+ /**
+ * Load the URL into the given WebView and update the title bar
+ * to reflect the new load. Call this instead of WebView.loadUrl
+ * directly.
+ * @param view The WebView used to load url.
+ * @param url The URL to load.
+ */
+ protected void loadUrl(WebView view, String url) {
+ updateTitleBarForNewLoad(view, url);
+ view.loadUrl(url);
+ }
+
+ /**
+ * Load UrlData into a Tab and update the title bar to reflect the new
+ * load. Call this instead of UrlData.loadIn directly.
+ * @param t The Tab used to load.
+ * @param data The UrlData being loaded.
+ */
+ protected void loadUrlDataIn(Tab t, UrlData data) {
+ updateTitleBarForNewLoad(t.getWebView(), data.mUrl);
+ data.loadIn(t);
+ }
+
+ /**
+ * Resets the browser title-view to whatever it must be
+ * (for example, if we had a loading error)
+ * When we have a new page, we call resetTitle, when we
+ * have to reset the titlebar to whatever it used to be
+ * (for example, if the user chose to stop loading), we
+ * call resetTitleAndRevertLockIcon.
+ */
+ public void resetTitleAndRevertLockIcon(Tab tab) {
+ mUi.resetTitleAndRevertLockIcon(tab);
+ }
+
+ void resetTitleAndIcon(Tab tab) {
+ mUi.resetTitleAndIcon(tab);
+ }
+
+ /**
+ * If the WebView is the top window, update the title bar to reflect
+ * loading the new URL. i.e. set its text, clear the favicon (which
+ * will be set once the page begins loading), and set the progress to
+ * INITIAL_PROGRESS to show that the page has begun to load. Called
+ * by loadUrl and loadUrlDataIn.
+ * @param view The WebView that is starting a load.
+ * @param url The URL that is being loaded.
+ */
+ private void updateTitleBarForNewLoad(WebView view, String url) {
+ if (view == getCurrentTopWebView()) {
+ // TODO we should come with a tab and not with a view
+ Tab tab = mTabControl.getTabFromView(view);
+ setUrlTitle(tab, url, null);
+ mUi.setFavicon(tab, null);
+ onProgressChanged(tab, INITIAL_PROGRESS);
+ }
+ }
+
+ /**
+ * Sets a title composed of the URL and the title string.
+ * @param url The URL of the site being loaded.
+ * @param title The title of the site being loaded.
+ */
+ void setUrlTitle(Tab tab, String url, String title) {
+ tab.setCurrentUrl(url);
+ tab.setCurrentTitle(title);
+ // If we are in voice search mode, the title has already been set.
+ if (tab.isInVoiceSearchMode()) return;
+ mUi.setUrlTitle(tab, url, title);
+ }
+
+ void goBackOnePageOrQuit() {
+ Tab current = mTabControl.getCurrentTab();
+ if (current == null) {
+ /*
+ * Instead of finishing the activity, simply push this to the back
+ * of the stack and let ActivityManager to choose the foreground
+ * activity. As BrowserActivity is singleTask, it will be always the
+ * root of the task. So we can use either true or false for
+ * moveTaskToBack().
+ */
+ mActivity.moveTaskToBack(true);
+ return;
+ }
+ WebView w = current.getWebView();
+ if (w.canGoBack()) {
+ w.goBack();
+ } else {
+ // Check to see if we are closing a window that was created by
+ // another window. If so, we switch back to that window.
+ Tab parent = current.getParentTab();
+ if (parent != null) {
+ switchToTab(mTabControl.getTabIndex(parent));
+ // Now we close the other tab
+ closeTab(current);
+ } else {
+ if (current.closeOnExit()) {
+ // force the tab's inLoad() to be false as we are going to
+ // either finish the activity or remove the tab. This will
+ // ensure pauseWebViewTimers() taking action.
+ mTabControl.getCurrentTab().clearInPageLoad();
+ if (mTabControl.getTabCount() == 1) {
+ mActivity.finish();
+ return;
+ }
+ if (mActivityPaused) {
+ Log.e(LOGTAG, "BrowserActivity is already paused "
+ + "while handing goBackOnePageOrQuit.");
+ }
+ pauseWebViewTimers(true);
+ removeTab(current);
+ }
+ /*
+ * Instead of finishing the activity, simply push this to the back
+ * of the stack and let ActivityManager to choose the foreground
+ * activity. As BrowserActivity is singleTask, it will be always the
+ * root of the task. So we can use either true or false for
+ * moveTaskToBack().
+ */
+ mActivity.moveTaskToBack(true);
+ }
+ }
+ }
+
+ /**
+ * Feed the previously stored results strings to the BrowserProvider so that
+ * the SearchDialog will show them instead of the standard searches.
+ * @param result String to show on the editable line of the SearchDialog.
+ */
+ @Override
+ public void showVoiceSearchResults(String result) {
+ ContentProviderClient client = mActivity.getContentResolver()
+ .acquireContentProviderClient(Browser.BOOKMARKS_URI);
+ ContentProvider prov = client.getLocalContentProvider();
+ BrowserProvider bp = (BrowserProvider) prov;
+ bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults());
+ client.release();
+
+ Bundle bundle = createGoogleSearchSourceBundle(
+ GOOGLE_SEARCH_SOURCE_SEARCHKEY);
+ bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true);
+ startSearch(result, false, bundle, false);
+ }
+
+ private void startSearch(String initialQuery, boolean selectInitialQuery,
+ Bundle appSearchData, boolean globalSearch) {
+ 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);
+ }
+ mActivity.startSearch(initialQuery, selectInitialQuery, appSearchData,
+ globalSearch);
+ }
+
+ private Bundle createGoogleSearchSourceBundle(String source) {
+ Bundle bundle = new Bundle();
+ bundle.putString(Search.SOURCE, source);
+ return bundle;
+ }
+
+ /**
+ * handle key events in browser
+ *
+ * @param keyCode
+ * @param event
+ * @return true if handled, false to pass to super
+ */
+ boolean onKeyDown(int keyCode, KeyEvent event) {
+ // Even if MENU is already held down, we need to call to super to open
+ // the IME on long press.
+ if (KeyEvent.KEYCODE_MENU == keyCode) {
+ mMenuIsDown = true;
+ return false;
+ }
+ // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
+ // still down, we don't want to trigger the search. Pretend to consume
+ // the key and do nothing.
+ if (mMenuIsDown) return true;
+
+ switch(keyCode) {
+ case KeyEvent.KEYCODE_SPACE:
+ // WebView/WebTextView handle the keys in the KeyDown. As
+ // the Activity's shortcut keys are only handled when WebView
+ // doesn't, have to do it in onKeyDown instead of onKeyUp.
+ if (event.isShiftPressed()) {
+ pageUp();
+ } else {
+ pageDown();
+ }
+ return true;
+ case KeyEvent.KEYCODE_BACK:
+ if (event.getRepeatCount() == 0) {
+ event.startTracking();
+ return true;
+ } else if (mUi.showsWeb()
+ && event.isLongPress()) {
+ bookmarksOrHistoryPicker(true);
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch(keyCode) {
+ case KeyEvent.KEYCODE_MENU:
+ mMenuIsDown = false;
+ break;
+ case KeyEvent.KEYCODE_BACK:
+ if (event.isTracking() && !event.isCanceled()) {
+ onBackKey();
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ public boolean isMenuDown() {
+ return mMenuIsDown;
+ }
+
+ public void setupAutoFill(Message message) {
+ // Open the settings activity at the AutoFill profile fragment so that
+ // the user can create a new profile. When they return, we will dispatch
+ // the message so that we can autofill the form using their new profile.
+ Intent intent = new Intent(mActivity, BrowserPreferencesPage.class);
+ intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
+ AutoFillSettingsFragment.class.getName());
+ mAutoFillSetupMessage = message;
+ mActivity.startActivityForResult(intent, AUTOFILL_SETUP);
+ }
+}
diff --git a/src/com/android/browser/DateSortedExpandableListAdapter.java b/src/com/android/browser/DateSortedExpandableListAdapter.java
index 1d04493aa..a48efe679 100644
--- a/src/com/android/browser/DateSortedExpandableListAdapter.java
+++ b/src/com/android/browser/DateSortedExpandableListAdapter.java
@@ -17,65 +17,55 @@
package com.android.browser;
import android.content.Context;
-import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
-import android.os.Handler;
-import android.provider.BaseColumns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.DateSorter;
-import android.widget.ExpandableListAdapter;
+import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;
-import java.util.Vector;
-
/**
* ExpandableListAdapter which separates data into categories based on date.
* Used for History and Downloads.
*/
-public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
+public class DateSortedExpandableListAdapter extends BaseExpandableListAdapter {
// Array for each of our bins. Each entry represents how many items are
// in that bin.
private int mItemMap[];
// This is our GroupCount. We will have at most DateSorter.DAY_COUNT
// bins, less if the user has no items in one or more bins.
private int mNumberOfBins;
- private Vector<DataSetObserver> mObservers;
private Cursor mCursor;
private DateSorter mDateSorter;
private int mDateIndex;
private int mIdIndex;
private Context mContext;
- private class ChangeObserver extends ContentObserver {
- public ChangeObserver() {
- super(new Handler());
- }
+ boolean mDataValid;
+ DataSetObserver mDataSetObserver = new DataSetObserver() {
@Override
- public boolean deliverSelfNotifications() {
- return true;
+ public void onChanged() {
+ mDataValid = true;
+ notifyDataSetChanged();
}
@Override
- public void onChange(boolean selfChange) {
- refreshData();
+ public void onInvalidated() {
+ mDataValid = false;
+ notifyDataSetInvalidated();
}
- }
-
- public DateSortedExpandableListAdapter(Context context, Cursor cursor,
- int dateIndex) {
+ };
+
+ public DateSortedExpandableListAdapter(Context context, int dateIndex) {
mContext = context;
mDateSorter = new DateSorter(context);
- mObservers = new Vector<DataSetObserver>();
- mCursor = cursor;
- mIdIndex = cursor.getColumnIndexOrThrow(BaseColumns._ID);
- cursor.registerContentObserver(new ChangeObserver());
mDateIndex = dateIndex;
- buildMap();
+ mDataValid = false;
+ mIdIndex = -1;
}
/**
@@ -122,6 +112,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @return corresponding byte array from the Cursor.
*/
/* package */ byte[] getBlob(int cursorIndex) {
+ if (!mDataValid) return null;
return mCursor.getBlob(cursorIndex);
}
@@ -138,6 +129,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @return corresponding integer from the Cursor.
*/
/* package */ int getInt(int cursorIndex) {
+ if (!mDataValid) return 0;
return mCursor.getInt(cursorIndex);
}
@@ -146,6 +138,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* already been moved to the correct position.
*/
/* package */ long getLong(int cursorIndex) {
+ if (!mDataValid) return 0;
return mCursor.getLong(cursorIndex);
}
@@ -158,6 +151,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @return corresponding String from the Cursor.
*/
/* package */ String getString(int cursorIndex) {
+ if (!mDataValid) return null;
return mCursor.getString(cursorIndex);
}
@@ -166,6 +160,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @param childId ID of the child view in question.
* @return int Group position of the containing group.
/* package */ int groupFromChildId(long childId) {
+ if (!mDataValid) return -1;
int group = -1;
for (mCursor.moveToFirst(); !mCursor.isAfterLast();
mCursor.moveToNext()) {
@@ -173,11 +168,15 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
int bin = mDateSorter.getIndex(getLong(mDateIndex));
// bin is the same as the group if the number of bins is the
// same as DateSorter
- if (mDateSorter.DAY_COUNT == mNumberOfBins) return bin;
+ if (DateSorter.DAY_COUNT == mNumberOfBins) {
+ return bin;
+ }
// There are some empty bins. Find the corresponding group.
group = 0;
for (int i = 0; i < bin; i++) {
- if (mItemMap[i] != 0) group++;
+ if (mItemMap[i] != 0) {
+ group++;
+ }
}
break;
}
@@ -193,6 +192,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @return The corresponding bin that holds that group.
*/
private int groupPositionToBin(int groupPosition) {
+ if (!mDataValid) return -1;
if (groupPosition < 0 || groupPosition >= DateSorter.DAY_COUNT) {
throw new AssertionError("group position out of range");
}
@@ -241,7 +241,9 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
*/
/* package */ boolean moveCursorToChildPosition(int groupPosition,
int childPosition) {
- if (mCursor.isClosed()) return false;
+ if (!mDataValid || mCursor.isClosed()) {
+ return false;
+ }
groupPosition = groupPositionToBin(groupPosition);
int index = childPosition;
for (int i = 0; i < groupPosition; i++) {
@@ -250,19 +252,34 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
return mCursor.moveToPosition(index);
}
- /* package */ void refreshData() {
- if (mCursor.isClosed()) {
+ public void changeCursor(Cursor cursor) {
+ if (cursor == mCursor) {
return;
}
- mCursor.requery();
- buildMap();
- for (DataSetObserver o : mObservers) {
- o.onChanged();
+ if (mCursor != null) {
+ mCursor.unregisterDataSetObserver(mDataSetObserver);
+ mCursor.close();
+ }
+ mCursor = cursor;
+ if (cursor != null) {
+ cursor.registerDataSetObserver(mDataSetObserver);
+ mIdIndex = cursor.getColumnIndexOrThrow("_id");
+ mDataValid = true;
+ buildMap();
+ // notify the observers about the new cursor
+ notifyDataSetChanged();
+ } else {
+ mIdIndex = -1;
+ mDataValid = false;
+ // notify the observers about the lack of a data set
+ notifyDataSetInvalidated();
}
}
+ @Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
+ if (!mDataValid) throw new IllegalStateException("Data is not valid");
TextView item;
if (null == convertView || !(convertView instanceof TextView)) {
LayoutInflater factory = LayoutInflater.from(mContext);
@@ -275,73 +292,87 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
return item;
}
+ @Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
+ if (!mDataValid) throw new IllegalStateException("Data is not valid");
return null;
}
+ @Override
public boolean areAllItemsEnabled() {
return true;
}
+ @Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
+ @Override
public int getGroupCount() {
+ if (!mDataValid) return 0;
return mNumberOfBins;
}
+ @Override
public int getChildrenCount(int groupPosition) {
+ if (!mDataValid) return 0;
return mItemMap[groupPositionToBin(groupPosition)];
}
+ @Override
public Object getGroup(int groupPosition) {
return null;
}
+ @Override
public Object getChild(int groupPosition, int childPosition) {
return null;
}
+ @Override
public long getGroupId(int groupPosition) {
+ if (!mDataValid) return 0;
return groupPosition;
}
+ @Override
public long getChildId(int groupPosition, int childPosition) {
+ if (!mDataValid) return 0;
if (moveCursorToChildPosition(groupPosition, childPosition)) {
return getLong(mIdIndex);
}
return 0;
}
+ @Override
public boolean hasStableIds() {
return true;
}
- public void registerDataSetObserver(DataSetObserver observer) {
- mObservers.add(observer);
- }
-
- public void unregisterDataSetObserver(DataSetObserver observer) {
- mObservers.remove(observer);
- }
-
+ @Override
public void onGroupExpanded(int groupPosition) {
}
+ @Override
public void onGroupCollapsed(int groupPosition) {
}
+ @Override
public long getCombinedChildId(long groupId, long childId) {
+ if (!mDataValid) return 0;
return childId;
}
+ @Override
public long getCombinedGroupId(long groupId) {
+ if (!mDataValid) return 0;
return groupId;
}
+ @Override
public boolean isEmpty() {
- return mCursor.isClosed() || mCursor.getCount() == 0;
+ return !mDataValid || mCursor == null || mCursor.isClosed() || mCursor.getCount() == 0;
}
}
diff --git a/src/com/android/browser/Dots.java b/src/com/android/browser/Dots.java
deleted file mode 100644
index eb8d49347..000000000
--- a/src/com/android/browser/Dots.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2008 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.util.AttributeSet;
-import android.view.Gravity;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import java.util.Map;
-
-/**
- * Displays a series of dots. The selected one is highlighted.
- * No animations yet. Nothing fancy.
- */
-class Dots extends LinearLayout {
-
- private static final int MAX_DOTS = 8;
- private int mSelected = -1;
-
- public Dots(Context context) {
- this(context, null);
- }
-
- public Dots(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setGravity(Gravity.CENTER);
- setPadding(0, 4, 0, 4);
-
- LayoutParams lp =
- new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
-
- for (int i = 0; i < MAX_DOTS; i++) {
- ImageView dotView = new ImageView(mContext);
- dotView.setImageResource(R.drawable.page_indicator_unselected2);
- addView(dotView, lp);
- }
- }
-
- /**
- * @param dotCount if less than 1 or greater than MAX_DOTS, Dots
- * disappears
- */
- public void setDotCount(int dotCount) {
- if (dotCount > 1 && dotCount <= MAX_DOTS) {
- setVisibility(VISIBLE);
- for (int i = 0; i < MAX_DOTS; i++) {
- getChildAt(i).setVisibility(i < dotCount? VISIBLE : GONE);
- }
- } else {
- setVisibility(GONE);
- }
- }
-
- public void setSelected(int index) {
- if (index < 0 || index >= MAX_DOTS) return;
-
- if (mSelected >= 0) {
- // Unselect old
- ((ImageView)getChildAt(mSelected)).setImageResource(
- R.drawable.page_indicator_unselected2);
- }
- ((ImageView)getChildAt(index)).setImageResource(R.drawable.page_indicator);
- mSelected = index;
- }
-}
diff --git a/src/com/android/browser/DownloadHandler.java b/src/com/android/browser/DownloadHandler.java
new file mode 100644
index 000000000..cbf26f40f
--- /dev/null
+++ b/src/com/android/browser/DownloadHandler.java
@@ -0,0 +1,218 @@
+/*
+ * 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.app.Activity;
+import android.app.AlertDialog;
+import android.app.DownloadManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.net.WebAddress;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.CookieManager;
+import android.webkit.URLUtil;
+import android.widget.Toast;
+
+/**
+ * Handle download requests
+ */
+public class DownloadHandler {
+
+ private static final boolean LOGD_ENABLED =
+ com.android.browser.Browser.LOGD_ENABLED;
+
+ private static final String LOGTAG = "DLHandler";
+
+ /**
+ * Notify the host application a download should be done, or that
+ * the data should be streamed if a streaming viewer is available.
+ * @param activity Activity requesting the download.
+ * @param url The full url to the content that should be downloaded
+ * @param userAgent User agent of the downloading application.
+ * @param contentDisposition Content-disposition http header, if present.
+ * @param mimetype The mimetype of the content reported by the server
+ */
+ public static void onDownloadStart(Activity activity, String url,
+ String userAgent, String contentDisposition, String mimetype) {
+ // if we're dealing wih A/V content that's not explicitly marked
+ // for download, check if it's streamable.
+ if (contentDisposition == null
+ || !contentDisposition.regionMatches(
+ true, 0, "attachment", 0, 10)) {
+ // query the package manager to see if there's a registered handler
+ // that matches.
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.parse(url), mimetype);
+ ResolveInfo info = activity.getPackageManager().resolveActivity(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (info != null) {
+ ComponentName myName = activity.getComponentName();
+ // If we resolved to ourselves, we don't want to attempt to
+ // load the url only to try and download it again.
+ if (!myName.getPackageName().equals(
+ info.activityInfo.packageName)
+ || !myName.getClassName().equals(
+ info.activityInfo.name)) {
+ // someone (other than us) knows how to handle this mime
+ // type with this scheme, don't download.
+ try {
+ activity.startActivity(intent);
+ return;
+ } catch (ActivityNotFoundException ex) {
+ if (LOGD_ENABLED) {
+ Log.d(LOGTAG, "activity not found for " + mimetype
+ + " over " + Uri.parse(url).getScheme(),
+ ex);
+ }
+ // Best behavior is to fall back to a download in this
+ // case
+ }
+ }
+ }
+ }
+ onDownloadStartNoStream(activity, url, userAgent, contentDisposition,
+ mimetype);
+ }
+
+ // This is to work around the fact that java.net.URI throws Exceptions
+ // instead of just encoding URL's properly
+ // Helper method for onDownloadStartNoStream
+ private static String encodePath(String path) {
+ char[] chars = path.toCharArray();
+
+ boolean needed = false;
+ for (char c : chars) {
+ if (c == '[' || c == ']') {
+ needed = true;
+ break;
+ }
+ }
+ if (needed == false) {
+ return path;
+ }
+
+ StringBuilder sb = new StringBuilder("");
+ for (char c : chars) {
+ if (c == '[' || c == ']') {
+ sb.append('%');
+ sb.append(Integer.toHexString(c));
+ } else {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Notify the host application a download should be done, even if there
+ * is a streaming viewer available for thise type.
+ * @param activity Activity requesting the download.
+ * @param url The full url to the content that should be downloaded
+ * @param userAgent User agent of the downloading application.
+ * @param contentDisposition Content-disposition http header, if present.
+ * @param mimetype The mimetype of the content reported by the server
+ */
+ /*package */ static void onDownloadStartNoStream(Activity activity,
+ String url, String userAgent, String contentDisposition,
+ String mimetype) {
+
+ String filename = URLUtil.guessFileName(url,
+ contentDisposition, mimetype);
+
+ // Check to see if we have an SDCard
+ String status = Environment.getExternalStorageState();
+ if (!status.equals(Environment.MEDIA_MOUNTED)) {
+ int title;
+ String msg;
+
+ // Check to see if the SDCard is busy, same as the music app
+ if (status.equals(Environment.MEDIA_SHARED)) {
+ msg = activity.getString(R.string.download_sdcard_busy_dlg_msg);
+ title = R.string.download_sdcard_busy_dlg_title;
+ } else {
+ msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename);
+ title = R.string.download_no_sdcard_dlg_title;
+ }
+
+ new AlertDialog.Builder(activity)
+ .setTitle(title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(msg)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ return;
+ }
+
+ // java.net.URI is a lot stricter than KURL so we have to encode some
+ // extra characters. Fix for b 2538060 and b 1634719
+ WebAddress webAddress;
+ try {
+ webAddress = new WebAddress(url);
+ webAddress.setPath(encodePath(webAddress.getPath()));
+ } catch (Exception e) {
+ // This only happens for very bad urls, we want to chatch the
+ // exception here
+ Log.e(LOGTAG, "Exception trying to parse url:" + url);
+ return;
+ }
+
+ String addressString = webAddress.toString();
+ Uri uri = Uri.parse(addressString);
+ final DownloadManager.Request request = new DownloadManager.Request(uri);
+ request.setMimeType(mimetype);
+ request.setDestinationInExternalFilesDir(activity, null, filename);
+ // let this downloaded file be scanned by MediaScanner - so that it can
+ // show up in Gallery app, for example.
+ request.allowScanningByMediaScanner();
+ request.setDescription(webAddress.getHost());
+ // XXX: Have to use the old url since the cookies were stored using the
+ // old percent-encoded url.
+ String cookies = CookieManager.getInstance().getCookie(url);
+ request.addRequestHeader("cookie", cookies);
+ request.setNotificationVisibility(
+ DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ if (mimetype == null) {
+ if (TextUtils.isEmpty(addressString)) {
+ return;
+ }
+ // We must have long pressed on a link or image to download it. We
+ // are not sure of the mimetype in this case, so do a head request
+ new FetchUrlMimeType(activity, request, addressString, cookies,
+ userAgent).start();
+ } else {
+ final DownloadManager manager
+ = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
+ new Thread("Browser download") {
+ public void run() {
+ manager.enqueue(request);
+ }
+ }.start();
+ }
+ Toast.makeText(activity, R.string.download_pending, Toast.LENGTH_SHORT)
+ .show();
+ }
+
+}
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
index e8a912cd8..768eab584 100644
--- a/src/com/android/browser/DownloadTouchIcon.java
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -16,27 +16,28 @@
package com.android.browser;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.conn.params.ConnRouteParams;
+
import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.net.http.AndroidHttpClient;
import android.net.Proxy;
+import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
-import android.provider.Browser;
+import android.os.Bundle;
+import android.os.Message;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Images;
import android.webkit.WebView;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.params.HttpClientParams;
-import org.apache.http.conn.params.ConnRouteParams;
-
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -46,10 +47,17 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
private Cursor mCursor;
private final String mOriginalUrl;
private final String mUrl;
- private final String mUserAgent;
+ private final String mUserAgent; // Sites may serve a different icon to different UAs
+ private Message mMessage;
+
private final Context mContext;
/* package */ Tab mTab;
+ /**
+ * Use this ctor to store the touch icon in the bookmarks database for
+ * the originalUrl so we take account of redirects. Used when the user
+ * bookmarks a page from outside the bookmarks activity.
+ */
public DownloadTouchIcon(Tab tab, Context ctx, ContentResolver cr, WebView view) {
mTab = tab;
mContext = ctx;
@@ -60,6 +68,13 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
mUserAgent = view.getSettings().getUserAgentString();
}
+ /**
+ * Use this ctor to download the touch icon and update the bookmarks database
+ * entry for the given url. Used when the user creates a bookmark from
+ * within the bookmarks activity and there haven't been any redirects.
+ * TODO: Would be nice to set the user agent here so that there is no
+ * potential for the three different ctors here to return different icons.
+ */
public DownloadTouchIcon(Context ctx, ContentResolver cr, String url) {
mTab = null;
mContext = ctx;
@@ -69,15 +84,33 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
mUserAgent = null;
}
+ /**
+ * Use this ctor to not store the touch icon in a database, rather add it to
+ * the passed Message's data bundle with the key
+ * {@link BrowserContract.Bookmarks#TOUCH_ICON} and then send the message.
+ */
+ public DownloadTouchIcon(Context context, Message msg, String userAgent) {
+ mMessage = msg;
+ mContext = context;
+ mContentResolver = null;
+ mOriginalUrl = null;
+ mUrl = null;
+ mUserAgent = userAgent;
+ }
+
@Override
public Void doInBackground(String... values) {
- mCursor = BrowserBookmarksAdapter.queryBookmarksForUrl(mContentResolver,
- mOriginalUrl, mUrl, true);
- if (mCursor != null && mCursor.getCount() > 0) {
- String url = values[0];
+ if (mContentResolver != null) {
+ mCursor = Bookmarks.queryCombinedForUrl(mContentResolver,
+ mOriginalUrl, mUrl);
+ }
+
+ boolean inDatabase = mCursor != null && mCursor.getCount() > 0;
+
+ String url = values[0];
- AndroidHttpClient client = AndroidHttpClient.newInstance(
- mUserAgent);
+ if (inDatabase || mMessage != null) {
+ AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
HttpHost httpHost = Proxy.getPreferredHttpHost(mContext, url);
if (httpHost != null) {
ConnRouteParams.setDefaultProxy(client.getParams(), httpHost);
@@ -90,7 +123,6 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
try {
HttpResponse response = client.execute(request);
-
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
if (entity != null) {
@@ -98,7 +130,12 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
if (content != null) {
Bitmap icon = BitmapFactory.decodeStream(
content, null, null);
- storeIcon(icon);
+ if (inDatabase) {
+ storeIcon(icon);
+ } else if (mMessage != null) {
+ Bundle b = mMessage.getData();
+ b.putParcelable(BrowserContract.Bookmarks.TOUCH_ICON, icon);
+ }
}
}
}
@@ -110,9 +147,15 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
client.close();
}
}
+
if (mCursor != null) {
mCursor.close();
}
+
+ if (mMessage != null) {
+ mMessage.sendToTarget();
+ }
+
return null;
}
@@ -134,17 +177,16 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
return;
}
- final ByteArrayOutputStream os = new ByteArrayOutputStream();
- icon.compress(Bitmap.CompressFormat.PNG, 100, os);
- ContentValues values = new ContentValues();
- values.put(Browser.BookmarkColumns.TOUCH_ICON,
- os.toByteArray());
-
if (mCursor.moveToFirst()) {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ icon.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+ ContentValues values = new ContentValues();
+ values.put(Images.TOUCH_ICON, os.toByteArray());
+ values.put(Images.URL, mCursor.getString(0));
+
do {
- mContentResolver.update(ContentUris.withAppendedId(
- Browser.BOOKMARKS_URI, mCursor.getInt(0)),
- values, null, null);
+ mContentResolver.update(Images.CONTENT_URI, values, null, null);
} while (mCursor.moveToNext());
}
}
diff --git a/src/com/android/browser/FetchUrlMimeType.java b/src/com/android/browser/FetchUrlMimeType.java
index 62d877edf..2538d90ad 100644
--- a/src/com/android/browser/FetchUrlMimeType.java
+++ b/src/com/android/browser/FetchUrlMimeType.java
@@ -16,78 +16,67 @@
package com.android.browser;
-import android.content.ContentValues;
-import android.net.Proxy;
-import android.net.Uri;
-import android.net.http.AndroidHttpClient;
-
+import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
-import org.apache.http.Header;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.conn.params.ConnRouteParams;
-import java.io.IOException;
-
-import android.os.AsyncTask;
-import android.provider.Downloads;
+import android.app.Activity;
+import android.app.DownloadManager;
+import android.content.Context;
+import android.net.Proxy;
+import android.net.http.AndroidHttpClient;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;
+import java.io.IOException;
+
/**
* This class is used to pull down the http headers of a given URL so that
* we can analyse the mimetype and make any correction needed before we give
- * the URL to the download manager. The ContentValues class holds the
- * content that would be provided to the download manager, so that on
- * completion of checking the mimetype, we can issue the download to
- * the download manager.
+ * the URL to the download manager.
* This operation is needed when the user long-clicks on a link or image and
* we don't know the mimetype. If the user just clicks on the link, we will
* do the same steps of correcting the mimetype down in
* android.os.webkit.LoadListener rather than handling it here.
*
*/
-class FetchUrlMimeType extends AsyncTask<ContentValues, String, ContentValues> {
+class FetchUrlMimeType extends Thread {
- BrowserActivity mActivity;
- ContentValues mValues;
+ private Activity mActivity;
+ private DownloadManager.Request mRequest;
+ private String mUri;
+ private String mCookies;
+ private String mUserAgent;
- public FetchUrlMimeType(BrowserActivity activity) {
+ public FetchUrlMimeType(Activity activity, DownloadManager.Request request,
+ String uri, String cookies, String userAgent) {
mActivity = activity;
+ mRequest = request;
+ mUri = uri;
+ mCookies = cookies;
+ mUserAgent = userAgent;
}
@Override
- public ContentValues doInBackground(ContentValues... values) {
- mValues = values[0];
-
- // Check to make sure we have a URI to download
- String uri = mValues.getAsString(Downloads.Impl.COLUMN_URI);
- if (uri == null || uri.length() == 0) {
- return null;
- }
-
+ public void run() {
// User agent is likely to be null, though the AndroidHttpClient
// seems ok with that.
- AndroidHttpClient client = AndroidHttpClient.newInstance(
- mValues.getAsString(Downloads.Impl.COLUMN_USER_AGENT));
- HttpHost httpHost = Proxy.getPreferredHttpHost(mActivity, uri);
+ AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
+ HttpHost httpHost = Proxy.getPreferredHttpHost(mActivity, mUri);
if (httpHost != null) {
ConnRouteParams.setDefaultProxy(client.getParams(), httpHost);
}
- HttpHead request = new HttpHead(uri);
+ HttpHead request = new HttpHead(mUri);
- String cookie = mValues.getAsString(Downloads.Impl.COLUMN_COOKIE_DATA);
- if (cookie != null && cookie.length() > 0) {
- request.addHeader("Cookie", cookie);
- }
-
- String referer = mValues.getAsString(Downloads.Impl.COLUMN_REFERER);
- if (referer != null && referer.length() > 0) {
- request.addHeader("Referer", referer);
+ if (mCookies != null && mCookies.length() > 0) {
+ request.addHeader("Cookie", mCookies);
}
HttpResponse response;
- ContentValues result = new ContentValues();
+ String mimeType = null;
+ String contentDisposition = null;
try {
response = client.execute(request);
// We could get a redirect here, but if we do lets let
@@ -96,16 +85,15 @@ class FetchUrlMimeType extends AsyncTask<ContentValues, String, ContentValues> {
if (response.getStatusLine().getStatusCode() == 200) {
Header header = response.getFirstHeader("Content-Type");
if (header != null) {
- String mimeType = header.getValue();
+ mimeType = header.getValue();
final int semicolonIndex = mimeType.indexOf(';');
if (semicolonIndex != -1) {
mimeType = mimeType.substring(0, semicolonIndex);
}
- result.put("Content-Type", mimeType);
}
Header contentDispositionHeader = response.getFirstHeader("Content-Disposition");
if (contentDispositionHeader != null) {
- result.put("Content-Disposition", contentDispositionHeader.getValue());
+ contentDisposition = contentDispositionHeader.getValue();
}
}
} catch (IllegalArgumentException ex) {
@@ -116,32 +104,25 @@ class FetchUrlMimeType extends AsyncTask<ContentValues, String, ContentValues> {
client.close();
}
- return result;
- }
-
- @Override
- public void onPostExecute(ContentValues values) {
- final String mimeType = values.getAsString("Content-Type");
- final String contentDisposition = values.getAsString("Content-Disposition");
if (mimeType != null) {
- String url = mValues.getAsString(Downloads.Impl.COLUMN_URI);
if (mimeType.equalsIgnoreCase("text/plain") ||
mimeType.equalsIgnoreCase("application/octet-stream")) {
String newMimeType =
MimeTypeMap.getSingleton().getMimeTypeFromExtension(
- MimeTypeMap.getFileExtensionFromUrl(url));
+ MimeTypeMap.getFileExtensionFromUrl(mUri));
if (newMimeType != null) {
- mValues.put(Downloads.Impl.COLUMN_MIME_TYPE, newMimeType);
+ mRequest.setMimeType(newMimeType);
}
}
- String filename = URLUtil.guessFileName(url,
- contentDisposition, mimeType);
- mValues.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, filename);
+ String filename = URLUtil.guessFileName(mUri, contentDisposition,
+ mimeType);
+ mRequest.setDestinationInExternalFilesDir(mActivity, null, filename);
}
// Start the download
- final Uri contentUri =
- mActivity.getContentResolver().insert(Downloads.Impl.CONTENT_URI, mValues);
+ DownloadManager manager = (DownloadManager) mActivity.getSystemService(
+ Context.DOWNLOAD_SERVICE);
+ manager.enqueue(mRequest);
}
}
diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java
deleted file mode 100644
index 726138ef0..000000000
--- a/src/com/android/browser/FindDialog.java
+++ /dev/null
@@ -1,247 +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.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.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 WebDialog implements TextWatcher {
- private TextView mMatches;
-
- // Views with which the user can interact.
- private EditText mEditText;
- private View mNextButton;
- 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 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();
- }
- };
-
- private void disableButtons() {
- mPrevButton.setEnabled(false);
- mNextButton.setEnabled(false);
- mPrevButton.setFocusable(false);
- mNextButton.setFocusable(false);
- }
-
- /* package */ FindDialog(BrowserActivity context) {
- super(context);
-
- 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);
- button.setOnClickListener(mFindListener);
- mNextButton = button;
-
- button = findViewById(R.id.previous);
- button.setOnClickListener(mFindPreviousListener);
- mPrevButton = button;
-
- mMatches = (TextView) findViewById(R.id.matches);
- mMatchesView = findViewById(R.id.matches_view);
- disableButtons();
-
- }
-
- /**
- * 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();
- 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) {
- 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);
- }
-
- private void findNext() {
- if (mWebView == null) {
- 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();
- Spannable span = (Spannable) mEditText.getText();
- 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
- public void beforeTextChanged(CharSequence s,
- int start,
- int count,
- int after) {
- }
-
- public void onTextChanged(CharSequence s,
- int start,
- int before,
- int count) {
- findAll();
- }
-
- private void findAll() {
- if (mWebView == null) {
- throw new AssertionError(
- "No WebView for FindDialog::findAll");
- }
- CharSequence find = mEditText.getText();
- if (0 == find.length()) {
- disableButtons();
- mWebView.clearMatches();
- mMatchesView.setVisibility(View.INVISIBLE);
- } else {
- mMatchesView.setVisibility(View.VISIBLE);
- int found = mWebView.findAll(find.toString());
- mMatchesFound = true;
- setMatchesFound(found);
- if (found < 2) {
- disableButtons();
- if (found == 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);
- mNextButton.setFocusable(true);
- mPrevButton.setEnabled(true);
- mNextButton.setEnabled(true);
- }
- }
- }
-
- 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, mNumberOfMatches,
- mWebView.findIndex() + 1, mNumberOfMatches);
-
- mMatches.setText(template);
- }
-
- public void afterTextChanged(Editable s) {
- }
-}
diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java
index 72e1b19e5..11198f04d 100644
--- a/src/com/android/browser/HistoryItem.java
+++ b/src/com/android/browser/HistoryItem.java
@@ -18,12 +18,8 @@
package com.android.browser;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.provider.Browser;
import android.view.View;
import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.TextView;
/**
* Layout representing a history item in the classic history viewer.
@@ -45,12 +41,13 @@ import android.widget.TextView;
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
- Bookmarks.addBookmark(mContext,
- mContext.getContentResolver(), mUrl, getName(), null, true);
+ // FIXME: For now, add at the root level. Should we
+ // open AddBookmark from here?
+ Bookmarks.addBookmark(getContext(), true, mUrl, getName(), null, true, 0);
LogTag.logBookmarkAdded(mUrl, "history");
} else {
- Bookmarks.removeFromBookmarks(mContext,
- mContext.getContentResolver(), mUrl, getName());
+ Bookmarks.removeFromBookmarks(getContext(),
+ getContext().getContentResolver(), mUrl, getName());
}
}
};
diff --git a/src/com/android/browser/HttpAuthenticationDialog.java b/src/com/android/browser/HttpAuthenticationDialog.java
new file mode 100644
index 000000000..a9ba332e3
--- /dev/null
+++ b/src/com/android/browser/HttpAuthenticationDialog.java
@@ -0,0 +1,153 @@
+/*
+ * 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.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+/**
+ * HTTP authentication dialog.
+ */
+public class HttpAuthenticationDialog {
+
+ private final Context mContext;
+
+ private final String mHost;
+ private final String mRealm;
+
+ private AlertDialog mDialog;
+ private TextView mUsernameView;
+ private TextView mPasswordView;
+
+ private OkListener mOkListener;
+ private CancelListener mCancelListener;
+
+ /**
+ * Creates an HTTP authentication dialog.
+ */
+ public HttpAuthenticationDialog(Context context, String host, String realm) {
+ mContext = context;
+ mHost = host;
+ mRealm = realm;
+ createDialog();
+ }
+
+ private String getUsername() {
+ return mUsernameView.getText().toString();
+ }
+
+ private String getPassword() {
+ return mPasswordView.getText().toString();
+ }
+
+ /**
+ * Sets the listener that will be notified when the user submits the credentials.
+ */
+ public void setOkListener(OkListener okListener) {
+ mOkListener = okListener;
+ }
+
+ /**
+ * Sets the listener that will be notified when the user cancels the authentication
+ * dialog.
+ */
+ public void setCancelListener(CancelListener cancelListener) {
+ mCancelListener = cancelListener;
+ }
+
+ /**
+ * Shows the dialog.
+ */
+ public void show() {
+ mDialog.show();
+ mUsernameView.requestFocus();
+ }
+
+ /**
+ * Hides, recreates, and shows the dialog. This can be used to handle configuration changes.
+ */
+ public void reshow() {
+ String username = getUsername();
+ String password = getPassword();
+ int focusId = mDialog.getCurrentFocus().getId();
+ mDialog.dismiss();
+ createDialog();
+ mDialog.show();
+ if (username != null) {
+ mUsernameView.setText(username);
+ }
+ if (password != null) {
+ mPasswordView.setText(password);
+ }
+ if (focusId != 0) {
+ mDialog.findViewById(focusId).requestFocus();
+ } else {
+ mUsernameView.requestFocus();
+ }
+ }
+
+ private void createDialog() {
+ LayoutInflater factory = LayoutInflater.from(mContext);
+ View v = factory.inflate(R.layout.http_authentication, null);
+ mUsernameView = (TextView) v.findViewById(R.id.username_edit);
+ mPasswordView = (TextView) v.findViewById(R.id.password_edit);
+
+ String title = mContext.getText(R.string.sign_in_to).toString().replace(
+ "%s1", mHost).replace("%s2", mRealm);
+
+ mDialog = new AlertDialog.Builder(mContext)
+ .setTitle(title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setView(v)
+ .setPositiveButton(R.string.action, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ if (mOkListener != null) {
+ mOkListener.onOk(mHost, mRealm, getUsername(), getPassword());
+ }
+ }})
+ .setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ if (mCancelListener != null) mCancelListener.onCancel();
+ }})
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ if (mCancelListener != null) mCancelListener.onCancel();
+ }})
+ .create();
+
+ // Make the IME appear when the dialog is displayed if applicable.
+ mDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+ }
+
+ /**
+ * Interface for listeners that are notified when the user submits the credentials.
+ */
+ public interface OkListener {
+ void onOk(String host, String realm, String username, String password);
+ }
+
+ /**
+ * Interface for listeners that are notified when the user cancels the dialog.
+ */
+ public interface CancelListener {
+ void onCancel();
+ }
+}
diff --git a/src/com/android/browser/IntentHandler.java b/src/com/android/browser/IntentHandler.java
new file mode 100644
index 000000000..040af8178
--- /dev/null
+++ b/src/com/android/browser/IntentHandler.java
@@ -0,0 +1,371 @@
+/*
+ * 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 com.android.browser.search.SearchEngine;
+import com.android.common.Search;
+import com.android.common.speech.LoggingEvents;
+
+import android.app.Activity;
+import android.app.SearchManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.Browser;
+import android.provider.MediaStore;
+import android.speech.RecognizerResultsIntent;
+import android.text.TextUtils;
+import android.util.Patterns;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Handle all browser related intents
+ */
+public class IntentHandler {
+
+ // "source" parameter for Google search suggested by the browser
+ final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
+ // "source" parameter for Google search from unknown source
+ final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
+
+ /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
+
+ private Activity mActivity;
+ private Controller mController;
+ private TabControl mTabControl;
+ private BrowserSettings mSettings;
+
+ public IntentHandler(Activity browser, Controller controller) {
+ mActivity = browser;
+ mController = controller;
+ mTabControl = mController.getTabControl();
+ mSettings = controller.getSettings();
+ }
+
+ void onNewIntent(Intent intent) {
+ Tab current = mTabControl.getCurrentTab();
+ // When a tab is closed on exit, the current tab index is set to -1.
+ // Reset before proceed as Browser requires the current tab to be set.
+ if (current == null) {
+ // Try to reset the tab in case the index was incorrect.
+ current = mTabControl.getTab(0);
+ if (current == null) {
+ // No tabs at all so just ignore this intent.
+ return;
+ }
+ mController.setActiveTab(current);
+ mController.resetTitleAndIcon(current);
+ }
+ final String action = intent.getAction();
+ final int flags = intent.getFlags();
+ if (Intent.ACTION_MAIN.equals(action) ||
+ (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
+ // just resume the browser
+ return;
+ }
+ // In case the SearchDialog is open.
+ ((SearchManager) mActivity.getSystemService(Context.SEARCH_SERVICE))
+ .stopSearch();
+ boolean activateVoiceSearch = RecognizerResultsIntent
+ .ACTION_VOICE_SEARCH_RESULTS.equals(action);
+ if (Intent.ACTION_VIEW.equals(action)
+ || Intent.ACTION_SEARCH.equals(action)
+ || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
+ || Intent.ACTION_WEB_SEARCH.equals(action)
+ || activateVoiceSearch) {
+ if (current.isInVoiceSearchMode()) {
+ String title = current.getVoiceDisplayTitle();
+ if (title != null && title.equals(intent.getStringExtra(
+ SearchManager.QUERY))) {
+ // The user submitted the same search as the last voice
+ // search, so do nothing.
+ return;
+ }
+ if (Intent.ACTION_SEARCH.equals(action)
+ && current.voiceSearchSourceIsGoogle()) {
+ Intent logIntent = new Intent(
+ LoggingEvents.ACTION_LOG_EVENT);
+ logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
+ LoggingEvents.VoiceSearch.QUERY_UPDATED);
+ logIntent.putExtra(
+ LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE,
+ intent.getDataString());
+ mActivity.sendBroadcast(logIntent);
+ // Note, onPageStarted will revert the voice title bar
+ // When http://b/issue?id=2379215 is fixed, we should update
+ // the title bar here.
+ }
+ }
+ // If this was a search request (e.g. search query directly typed into the address bar),
+ // pass it on to the default web search provider.
+ if (handleWebSearchIntent(mActivity, mController, intent)) {
+ return;
+ }
+
+ UrlData urlData = getUrlDataFromIntent(intent);
+ if (urlData.isEmpty()) {
+ urlData = new UrlData(mSettings.getHomePage());
+ }
+
+ final String appId = intent
+ .getStringExtra(Browser.EXTRA_APPLICATION_ID);
+ if ((Intent.ACTION_VIEW.equals(action)
+ // If a voice search has no appId, it means that it came
+ // from the browser. In that case, reuse the current tab.
+ || (activateVoiceSearch && appId != null))
+ && !mActivity.getPackageName().equals(appId)
+ && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
+ Tab appTab = mTabControl.getTabFromId(appId);
+ if (appTab != null) {
+ mController.reuseTab(appTab, appId, urlData);
+ return;
+ } else {
+ // No matching application tab, try to find a regular tab
+ // with a matching url.
+ appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
+ if (appTab != null) {
+ if (current != appTab) {
+ mController.switchToTab(mTabControl.getTabIndex(appTab));
+ }
+ // Otherwise, we are already viewing the correct tab.
+ } else {
+ // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
+ // will be opened in a new tab unless we have reached
+ // MAX_TABS. Then the url will be opened in the current
+ // tab. If a new tab is created, it will have "true" for
+ // exit on close.
+ mController.openTabAndShow(urlData, true, appId);
+ }
+ }
+ } else {
+ if (!urlData.isEmpty()
+ && urlData.mUrl.startsWith("about:debug")) {
+ if ("about:debug.dom".equals(urlData.mUrl)) {
+ current.getWebView().dumpDomTree(false);
+ } else if ("about:debug.dom.file".equals(urlData.mUrl)) {
+ current.getWebView().dumpDomTree(true);
+ } else if ("about:debug.render".equals(urlData.mUrl)) {
+ current.getWebView().dumpRenderTree(false);
+ } else if ("about:debug.render.file".equals(urlData.mUrl)) {
+ current.getWebView().dumpRenderTree(true);
+ } else if ("about:debug.display".equals(urlData.mUrl)) {
+ current.getWebView().dumpDisplayTree();
+ } else {
+ mSettings.toggleDebugSettings();
+ }
+ return;
+ }
+ // Get rid of the subwindow if it exists
+ mController.dismissSubWindow(current);
+ // If the current Tab is being used as an application tab,
+ // remove the association, since the new Intent means that it is
+ // no longer associated with that application.
+ current.setAppId(null);
+ mController.loadUrlDataIn(current, urlData);
+ }
+ }
+ }
+
+ protected UrlData getUrlDataFromIntent(Intent intent) {
+ String url = "";
+ Map<String, String> headers = null;
+ if (intent != null) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_VIEW.equals(action)) {
+ url = UrlUtils.smartUrlFilter(intent.getData());
+ if (url != null && url.startsWith("content:")) {
+ /* Append mimetype so webview knows how to display */
+ String mimeType = intent.resolveType(mActivity.getContentResolver());
+ if (mimeType != null) {
+ url += "?" + mimeType;
+ }
+ }
+ if (url != null && url.startsWith("http")) {
+ final Bundle pairs = intent
+ .getBundleExtra(Browser.EXTRA_HEADERS);
+ if (pairs != null && !pairs.isEmpty()) {
+ Iterator<String> iter = pairs.keySet().iterator();
+ headers = new HashMap<String, String>();
+ while (iter.hasNext()) {
+ String key = iter.next();
+ headers.put(key, pairs.getString(key));
+ }
+ }
+ }
+ } else if (Intent.ACTION_SEARCH.equals(action)
+ || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
+ || Intent.ACTION_WEB_SEARCH.equals(action)) {
+ url = intent.getStringExtra(SearchManager.QUERY);
+ if (url != null) {
+ // In general, we shouldn't modify URL from Intent.
+ // But currently, we get the user-typed URL from search box as well.
+ url = UrlUtils.fixUrl(url);
+ url = UrlUtils.smartUrlFilter(url);
+ final ContentResolver cr = mActivity.getContentResolver();
+ final String newUrl = url;
+ if (mTabControl == null
+ || mTabControl.getCurrentWebView() == null
+ || !mTabControl.getCurrentWebView().isPrivateBrowsingEnabled()) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ Browser.updateVisitedHistory(cr, newUrl, false);
+ return null;
+ }
+ }.execute();
+ }
+ String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
+ if (url.contains(searchSource)) {
+ String source = null;
+ final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
+ if (appData != null) {
+ source = appData.getString(Search.SOURCE);
+ }
+ if (TextUtils.isEmpty(source)) {
+ source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
+ }
+ url = url.replace(searchSource, "&source=android-"+source+"&");
+ }
+ }
+ }
+ }
+ return new UrlData(url, headers, intent);
+ }
+
+ /**
+ * 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.
+ * @return true if the intent was handled and web search activity was launched, false if not.
+ */
+ static boolean handleWebSearchIntent(Activity activity,
+ Controller controller, Intent intent) {
+ if (intent == null) return false;
+
+ String url = null;
+ final String action = intent.getAction();
+ if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS.equals(
+ action)) {
+ return false;
+ }
+ if (Intent.ACTION_VIEW.equals(action)) {
+ Uri data = intent.getData();
+ if (data != null) url = data.toString();
+ } else if (Intent.ACTION_SEARCH.equals(action)
+ || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
+ || Intent.ACTION_WEB_SEARCH.equals(action)) {
+ url = intent.getStringExtra(SearchManager.QUERY);
+ }
+ return handleWebSearchRequest(activity, controller, url,
+ intent.getBundleExtra(SearchManager.APP_DATA),
+ intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
+ }
+
+ /**
+ * Launches the default web search activity with the query parameters if the given url string
+ * was identified as plain search terms and not URL/shortcut.
+ * @return true if the request was handled and web search activity was launched, false if not.
+ */
+ private static boolean handleWebSearchRequest(Activity activity,
+ Controller controller, String inUrl, Bundle appData,
+ String extraData) {
+ if (inUrl == null) return false;
+
+ // In general, we shouldn't modify URL from Intent.
+ // But currently, we get the user-typed URL from search box as well.
+ String url = UrlUtils.fixUrl(inUrl).trim();
+
+ // URLs are handled by the regular flow of control, so
+ // return early.
+ if (Patterns.WEB_URL.matcher(url).matches()
+ || UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url).matches()) {
+ return false;
+ }
+
+ final ContentResolver cr = activity.getContentResolver();
+ final String newUrl = url;
+ if (controller == null || controller.getTabControl() == null
+ || controller.getTabControl().getCurrentWebView() == null
+ || !controller.getTabControl().getCurrentWebView()
+ .isPrivateBrowsingEnabled()) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ Browser.updateVisitedHistory(cr, newUrl, false);
+ Browser.addSearchUrl(cr, newUrl);
+ return null;
+ }
+ }.execute();
+ }
+
+ SearchEngine searchEngine = BrowserSettings.getInstance().getSearchEngine();
+ if (searchEngine == null) return false;
+ searchEngine.startSearch(activity, url, appData, extraData);
+
+ return true;
+ }
+
+ /**
+ * A UrlData class to abstract how the content will be set to WebView.
+ * This base class uses loadUrl to show the content.
+ */
+ static class UrlData {
+ final String mUrl;
+ final Map<String, String> mHeaders;
+ final Intent mVoiceIntent;
+
+ UrlData(String url) {
+ this.mUrl = url;
+ this.mHeaders = null;
+ this.mVoiceIntent = null;
+ }
+
+ UrlData(String url, Map<String, String> headers, Intent intent) {
+ this.mUrl = url;
+ this.mHeaders = headers;
+ if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
+ .equals(intent.getAction())) {
+ this.mVoiceIntent = intent;
+ } else {
+ this.mVoiceIntent = null;
+ }
+ }
+
+ boolean isEmpty() {
+ return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0);
+ }
+
+ /**
+ * Load this UrlData into the given Tab. Use loadUrlDataIn to update
+ * the title bar as well.
+ */
+ public void loadIn(Tab t) {
+ if (mVoiceIntent != null) {
+ t.activateVoiceSearchMode(mVoiceIntent);
+ } else {
+ t.getWebView().loadUrl(mUrl, mHeaders);
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/browser/MeshTracker.java b/src/com/android/browser/MeshTracker.java
deleted file mode 100644
index c4b63329f..000000000
--- a/src/com/android/browser/MeshTracker.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2009 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.graphics.Bitmap;
-import android.graphics.utils.BoundaryPatch;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.webkit.WebView;
-
-/*package*/ class MeshTracker extends WebView.DragTracker {
-
- private static class Mesh {
- private int mWhich;
- private int mRows;
- private int mCols;
- private BoundaryPatch mPatch = new BoundaryPatch();
- private float[] mCubics = new float[24];
- private float[] mOrig = new float[24];
- private float mStretchX, mStretchY;
-
- Mesh(int which, int rows, int cols) {
- mWhich = which;
- mRows = rows;
- mCols = cols;
- }
-
- private void rebuildPatch() {
- mPatch.setCubicBoundary(mCubics, 0, mRows, mCols);
- }
-
- private void setSize(float w, float h) {
- float[] pts = mCubics;
- float x1 = w*0.3333f;
- float y1 = h*0.3333f;
- float x2 = w*0.6667f;
- float y2 = h*0.6667f;
- pts[0*2+0] = 0; pts[0*2+1] = 0;
- pts[1*2+0] = x1; pts[1*2+1] = 0;
- pts[2*2+0] = x2; pts[2*2+1] = 0;
-
- pts[3*2+0] = w; pts[3*2+1] = 0;
- pts[4*2+0] = w; pts[4*2+1] = y1;
- pts[5*2+0] = w; pts[5*2+1] = y2;
-
- pts[6*2+0] = w; pts[6*2+1] = h;
- pts[7*2+0] = x2; pts[7*2+1] = h;
- pts[8*2+0] = x1; pts[8*2+1] = h;
-
- pts[9*2+0] = 0; pts[9*2+1] = h;
- pts[10*2+0] = 0; pts[10*2+1] = y2;
- pts[11*2+0] = 0; pts[11*2+1] = y1;
-
- System.arraycopy(pts, 0, mOrig, 0, 24);
-
- // recall our stretcher
- setStretch(mStretchX, mStretchY);
- }
-
- public void setBitmap(Bitmap bm) {
- mPatch.setTexture(bm);
- setSize(bm.getWidth(), bm.getHeight());
- }
-
- // first experimental behavior
- private void doit1(float dx, float dy) {
- final float scale = 0.75f; // temper how far we actually move
- dx *= scale;
- dy *= scale;
-
- int index;
- if (dx < 0) {
- index = 10;
- } else {
- index = 4;
- }
- mCubics[index*2 + 0] = mOrig[index*2 + 0] + dx;
- mCubics[index*2 + 2] = mOrig[index*2 + 2] + dx;
-
- if (dy < 0) {
- index = 1;
- } else {
- index = 7;
- }
- mCubics[index*2 + 1] = mOrig[index*2 + 1] + dy;
- mCubics[index*2 + 3] = mOrig[index*2 + 3] + dy;
- }
-
- private void doit2(float dx, float dy) {
- final float scale = 0.35f; // temper how far we actually move
- dx *= scale;
- dy *= scale;
- final float cornerScale = 0.25f;
-
- int index;
- if (dx < 0) {
- index = 4;
- } else {
- index = 10;
- }
- mCubics[index*2 + 0] = mOrig[index*2 + 0] + dx;
- mCubics[index*2 + 2] = mOrig[index*2 + 2] + dx;
- // corners
- index -= 1;
- mCubics[index*2 + 0] = mOrig[index*2 + 0] + dx * cornerScale;
- index = (index + 3) % 12; // next corner
- mCubics[index*2 + 0] = mOrig[index*2 + 0] + dx * cornerScale;
-
- if (dy < 0) {
- index = 7;
- } else {
- index = 1;
- }
- mCubics[index*2 + 1] = mOrig[index*2 + 1] + dy;
- mCubics[index*2 + 3] = mOrig[index*2 + 3] + dy;
- // corners
- index -= 1;
- mCubics[index*2 + 1] = mOrig[index*2 + 1] + dy * cornerScale;
- index = (index + 3) % 12; // next corner
- mCubics[index*2 + 1] = mOrig[index*2 + 1] + dy * cornerScale;
- }
-
- public void setStretch(float dx, float dy) {
- mStretchX = dx;
- mStretchY = dy;
- switch (mWhich) {
- case 1:
- doit1(dx, dy);
- break;
- case 2:
- doit2(dx, dy);
- break;
- }
- rebuildPatch();
- }
-
- public void draw(Canvas canvas) {
- mPatch.draw(canvas);
- }
- }
-
- private Mesh mMesh;
- private Bitmap mBitmap;
- private int mWhich;
- private Paint mBGPaint;
-
- public MeshTracker(int which) {
- mWhich = which;
- }
-
- public void setBGPaint(Paint paint) {
- mBGPaint = paint;
- }
-
- @Override public void onStartDrag(float x, float y) {
- mMesh = new Mesh(mWhich, 16, 16);
- }
-
- @Override public void onBitmapChange(Bitmap bm) {
- mBitmap = bm;
- mMesh.setBitmap(bm);
- }
-
- @Override public boolean onStretchChange(float sx, float sy) {
- mMesh.setStretch(-sx, -sy);
- return true;
- }
-
- @Override public void onStopDrag() {
- mMesh = null;
- }
-
- @Override public void onDraw(Canvas canvas) {
- if (mWhich == 2) {
- if (mBGPaint != null) {
- canvas.drawPaint(mBGPaint);
- } else {
- canvas.drawColor(0xFF000000);
- }
- }
- mMesh.draw(canvas);
- }
-}
-
diff --git a/src/com/android/browser/NetworkStateHandler.java b/src/com/android/browser/NetworkStateHandler.java
new file mode 100644
index 000000000..3b2007ee3
--- /dev/null
+++ b/src/com/android/browser/NetworkStateHandler.java
@@ -0,0 +1,140 @@
+/*
+ * 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.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.webkit.WebView;
+
+/**
+ * Handle network state changes
+ */
+public class NetworkStateHandler {
+
+ Activity mActivity;
+ Controller mController;
+
+ // monitor platform changes
+ private IntentFilter mNetworkStateChangedFilter;
+ private BroadcastReceiver mNetworkStateIntentReceiver;
+ private boolean mIsNetworkUp;
+
+ /* hold a ref so we can auto-cancel if necessary */
+ private AlertDialog mAlertDialog;
+
+ public NetworkStateHandler(Activity activity, Controller controller) {
+ mActivity = activity;
+ mController = controller;
+ // Find out if the network is currently up.
+ ConnectivityManager cm = (ConnectivityManager) mActivity
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo info = cm.getActiveNetworkInfo();
+ if (info != null) {
+ mIsNetworkUp = info.isAvailable();
+ }
+
+ /*
+ * enables registration for changes in network status from http stack
+ */
+ mNetworkStateChangedFilter = new IntentFilter();
+ mNetworkStateChangedFilter.addAction(
+ ConnectivityManager.CONNECTIVITY_ACTION);
+ mNetworkStateIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(
+ ConnectivityManager.CONNECTIVITY_ACTION)) {
+
+ NetworkInfo info = intent.getParcelableExtra(
+ ConnectivityManager.EXTRA_NETWORK_INFO);
+ String typeName = info.getTypeName();
+ String subtypeName = info.getSubtypeName();
+ sendNetworkType(typeName.toLowerCase(),
+ (subtypeName != null ? subtypeName.toLowerCase() : ""));
+
+ onNetworkToggle(info.isAvailable());
+ }
+ }
+ };
+
+ }
+
+ void onPause() {
+ // unregister network state listener
+ mActivity.unregisterReceiver(mNetworkStateIntentReceiver);
+ }
+
+ void onResume() {
+ mActivity.registerReceiver(mNetworkStateIntentReceiver,
+ mNetworkStateChangedFilter);
+ }
+
+ /**
+ * connectivity manager says net has come or gone... inform the user
+ * @param up true if net has come up, false if net has gone down
+ */
+ void onNetworkToggle(boolean up) {
+ if (up == mIsNetworkUp) {
+ return;
+ } else if (up) {
+ mIsNetworkUp = true;
+ if (mAlertDialog != null) {
+ mAlertDialog.cancel();
+ mAlertDialog = null;
+ }
+ } else {
+ mIsNetworkUp = false;
+ if (mController.isInLoad()) {
+ createAndShowNetworkDialog();
+ }
+ }
+ WebView w = mController.getCurrentWebView();
+ if (w != null) {
+ w.setNetworkAvailable(up);
+ }
+ }
+
+ boolean isNetworkUp() {
+ return mIsNetworkUp;
+ }
+
+ // This method shows the network dialog alerting the user that the net is
+ // down. It will only show the dialog if mAlertDialog is null.
+ void createAndShowNetworkDialog() {
+ if (mAlertDialog == null) {
+ mAlertDialog = new AlertDialog.Builder(mActivity)
+ .setTitle(R.string.loadSuspendedTitle)
+ .setMessage(R.string.loadSuspended)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ }
+ }
+
+ private void sendNetworkType(String type, String subtype) {
+ WebView w = mController.getCurrentWebView();
+ if (w != null) {
+ w.setNetworkType(type, subtype);
+ }
+ }
+
+}
diff --git a/src/com/android/browser/OpenDownloadReceiver.java b/src/com/android/browser/OpenDownloadReceiver.java
index 99e5f4106..4277ff493 100644
--- a/src/com/android/browser/OpenDownloadReceiver.java
+++ b/src/com/android/browser/OpenDownloadReceiver.java
@@ -19,15 +19,11 @@ 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.net.Uri;
-import android.provider.Downloads;
-import android.widget.Toast;
-
-import java.io.File;
+import android.os.Handler;
+import android.os.HandlerThread;
/**
* This {@link BroadcastReceiver} handles clicks to notifications that
@@ -36,49 +32,63 @@ import java.io.File;
* a complete, successful download will open the file.
*/
public class OpenDownloadReceiver extends BroadcastReceiver {
- public void onReceive(Context context, Intent intent) {
- ContentResolver cr = context.getContentResolver();
- Uri data = intent.getData();
- Cursor cursor = null;
- try {
- cursor = cr.query(data,
- new String[] { Downloads.Impl._ID, Downloads.Impl._DATA,
- Downloads.Impl.COLUMN_MIME_TYPE, Downloads.COLUMN_STATUS },
- null, null, null);
- if (cursor.moveToFirst()) {
- String filename = cursor.getString(1);
- String mimetype = cursor.getString(2);
- String action = intent.getAction();
- if (Downloads.ACTION_NOTIFICATION_CLICKED.equals(action)) {
- int status = cursor.getInt(3);
- if (Downloads.isStatusCompleted(status)
- && Downloads.isStatusSuccess(status)) {
- Intent launchIntent = new Intent(Intent.ACTION_VIEW);
- Uri path = Uri.parse(filename);
- // If there is no scheme, then it must be a file
- if (path.getScheme() == null) {
- path = Uri.fromFile(new File(filename));
- }
- launchIntent.setDataAndType(path, mimetype);
- launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- context.startActivity(launchIntent);
- } catch (ActivityNotFoundException ex) {
- Toast.makeText(context,
- R.string.download_no_application_title,
- Toast.LENGTH_LONG).show();
- }
- } else {
- // Open the downloads page
- Intent pageView = new Intent(
- DownloadManager.ACTION_VIEW_DOWNLOADS);
- pageView.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(pageView);
- }
- }
+ private static Handler sAsyncHandler;
+ static {
+ HandlerThread thr = new HandlerThread("Open browser download async");
+ thr.start();
+ sAsyncHandler = new Handler(thr.getLooper());
+ }
+ @Override
+ public void onReceive(final Context context, Intent intent) {
+ String action = intent.getAction();
+ if (!DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(action)) {
+ openDownloadsPage(context);
+ return;
+ }
+ long ids[] = intent.getLongArrayExtra(
+ DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
+ if (ids == null || ids.length == 0) {
+ openDownloadsPage(context);
+ return;
+ }
+ final long id = ids[0];
+ final PendingResult result = goAsync();
+ Runnable worker = new Runnable() {
+ @Override
+ public void run() {
+ onReceiveAsync(context, id);
+ result.finish();
+ }
+ };
+ sAsyncHandler.post(worker);
+ }
+
+ private void onReceiveAsync(Context context, long id) {
+ DownloadManager manager = (DownloadManager) context.getSystemService(
+ Context.DOWNLOAD_SERVICE);
+ Uri uri = manager.getUriForDownloadedFile(id);
+ if (uri == null) {
+ // Open the downloads page
+ openDownloadsPage(context);
+ } else {
+ Intent launchIntent = new Intent(Intent.ACTION_VIEW);
+ launchIntent.setDataAndType(uri, manager.getMimeTypeForDownloadedFile(id));
+ launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ context.startActivity(launchIntent);
+ } catch (ActivityNotFoundException e) {
+ openDownloadsPage(context);
}
- } finally {
- if (cursor != null) cursor.close();
}
}
+
+ /**
+ * Open the Activity which shows a list of all downloads.
+ * @param context
+ */
+ private void openDownloadsPage(Context context) {
+ Intent pageView = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+ pageView.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(pageView);
+ }
}
diff --git a/src/com/android/browser/PageDialogsHandler.java b/src/com/android/browser/PageDialogsHandler.java
new file mode 100644
index 000000000..6843a10fc
--- /dev/null
+++ b/src/com/android/browser/PageDialogsHandler.java
@@ -0,0 +1,465 @@
+/*
+ * 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.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.net.http.SslCertificate;
+import android.net.http.SslError;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.Date;
+
+/**
+ * Displays page info
+ *
+ */
+public class PageDialogsHandler {
+
+ private Context mContext;
+ private Controller mController;
+ private boolean mPageInfoFromShowSSLCertificateOnError;
+ private Tab mPageInfoView;
+ private AlertDialog mPageInfoDialog;
+
+ // as SSLCertificateOnError has different style for landscape / portrait,
+ // we have to re-open it when configuration changed
+ private AlertDialog mSSLCertificateOnErrorDialog;
+ private WebView mSSLCertificateOnErrorView;
+ private SslErrorHandler mSSLCertificateOnErrorHandler;
+ private SslError mSSLCertificateOnErrorError;
+
+ // as SSLCertificate has different style for landscape / portrait, we
+ // have to re-open it when configuration changed
+ private AlertDialog mSSLCertificateDialog;
+ private Tab mSSLCertificateView;
+ private HttpAuthenticationDialog mHttpAuthenticationDialog;
+
+ public PageDialogsHandler(Context context, Controller controller) {
+ mContext = context;
+ mController = controller;
+ }
+
+ public void onConfigurationChanged(Configuration config) {
+ if (mPageInfoDialog != null) {
+ mPageInfoDialog.dismiss();
+ showPageInfo(mPageInfoView, mPageInfoFromShowSSLCertificateOnError);
+ }
+ if (mSSLCertificateDialog != null) {
+ mSSLCertificateDialog.dismiss();
+ showSSLCertificate(mSSLCertificateView);
+ }
+ if (mSSLCertificateOnErrorDialog != null) {
+ mSSLCertificateOnErrorDialog.dismiss();
+ showSSLCertificateOnError(mSSLCertificateOnErrorView, mSSLCertificateOnErrorHandler,
+ mSSLCertificateOnErrorError);
+ }
+ if (mHttpAuthenticationDialog != null) {
+ mHttpAuthenticationDialog.reshow();
+ }
+ }
+
+ /**
+ * Displays an http-authentication dialog.
+ */
+ void showHttpAuthentication(final Tab tab, final HttpAuthHandler handler, String host, String realm) {
+ mHttpAuthenticationDialog = new HttpAuthenticationDialog(mContext, host, realm);
+ mHttpAuthenticationDialog.setOkListener(new HttpAuthenticationDialog.OkListener() {
+ public void onOk(String host, String realm, String username, String password) {
+ setHttpAuthUsernamePassword(host, realm, username, password);
+ handler.proceed(username, password);
+ mHttpAuthenticationDialog = null;
+ }
+ });
+ mHttpAuthenticationDialog.setCancelListener(new HttpAuthenticationDialog.CancelListener() {
+ public void onCancel() {
+ handler.cancel();
+ mController.resetTitleAndRevertLockIcon(tab);
+ mHttpAuthenticationDialog = null;
+ }
+ });
+ mHttpAuthenticationDialog.show();
+ }
+
+ /**
+ * Set HTTP authentication password.
+ *
+ * @param host The host for the password
+ * @param realm The realm for the password
+ * @param username The username for the password. If it is null, it means
+ * password can't be saved.
+ * @param password The password
+ */
+ public void setHttpAuthUsernamePassword(String host, String realm,
+ String username,
+ String password) {
+ WebView w = mController.getCurrentTopWebView();
+ if (w != null) {
+ w.setHttpAuthUsernamePassword(host, realm, username, password);
+ }
+ }
+
+ /**
+ * Displays a page-info dialog.
+ * @param tab The tab to show info about
+ * @param fromShowSSLCertificateOnError The flag that indicates whether
+ * this dialog was opened from the SSL-certificate-on-error dialog or
+ * not. This is important, since we need to know whether to return to
+ * the parent dialog or simply dismiss.
+ */
+ void showPageInfo(final Tab tab,
+ final boolean fromShowSSLCertificateOnError) {
+ final LayoutInflater factory = LayoutInflater.from(mContext);
+
+ final View pageInfoView = factory.inflate(R.layout.page_info, null);
+
+ final WebView view = tab.getWebView();
+
+ String url = null;
+ String title = null;
+
+ if (view == null) {
+ url = tab.getUrl();
+ title = tab.getTitle();
+ } else if (view == mController.getCurrentWebView()) {
+ // Use the cached title and url if this is the current WebView
+ url = tab.getCurrentUrl();
+ title = tab.getCurrentTitle();
+ } else {
+ url = view.getUrl();
+ title = view.getTitle();
+ }
+
+ if (url == null) {
+ url = "";
+ }
+ if (title == null) {
+ title = "";
+ }
+
+ ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
+ ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
+
+ mPageInfoView = tab;
+ mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError;
+
+ AlertDialog.Builder alertDialogBuilder =
+ new AlertDialog.Builder(mContext)
+ .setTitle(R.string.page_info)
+ .setIcon(android.R.drawable.ic_dialog_info)
+ .setView(pageInfoView)
+ .setPositiveButton(
+ R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ mPageInfoDialog = null;
+ mPageInfoView = null;
+
+ // if we came here from the SSL error dialog
+ if (fromShowSSLCertificateOnError) {
+ // go back to the SSL error dialog
+ showSSLCertificateOnError(
+ mSSLCertificateOnErrorView,
+ mSSLCertificateOnErrorHandler,
+ mSSLCertificateOnErrorError);
+ }
+ }
+ })
+ .setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ mPageInfoDialog = null;
+ mPageInfoView = null;
+
+ // if we came here from the SSL error dialog
+ if (fromShowSSLCertificateOnError) {
+ // go back to the SSL error dialog
+ showSSLCertificateOnError(
+ mSSLCertificateOnErrorView,
+ mSSLCertificateOnErrorHandler,
+ mSSLCertificateOnErrorError);
+ }
+ }
+ });
+
+ // if we have a main top-level page SSL certificate set or a certificate
+ // error
+ if (fromShowSSLCertificateOnError ||
+ (view != null && view.getCertificate() != null)) {
+ // add a 'View Certificate' button
+ alertDialogBuilder.setNeutralButton(
+ R.string.view_certificate,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ mPageInfoDialog = null;
+ mPageInfoView = null;
+
+ // if we came here from the SSL error dialog
+ if (fromShowSSLCertificateOnError) {
+ // go back to the SSL error dialog
+ showSSLCertificateOnError(
+ mSSLCertificateOnErrorView,
+ mSSLCertificateOnErrorHandler,
+ mSSLCertificateOnErrorError);
+ } else {
+ // otherwise, display the top-most certificate from
+ // the chain
+ if (view.getCertificate() != null) {
+ showSSLCertificate(tab);
+ }
+ }
+ }
+ });
+ }
+
+ mPageInfoDialog = alertDialogBuilder.show();
+ }
+
+ /**
+ * Displays the main top-level page SSL certificate dialog
+ * (accessible from the Page-Info dialog).
+ * @param tab The tab to show certificate for.
+ */
+ private void showSSLCertificate(final Tab tab) {
+ final View certificateView =
+ inflateCertificateView(tab.getWebView().getCertificate());
+ if (certificateView == null) {
+ return;
+ }
+
+ LayoutInflater factory = LayoutInflater.from(mContext);
+
+ final LinearLayout placeholder =
+ (LinearLayout)certificateView.findViewById(R.id.placeholder);
+
+ LinearLayout ll = (LinearLayout) factory.inflate(
+ R.layout.ssl_success, placeholder);
+ ((TextView)ll.findViewById(R.id.success))
+ .setText(R.string.ssl_certificate_is_valid);
+
+ mSSLCertificateView = tab;
+ mSSLCertificateDialog =
+ new AlertDialog.Builder(mContext)
+ .setTitle(R.string.ssl_certificate).setIcon(
+ R.drawable.ic_dialog_browser_certificate_secure)
+ .setView(certificateView)
+ .setPositiveButton(R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ mSSLCertificateDialog = null;
+ mSSLCertificateView = null;
+
+ showPageInfo(tab, false);
+ }
+ })
+ .setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ mSSLCertificateDialog = null;
+ mSSLCertificateView = null;
+
+ showPageInfo(tab, false);
+ }
+ })
+ .show();
+ }
+
+ /**
+ * Displays the SSL error certificate dialog.
+ * @param view The target web-view.
+ * @param handler The SSL error handler responsible for cancelling the
+ * connection that resulted in an SSL error or proceeding per user request.
+ * @param error The SSL error object.
+ */
+ void showSSLCertificateOnError(
+ final WebView view, final SslErrorHandler handler,
+ final SslError error) {
+
+ final View certificateView =
+ inflateCertificateView(error.getCertificate());
+ if (certificateView == null) {
+ return;
+ }
+
+ LayoutInflater factory = LayoutInflater.from(mContext);
+
+ final LinearLayout placeholder =
+ (LinearLayout)certificateView.findViewById(R.id.placeholder);
+
+ if (error.hasError(SslError.SSL_UNTRUSTED)) {
+ LinearLayout ll = (LinearLayout)factory
+ .inflate(R.layout.ssl_warning, placeholder);
+ ((TextView)ll.findViewById(R.id.warning))
+ .setText(R.string.ssl_untrusted);
+ }
+
+ if (error.hasError(SslError.SSL_IDMISMATCH)) {
+ LinearLayout ll = (LinearLayout)factory
+ .inflate(R.layout.ssl_warning, placeholder);
+ ((TextView)ll.findViewById(R.id.warning))
+ .setText(R.string.ssl_mismatch);
+ }
+
+ if (error.hasError(SslError.SSL_EXPIRED)) {
+ LinearLayout ll = (LinearLayout)factory
+ .inflate(R.layout.ssl_warning, placeholder);
+ ((TextView)ll.findViewById(R.id.warning))
+ .setText(R.string.ssl_expired);
+ }
+
+ if (error.hasError(SslError.SSL_NOTYETVALID)) {
+ LinearLayout ll = (LinearLayout)factory
+ .inflate(R.layout.ssl_warning, placeholder);
+ ((TextView)ll.findViewById(R.id.warning))
+ .setText(R.string.ssl_not_yet_valid);
+ }
+
+ mSSLCertificateOnErrorHandler = handler;
+ mSSLCertificateOnErrorView = view;
+ mSSLCertificateOnErrorError = error;
+ mSSLCertificateOnErrorDialog =
+ new AlertDialog.Builder(mContext)
+ .setTitle(R.string.ssl_certificate).setIcon(
+ R.drawable.ic_dialog_browser_certificate_partially_secure)
+ .setView(certificateView)
+ .setPositiveButton(R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ mSSLCertificateOnErrorDialog = null;
+ mSSLCertificateOnErrorView = null;
+ mSSLCertificateOnErrorHandler = null;
+ mSSLCertificateOnErrorError = null;
+
+ view.getWebViewClient().onReceivedSslError(
+ view, handler, error);
+ }
+ })
+ .setNeutralButton(R.string.page_info_view,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ mSSLCertificateOnErrorDialog = null;
+
+ // do not clear the dialog state: we will
+ // need to show the dialog again once the
+ // user is done exploring the page-info details
+
+ showPageInfo(mController.getTabControl()
+ .getTabFromView(view),
+ true);
+ }
+ })
+ .setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ mSSLCertificateOnErrorDialog = null;
+ mSSLCertificateOnErrorView = null;
+ mSSLCertificateOnErrorHandler = null;
+ mSSLCertificateOnErrorError = null;
+
+ view.getWebViewClient().onReceivedSslError(
+ view, handler, error);
+ }
+ })
+ .show();
+ }
+
+ /**
+ * Inflates the SSL certificate view (helper method).
+ * @param certificate The SSL certificate.
+ * @return The resultant certificate view with issued-to, issued-by,
+ * issued-on, expires-on, and possibly other fields set.
+ * If the input certificate is null, returns null.
+ */
+ private View inflateCertificateView(SslCertificate certificate) {
+ if (certificate == null) {
+ return null;
+ }
+
+ LayoutInflater factory = LayoutInflater.from(mContext);
+
+ View certificateView = factory.inflate(
+ R.layout.ssl_certificate, null);
+
+ // issued to:
+ SslCertificate.DName issuedTo = certificate.getIssuedTo();
+ if (issuedTo != null) {
+ ((TextView) certificateView.findViewById(R.id.to_common))
+ .setText(issuedTo.getCName());
+ ((TextView) certificateView.findViewById(R.id.to_org))
+ .setText(issuedTo.getOName());
+ ((TextView) certificateView.findViewById(R.id.to_org_unit))
+ .setText(issuedTo.getUName());
+ }
+
+ // issued by:
+ SslCertificate.DName issuedBy = certificate.getIssuedBy();
+ if (issuedBy != null) {
+ ((TextView) certificateView.findViewById(R.id.by_common))
+ .setText(issuedBy.getCName());
+ ((TextView) certificateView.findViewById(R.id.by_org))
+ .setText(issuedBy.getOName());
+ ((TextView) certificateView.findViewById(R.id.by_org_unit))
+ .setText(issuedBy.getUName());
+ }
+
+ // issued on:
+ String issuedOn = formatCertificateDate(
+ certificate.getValidNotBeforeDate());
+ ((TextView) certificateView.findViewById(R.id.issued_on))
+ .setText(issuedOn);
+
+ // expires on:
+ String expiresOn = formatCertificateDate(
+ certificate.getValidNotAfterDate());
+ ((TextView) certificateView.findViewById(R.id.expires_on))
+ .setText(expiresOn);
+
+ return certificateView;
+ }
+
+ /**
+ * Formats the certificate date to a properly localized date string.
+ * @return Properly localized version of the certificate date string and
+ * the "" if it fails to localize.
+ */
+ private String formatCertificateDate(Date certificateDate) {
+ if (certificateDate == null) {
+ return "";
+ }
+ String formattedDate = DateFormat.getDateFormat(mContext)
+ .format(certificateDate);
+ if (formattedDate == null) {
+ return "";
+ }
+ return formattedDate;
+ }
+
+}
diff --git a/src/com/android/browser/PageProgressView.java b/src/com/android/browser/PageProgressView.java
new file mode 100644
index 000000000..f512cefa1
--- /dev/null
+++ b/src/com/android/browser/PageProgressView.java
@@ -0,0 +1,117 @@
+
+/*
+ * 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.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ *
+ */
+public class PageProgressView extends ImageView {
+
+ public static final int MAX_PROGRESS = 10000;
+ private static final int MSG_UPDATE = 42;
+ private static final int STEPS = 10;
+ private static final int DELAY = 40;
+
+ private int mCurrentProgress;
+ private int mTargetProgress;
+ private int mIncrement;
+ private Rect mBounds;
+ private Handler mHandler;
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public PageProgressView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public PageProgressView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /**
+ * @param context
+ */
+ public PageProgressView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ mBounds = new Rect(0,0,0,0);
+ mCurrentProgress = 0;
+ mTargetProgress = 0;
+ mHandler = new Handler() {
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_UPDATE) {
+ mCurrentProgress = Math.min(mTargetProgress,
+ mCurrentProgress + mIncrement);
+ mBounds.right = getWidth() * mCurrentProgress / MAX_PROGRESS;
+ invalidate();
+ if (mCurrentProgress < mTargetProgress) {
+ sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE), DELAY);
+ }
+ }
+ }
+
+ };
+ }
+
+ @Override
+ public void onLayout(boolean f, int l, int t, int r, int b) {
+ mBounds.left = 0;
+ mBounds.right = (r - l) * mCurrentProgress / MAX_PROGRESS;
+ mBounds.top = 0;
+ mBounds.bottom = b-t;
+ }
+
+ void setProgress(int progress) {
+ mCurrentProgress = mTargetProgress;
+ mTargetProgress = progress;
+ mIncrement = (mTargetProgress - mCurrentProgress) / STEPS;
+ mHandler.removeMessages(MSG_UPDATE);
+ mHandler.sendEmptyMessage(MSG_UPDATE);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+// super.onDraw(canvas);
+ Drawable d = getDrawable();
+ d.setBounds(mBounds);
+ d.draw(canvas);
+ }
+
+}
diff --git a/src/com/android/browser/Performance.java b/src/com/android/browser/Performance.java
new file mode 100644
index 000000000..e9ddfa263
--- /dev/null
+++ b/src/com/android/browser/Performance.java
@@ -0,0 +1,133 @@
+/*
+ * 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.net.WebAddress;
+import android.os.Debug;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.Log;
+
+/**
+ * Performance analysis
+ */
+public class Performance {
+
+ private static final String LOGTAG = "browser";
+
+ private final static boolean LOGD_ENABLED =
+ com.android.browser.Browser.LOGD_ENABLED;
+
+ private static boolean mInTrace;
+
+ // Performance probe
+ private static final int[] SYSTEM_CPU_FORMAT = new int[] {
+ Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
+ Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
+ Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
+ Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
+ Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
+ Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
+ Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
+ Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
+ };
+
+ private static long mStart;
+ private static long mProcessStart;
+ private static long mUserStart;
+ private static long mSystemStart;
+ private static long mIdleStart;
+ private static long mIrqStart;
+
+ private static long mUiStart;
+
+ static void tracePageStart(String url) {
+ if (BrowserSettings.getInstance().isTracing()) {
+ String host;
+ try {
+ WebAddress uri = new WebAddress(url);
+ host = uri.getHost();
+ } catch (android.net.ParseException ex) {
+ host = "browser";
+ }
+ host = host.replace('.', '_');
+ host += ".trace";
+ mInTrace = true;
+ Debug.startMethodTracing(host, 20 * 1024 * 1024);
+ }
+ }
+
+ static void tracePageFinished() {
+ if (mInTrace) {
+ mInTrace = false;
+ Debug.stopMethodTracing();
+ }
+ }
+
+ static void onPageStarted() {
+ mStart = SystemClock.uptimeMillis();
+ mProcessStart = Process.getElapsedCpuTime();
+ long[] sysCpu = new long[7];
+ if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, sysCpu, null)) {
+ mUserStart = sysCpu[0] + sysCpu[1];
+ mSystemStart = sysCpu[2];
+ mIdleStart = sysCpu[3];
+ mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
+ }
+ mUiStart = SystemClock.currentThreadTimeMillis();
+ }
+
+ static void onPageFinished(String url) {
+ long[] sysCpu = new long[7];
+ if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, sysCpu, null)) {
+ String uiInfo =
+ "UI thread used " + (SystemClock.currentThreadTimeMillis() - mUiStart) + " ms";
+ if (LOGD_ENABLED) {
+ Log.d(LOGTAG, uiInfo);
+ }
+ // The string that gets written to the log
+ String performanceString =
+ "It took total " + (SystemClock.uptimeMillis() - mStart)
+ + " ms clock time to load the page." + "\nbrowser process used "
+ + (Process.getElapsedCpuTime() - mProcessStart)
+ + " ms, user processes used " + (sysCpu[0] + sysCpu[1] - mUserStart)
+ * 10 + " ms, kernel used " + (sysCpu[2] - mSystemStart) * 10
+ + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
+ + " ms and irq took " + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
+ * 10 + " ms, " + uiInfo;
+ if (LOGD_ENABLED) {
+ Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
+ }
+ if (url != null) {
+ // strip the url to maintain consistency
+ String newUrl = new String(url);
+ if (newUrl.startsWith("http://www.")) {
+ newUrl = newUrl.substring(11);
+ } else if (newUrl.startsWith("http://")) {
+ newUrl = newUrl.substring(7);
+ } else if (newUrl.startsWith("https://www.")) {
+ newUrl = newUrl.substring(12);
+ } else if (newUrl.startsWith("https://")) {
+ newUrl = newUrl.substring(8);
+ }
+ if (LOGD_ENABLED) {
+ Log.d(LOGTAG, newUrl + " loaded");
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/browser/ScrollWebView.java b/src/com/android/browser/ScrollWebView.java
new file mode 100644
index 000000000..97bd2c7a1
--- /dev/null
+++ b/src/com/android/browser/ScrollWebView.java
@@ -0,0 +1,125 @@
+/*
+ * 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.util.AttributeSet;
+import android.view.View;
+import android.webkit.WebView;
+
+import java.util.Map;
+
+/**
+ * Manage WebView scroll events
+ */
+public class ScrollWebView extends WebView {
+
+ private ScrollListener mScrollListener;
+ private boolean mIsCancelled;
+ private Runnable mScrollRunnable;
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ * @param javascriptInterfaces
+ */
+ public ScrollWebView(Context context, AttributeSet attrs, int defStyle,
+ Map<String, Object> javascriptInterfaces, boolean privateBrowsing) {
+ super(context, attrs, defStyle, javascriptInterfaces, privateBrowsing);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public ScrollWebView(Context context, AttributeSet attrs, int defStyle,
+ boolean privateBrowsing) {
+ super(context, attrs, defStyle, privateBrowsing);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public ScrollWebView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * @param context
+ */
+ public ScrollWebView(Context context) {
+ super(context);
+ }
+
+ void hideEmbeddedTitleBar() {
+ scrollBy(0, getVisibleTitleHeight());
+ }
+
+ @Override
+ public void setEmbeddedTitleBar(final View title) {
+ super.setEmbeddedTitleBar(title);
+ if (title != null && mScrollListener != null) {
+ // allow the scroll listener to initialize its state
+ post(new Runnable() {
+ @Override
+ public void run() {
+ mScrollListener.onScroll((title.getHeight() == 0 ||
+ getVisibleTitleHeight() > 0));
+ }
+ });
+ }
+ }
+
+ @Override
+ public void stopScroll() {
+ mIsCancelled = true;
+ super.stopScroll();
+ }
+
+ @Override
+ protected void onScrollChanged(int l, final int t, int ol, int ot) {
+ super.onScrollChanged(l, t, ol, ot);
+ if (!mIsCancelled) {
+ post(mScrollRunnable);
+ } else {
+ mIsCancelled = false;
+ }
+ }
+
+ void setScrollListener(ScrollListener l) {
+ mScrollListener = l;
+ if (mScrollListener != null) {
+ mScrollRunnable = new Runnable() {
+ public void run() {
+ if (!mIsCancelled) {
+ mScrollListener.onScroll(getVisibleTitleHeight() > 0);
+ }
+ }
+ };
+ }
+ }
+
+ // callback for scroll events
+
+ interface ScrollListener {
+ public void onScroll(boolean titleVisible);
+ }
+
+}
diff --git a/src/com/android/browser/SelectDialog.java b/src/com/android/browser/SelectDialog.java
deleted file mode 100644
index 461127a5e..000000000
--- a/src/com/android/browser/SelectDialog.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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/ShortcutActivity.java b/src/com/android/browser/ShortcutActivity.java
new file mode 100644
index 000000000..57cb4a7f2
--- /dev/null
+++ b/src/com/android/browser/ShortcutActivity.java
@@ -0,0 +1,67 @@
+/*
+ * 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.app.Activity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+
+public class ShortcutActivity extends Activity
+ implements BookmarksPageCallbacks {
+
+ private BrowserBookmarksPage mBookmarks;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // TODO: Is this needed?
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+ mBookmarks = BrowserBookmarksPage.newInstance(this, null, null);
+ mBookmarks.setEnableContextMenu(false);
+ mBookmarks.setShowRootFolder(true);
+ getFragmentManager()
+ .openTransaction()
+ .add(android.R.id.content, mBookmarks)
+ .commit();
+ }
+
+ // BookmarksPageCallbacks
+
+ @Override
+ public boolean onBookmarkSelected(Cursor c, boolean isFolder) {
+ if (isFolder) {
+ return false;
+ }
+ Intent intent = BrowserBookmarksPage.createShortcutIntent(this, c);
+ setResult(RESULT_OK, intent);
+ finish();
+ return true;
+ }
+
+ @Override
+ public boolean onOpenInNewWindow(Cursor c) {
+ return false;
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (!mBookmarks.onBackPressed()) {
+ super.onBackPressed();
+ }
+ }
+}
diff --git a/src/com/android/browser/SuggestionsAdapter.java b/src/com/android/browser/SuggestionsAdapter.java
new file mode 100644
index 000000000..8c0635360
--- /dev/null
+++ b/src/com/android/browser/SuggestionsAdapter.java
@@ -0,0 +1,623 @@
+/*
+ * 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 com.android.browser.search.SearchEngine;
+
+import android.app.SearchManager;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.provider.BrowserContract;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * adapter to wrap multiple cursors for url/search completions
+ */
+public class SuggestionsAdapter extends BaseAdapter implements Filterable, OnClickListener {
+
+ static final int TYPE_BOOKMARK = 0;
+ static final int TYPE_SUGGEST_URL = 1;
+ static final int TYPE_HISTORY = 2;
+ static final int TYPE_SEARCH = 3;
+ static final int TYPE_SUGGEST = 4;
+
+ private static final String[] COMBINED_PROJECTION =
+ {BrowserContract.Combined._ID, BrowserContract.Combined.TITLE,
+ BrowserContract.Combined.URL, BrowserContract.Combined.IS_BOOKMARK};
+
+ private static final String[] SEARCHES_PROJECTION = {BrowserContract.Searches.SEARCH};
+
+ private static final String COMBINED_SELECTION =
+ "(url LIKE ? OR url LIKE ? OR url LIKE ? OR url LIKE ? OR title LIKE ?)";
+
+ Context mContext;
+ Filter mFilter;
+ SuggestionResults mMixedResults;
+ List<SuggestItem> mSuggestResults, mFilterResults;
+ List<CursorSource> mSources;
+ boolean mLandscapeMode;
+ CompletionListener mListener;
+ int mLinesPortrait;
+ int mLinesLandscape;
+ Object mResultsLock = new Object();
+
+ interface CompletionListener {
+
+ public void onSearch(String txt);
+
+ public void onSelect(String txt, String extraData);
+
+ public void onFilterComplete(int count);
+
+ }
+
+ public SuggestionsAdapter(Context ctx, CompletionListener listener) {
+ mContext = ctx;
+ mListener = listener;
+ mLinesPortrait = mContext.getResources().
+ getInteger(R.integer.max_suggest_lines_portrait);
+ mLinesLandscape = mContext.getResources().
+ getInteger(R.integer.max_suggest_lines_landscape);
+ mFilter = new SuggestFilter();
+ addSource(new SearchesCursor());
+ addSource(new CombinedCursor());
+ }
+
+ public void setLandscapeMode(boolean mode) {
+ mLandscapeMode = mode;
+ notifyDataSetChanged();
+ }
+
+ public int getLeftCount() {
+ return mMixedResults.getLeftCount();
+ }
+
+ public int getRightCount() {
+ return mMixedResults.getRightCount();
+ }
+
+ public void addSource(CursorSource c) {
+ if (mSources == null) {
+ mSources = new ArrayList<CursorSource>(5);
+ }
+ mSources.add(c);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (R.id.icon2 == v.getId()) {
+ // replace input field text with suggestion text
+ SuggestItem item = (SuggestItem) ((View) v.getParent()).getTag();
+ mListener.onSearch(item.title);
+ } else {
+ SuggestItem item = (SuggestItem) v.getTag();
+ mListener.onSelect((TextUtils.isEmpty(item.url)? item.title : item.url),
+ item.extra);
+ }
+ }
+
+ @Override
+ public Filter getFilter() {
+ return mFilter;
+ }
+
+ @Override
+ public int getCount() {
+ return (mMixedResults == null) ? 0 : mMixedResults.getLineCount();
+ }
+
+ @Override
+ public SuggestItem getItem(int position) {
+ if (mMixedResults == null) {
+ return null;
+ }
+ if (mLandscapeMode) {
+ if (position >= mMixedResults.getLineCount()) {
+ // right column
+ position = position - mMixedResults.getLineCount();
+ // index in column
+ if (position >= mMixedResults.getRightCount()) {
+ return null;
+ }
+ return mMixedResults.items.get(position + mMixedResults.getLeftCount());
+ } else {
+ // left column
+ if (position >= mMixedResults.getLeftCount()) {
+ return null;
+ }
+ return mMixedResults.items.get(position);
+ }
+ } else {
+ return mMixedResults.items.get(position);
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ View view = convertView;
+ if (view == null) {
+ view = inflater.inflate(R.layout.suggestion_two_column, parent, false);
+ }
+ View s1 = view.findViewById(R.id.suggest1);
+ View s2 = view.findViewById(R.id.suggest2);
+ View div = view.findViewById(R.id.suggestion_divider);
+ if (mLandscapeMode) {
+ SuggestItem item = getItem(position);
+ div.setVisibility(View.VISIBLE);
+ if (item != null) {
+ s1.setVisibility(View.VISIBLE);
+ bindView(s1, item);
+ } else {
+ s1.setVisibility(View.INVISIBLE);
+ }
+ item = getItem(position + mMixedResults.getLineCount());
+ if (item != null) {
+ s2.setVisibility(View.VISIBLE);
+ bindView(s2, item);
+ } else {
+ s2.setVisibility(View.INVISIBLE);
+ }
+ return view;
+ } else {
+ s1.setVisibility(View.VISIBLE);
+ div.setVisibility(View.GONE);
+ s2.setVisibility(View.GONE);
+ bindView(s1, getItem(position));
+ return view;
+ }
+ }
+
+ private void bindView(View view, SuggestItem item) {
+ // store item for click handling
+ view.setTag(item);
+ TextView tv1 = (TextView) view.findViewById(android.R.id.text1);
+ TextView tv2 = (TextView) view.findViewById(android.R.id.text2);
+ ImageView ic1 = (ImageView) view.findViewById(R.id.icon1);
+ View spacer = view.findViewById(R.id.spacer);
+ View ic2 = view.findViewById(R.id.icon2);
+ View div = view.findViewById(R.id.divider);
+ tv1.setText(item.title);
+ tv2.setText(item.url);
+ int id = -1;
+ switch (item.type) {
+ case TYPE_SUGGEST:
+ case TYPE_SEARCH:
+ id = R.drawable.ic_search_category_suggest;
+ break;
+ case TYPE_BOOKMARK:
+ id = R.drawable.ic_search_category_bookmark;
+ break;
+ case TYPE_HISTORY:
+ id = R.drawable.ic_search_category_history;
+ break;
+ case TYPE_SUGGEST_URL:
+ id = R.drawable.ic_search_category_browser;
+ break;
+ default:
+ id = -1;
+ }
+ if (id != -1) {
+ ic1.setImageDrawable(mContext.getResources().getDrawable(id));
+ }
+ ic2.setVisibility(((TYPE_SUGGEST == item.type) || (TYPE_SEARCH == item.type))
+ ? View.VISIBLE : View.GONE);
+ div.setVisibility(ic2.getVisibility());
+ spacer.setVisibility(((TYPE_SUGGEST == item.type) || (TYPE_SEARCH == item.type))
+ ? View.GONE : View.INVISIBLE);
+ view.setOnClickListener(this);
+ ic2.setOnClickListener(this);
+ }
+
+ class SlowFilterTask extends AsyncTask<CharSequence, Void, List<SuggestItem>> {
+
+ @Override
+ protected List<SuggestItem> doInBackground(CharSequence... params) {
+ SuggestCursor cursor = new SuggestCursor();
+ cursor.runQuery(params[0]);
+ List<SuggestItem> results = new ArrayList<SuggestItem>();
+ int count = cursor.getCount();
+ for (int i = 0; i < count; i++) {
+ results.add(cursor.getItem());
+ cursor.moveToNext();
+ }
+ cursor.close();
+ return results;
+ }
+
+ @Override
+ protected void onPostExecute(List<SuggestItem> items) {
+ mSuggestResults = items;
+ mMixedResults = buildSuggestionResults();
+ notifyDataSetChanged();
+ mListener.onFilterComplete(mMixedResults.getLineCount());
+ }
+ }
+
+ SuggestionResults buildSuggestionResults() {
+ SuggestionResults mixed = new SuggestionResults();
+ List<SuggestItem> filter, suggest;
+ synchronized (mResultsLock) {
+ filter = mFilterResults;
+ suggest = mSuggestResults;
+ }
+ if (filter != null) {
+ for (SuggestItem item : filter) {
+ mixed.addResult(item);
+ }
+ }
+ if (suggest != null) {
+ for (SuggestItem item : suggest) {
+ mixed.addResult(item);
+ }
+ }
+ return mixed;
+ }
+
+ class SuggestFilter extends Filter {
+
+ @Override
+ public CharSequence convertResultToString(Object item) {
+ if (item == null) {
+ return "";
+ }
+ SuggestItem sitem = (SuggestItem) item;
+ if (sitem.title != null) {
+ return sitem.title;
+ } else {
+ return sitem.url;
+ }
+ }
+
+ void startSuggestionsAsync(final CharSequence constraint) {
+ new SlowFilterTask().execute(constraint);
+ }
+
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ FilterResults res = new FilterResults();
+ if (TextUtils.isEmpty(constraint)) {
+ res.count = 0;
+ res.values = null;
+ return res;
+ }
+ startSuggestionsAsync(constraint);
+ List<SuggestItem> filterResults = new ArrayList<SuggestItem>();
+ if (constraint != null) {
+ for (CursorSource sc : mSources) {
+ sc.runQuery(constraint);
+ }
+ mixResults(filterResults);
+ }
+ synchronized (mResultsLock) {
+ mFilterResults = filterResults;
+ }
+ SuggestionResults mixed = buildSuggestionResults();
+ res.count = mixed.getLineCount();
+ res.values = mixed;
+ return res;
+ }
+
+ void mixResults(List<SuggestItem> results) {
+ int maxLines = mLandscapeMode ? mLinesLandscape : (mLinesPortrait / 2);
+ for (int i = 0; i < mSources.size(); i++) {
+ CursorSource s = mSources.get(i);
+ int n = Math.min(s.getCount(), maxLines);
+ maxLines -= n;
+ boolean more = false;
+ for (int j = 0; j < n; j++) {
+ results.add(s.getItem());
+ more = s.moveToNext();
+ }
+ }
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults fresults) {
+ mMixedResults = (SuggestionResults) fresults.values;
+ mListener.onFilterComplete(fresults.count);
+ notifyDataSetChanged();
+ }
+
+ }
+
+ /**
+ * sorted list of results of a suggestion query
+ *
+ */
+ class SuggestionResults {
+
+ ArrayList<SuggestItem> items;
+ // count per type
+ int[] counts;
+
+ SuggestionResults() {
+ items = new ArrayList<SuggestItem>(24);
+ // n of types:
+ counts = new int[5];
+ }
+
+ int getTypeCount(int type) {
+ return counts[type];
+ }
+
+ void addResult(SuggestItem item) {
+ int ix = 0;
+ while ((ix < items.size()) && (item.type >= items.get(ix).type))
+ ix++;
+ items.add(ix, item);
+ counts[item.type]++;
+ }
+
+ int getLineCount() {
+ if (mLandscapeMode) {
+ return Math.min(mLinesLandscape,
+ Math.max(getLeftCount(), getRightCount()));
+ } else {
+ return Math.min(mLinesPortrait, getLeftCount() + getRightCount());
+ }
+ }
+
+ int getLeftCount() {
+ return counts[TYPE_BOOKMARK] + counts[TYPE_HISTORY] + counts[TYPE_SUGGEST_URL];
+ }
+
+ int getRightCount() {
+ return counts[TYPE_SEARCH] + counts[TYPE_SUGGEST];
+ }
+
+ @Override
+ public String toString() {
+ if (items == null) return null;
+ if (items.size() == 0) return "[]";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < items.size(); i++) {
+ SuggestItem item = items.get(i);
+ sb.append(item.type + ": " + item.title);
+ if (i < items.size() - 1) {
+ sb.append(", ");
+ }
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * data object to hold suggestion values
+ */
+ class SuggestItem {
+ String title;
+ String url;
+ int type;
+ String extra;
+
+ public SuggestItem(String text, String u, int t) {
+ title = text;
+ url = u;
+ type = t;
+ }
+ }
+
+ abstract class CursorSource {
+
+ Cursor mCursor;
+
+ boolean moveToNext() {
+ return mCursor.moveToNext();
+ }
+
+ public abstract void runQuery(CharSequence constraint);
+
+ public abstract SuggestItem getItem();
+
+ public int getCount() {
+ return (mCursor != null) ? mCursor.getCount() : 0;
+ }
+
+ public void close() {
+ if (mCursor != null) {
+ mCursor.close();
+ }
+ }
+ }
+
+ /**
+ * combined bookmark & history source
+ */
+ class CombinedCursor extends CursorSource {
+
+ @Override
+ public SuggestItem getItem() {
+ if ((mCursor != null) && (!mCursor.isAfterLast())) {
+ String title = mCursor.getString(1);
+ String url = mCursor.getString(2);
+ boolean isBookmark = (mCursor.getInt(3) == 1);
+ return new SuggestItem(getTitle(title, url), getUrl(title, url),
+ isBookmark ? TYPE_BOOKMARK : TYPE_HISTORY);
+ }
+ return null;
+ }
+
+ @Override
+ public void runQuery(CharSequence constraint) {
+ // constraint != null
+ if (mCursor != null) {
+ mCursor.close();
+ }
+ String like = constraint + "%";
+ String[] args = null;
+ String selection = null;
+ if (like.startsWith("http") || like.startsWith("file")) {
+ args = new String[1];
+ args[0] = like;
+ selection = "url LIKE ?";
+ } else {
+ args = new String[5];
+ args[0] = "http://" + like;
+ args[1] = "http://www." + like;
+ args[2] = "https://" + like;
+ args[3] = "https://www." + like;
+ // To match against titles.
+ args[4] = like;
+ selection = COMBINED_SELECTION;
+ }
+ Uri.Builder ub = BrowserContract.Combined.CONTENT_URI.buildUpon();
+ ub.appendQueryParameter(BrowserContract.PARAM_LIMIT,
+ Integer.toString(mLinesPortrait));
+ mCursor =
+ mContext.getContentResolver().query(ub.build(), COMBINED_PROJECTION,
+ selection,
+ (constraint != null) ? args : null,
+ BrowserContract.Combined.VISITS + " DESC, " +
+ BrowserContract.Combined.DATE_LAST_VISITED + " DESC");
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ }
+ }
+
+ /**
+ * Provides the title (text line 1) for a browser suggestion, which should be the
+ * webpage title. If the webpage title is empty, returns the stripped url instead.
+ *
+ * @return the title string to use
+ */
+ private String getTitle(String title, String url) {
+ if (TextUtils.isEmpty(title) || TextUtils.getTrimmedLength(title) == 0) {
+ title = UrlUtils.stripUrl(url);
+ }
+ return title;
+ }
+
+ /**
+ * Provides the subtitle (text line 2) for a browser suggestion, which should be the
+ * webpage url. If the webpage title is empty, then the url should go in the title
+ * instead, and the subtitle should be empty, so this would return null.
+ *
+ * @return the subtitle string to use, or null if none
+ */
+ private String getUrl(String title, String url) {
+ if (TextUtils.isEmpty(title)
+ || TextUtils.getTrimmedLength(title) == 0
+ || title.equals(url)) {
+ return null;
+ } else {
+ return UrlUtils.stripUrl(url);
+ }
+ }
+ }
+
+ class SearchesCursor extends CursorSource {
+
+ @Override
+ public SuggestItem getItem() {
+ if ((mCursor != null) && (!mCursor.isAfterLast())) {
+ return new SuggestItem(mCursor.getString(0), null, TYPE_SEARCH);
+ }
+ return null;
+ }
+
+ @Override
+ public void runQuery(CharSequence constraint) {
+ // constraint != null
+ if (mCursor != null) {
+ mCursor.close();
+ }
+ String like = constraint + "%";
+ String[] args = new String[] {like};
+ String selection = BrowserContract.Searches.SEARCH + " LIKE ?";
+ Uri.Builder ub = BrowserContract.Searches.CONTENT_URI.buildUpon();
+ ub.appendQueryParameter(BrowserContract.PARAM_LIMIT,
+ Integer.toString(mLinesPortrait));
+ mCursor =
+ mContext.getContentResolver().query(ub.build(), SEARCHES_PROJECTION,
+ selection,
+ args, BrowserContract.Searches.DATE + " DESC");
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ }
+ }
+
+ }
+
+ class SuggestCursor extends CursorSource {
+
+ @Override
+ public SuggestItem getItem() {
+ if (mCursor != null) {
+ String title = mCursor.getString(
+ mCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
+ String text2 = mCursor.getString(
+ mCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2));
+ String url = mCursor.getString(
+ mCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL));
+ String uri = mCursor.getString(
+ mCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA));
+ int type = (TextUtils.isEmpty(url)) ? TYPE_SUGGEST : TYPE_SUGGEST_URL;
+ SuggestItem item = new SuggestItem(title, url, type);
+ item.extra = mCursor.getString(
+ mCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA));
+ return item;
+ }
+ return null;
+ }
+
+ @Override
+ public void runQuery(CharSequence constraint) {
+ if (mCursor != null) {
+ mCursor.close();
+ }
+ if (!TextUtils.isEmpty(constraint)) {
+ SearchEngine searchEngine = BrowserSettings.getInstance().getSearchEngine();
+ if (searchEngine != null && searchEngine.supportsSuggestions()) {
+ mCursor = searchEngine.getSuggestions(mContext, constraint.toString());
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ }
+ }
+ } else {
+ mCursor = null;
+ }
+ }
+
+ }
+
+ public void clearCache() {
+ mFilterResults = null;
+ mSuggestResults = null;
+ }
+
+}
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 7019c8a01..a048c2da9 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -16,42 +16,28 @@
package com.android.browser;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Vector;
+import com.android.common.speech.LoggingEvents;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.SearchManager;
import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Message;
import android.os.SystemClock;
-import android.provider.Browser;
import android.speech.RecognizerResultsIntent;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewStub;
-import android.view.View.OnClickListener;
import android.webkit.ConsoleMessage;
-import android.webkit.CookieSyncManager;
import android.webkit.DownloadListener;
import android.webkit.GeolocationPermissions;
import android.webkit.HttpAuthHandler;
@@ -62,21 +48,26 @@ import android.webkit.WebBackForwardList;
import android.webkit.WebBackForwardListClient;
import android.webkit.WebChromeClient;
import android.webkit.WebHistoryItem;
-import android.webkit.WebIconDatabase;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
-import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
+import android.widget.Toast;
-import com.android.common.speech.LoggingEvents;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Vector;
/**
* Class for maintaining Tabs with a main WebView and a subwindow.
*/
class Tab {
+
// Log Tag
private static final String LOGTAG = "Tab";
// Special case the logtag for messages for the Console to make it easier to
@@ -84,6 +75,13 @@ class Tab {
// of the browser.
private static final String CONSOLE_LOGTAG = "browser";
+ final static int LOCK_ICON_UNSECURE = 0;
+ final static int LOCK_ICON_SECURE = 1;
+ final static int LOCK_ICON_MIXED = 2;
+
+ Activity mActivity;
+ private WebViewController mWebViewController;
+
// The Geolocation permissions prompt
private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
// Main WebView wrapper
@@ -110,8 +108,9 @@ class Tab {
private boolean mCloseOnExit;
// If true, the tab is in the foreground of the current activity.
private boolean mInForeground;
- // If true, the tab is in loading state.
- private boolean mInLoad;
+ // If true, the tab is in page loading state (after onPageStarted,
+ // before onPageFinsihed)
+ private boolean mInPageLoad;
// The time the load started, used to find load page time
private long mLoadStartTime;
// Application identifier used to find tabs that another application wants
@@ -120,6 +119,10 @@ class Tab {
// Keep the original url around to avoid killing the old WebView if the url
// has not changed.
private String mOriginalUrl;
+ // Hold on to the currently loaded url
+ private String mCurrentUrl;
+ //The currently loaded title
+ private String mCurrentTitle;
// Error console for the tab
private ErrorConsoleView mErrorConsole;
// the lock icon type and previous lock icon type for the tab
@@ -127,8 +130,6 @@ class Tab {
private int mPrevLockIconType;
// Inflation service for making subwindows.
private final LayoutInflater mInflateService;
- // The BrowserActivity which owners the Tab
- private final BrowserActivity mActivity;
// The listener that gets invoked when a download is started from the
// mMainView
private final DownloadListener mDownloadListener;
@@ -155,6 +156,7 @@ class Tab {
static final String PARENTTAB = "parentTab";
static final String APPID = "appid";
static final String ORIGINALURL = "originalUrl";
+ static final String INCOGNITO = "privateBrowsingEnabled";
// -------------------------------------------------------------------------
@@ -170,10 +172,11 @@ class Tab {
if (mVoiceSearchData != null) {
mVoiceSearchData = null;
if (mInForeground) {
- mActivity.revertVoiceTitleBar();
+ mWebViewController.revertVoiceSearchMode(this);
}
}
}
+
/**
* Return whether the tab is in voice search mode.
*/
@@ -271,7 +274,7 @@ class Tab {
mVoiceSearchData.mLastVoiceSearchTitle
= mVoiceSearchData.mVoiceSearchResults.get(index);
if (mInForeground) {
- mActivity.showVoiceTitleBar(mVoiceSearchData.mLastVoiceSearchTitle);
+ mWebViewController.activateVoiceSearchMode(mVoiceSearchData.mLastVoiceSearchTitle);
}
if (mVoiceSearchData.mVoiceSearchHtmls != null) {
// When index was found it was already ensured that it was valid
@@ -298,7 +301,7 @@ class Tab {
mVoiceSearchData.mLastVoiceSearchUrl
= mVoiceSearchData.mVoiceSearchUrls.get(index);
if (null == mVoiceSearchData.mLastVoiceSearchUrl) {
- mVoiceSearchData.mLastVoiceSearchUrl = mActivity.smartUrlFilter(
+ mVoiceSearchData.mLastVoiceSearchUrl = UrlUtils.smartUrlFilter(
mVoiceSearchData.mLastVoiceSearchTitle);
}
Map<String, String> headers = null;
@@ -391,7 +394,7 @@ class Tab {
mDescription = desc;
mError = error;
}
- };
+ }
private void processNextError() {
if (mQueuedErrors == null) {
@@ -458,7 +461,7 @@ class Tab {
private Message mResend;
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
- mInLoad = true;
+ mInPageLoad = true;
mLoadStartTime = SystemClock.uptimeMillis();
if (mVoiceSearchData != null
&& !url.equals(mVoiceSearchData.mLastVoiceSearchUrl)) {
@@ -470,12 +473,6 @@ class Tab {
revertVoiceSearchMode();
}
- // We've started to load a new page. If there was a pending message
- // to save a screenshot then we will now take the new page and save
- // an incorrect screenshot. Therefore, remove any pending thumbnail
- // messages from the queue.
- mActivity.removeMessages(BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL,
- view);
// If we start a touch icon load and then load a new page, we don't
// want to cancel the current touch icon loader. But, we do want to
@@ -488,49 +485,23 @@ class Tab {
// reset the error console
if (mErrorConsole != null) {
mErrorConsole.clearErrorMessages();
- if (mActivity.shouldShowErrorConsole()) {
+ if (mWebViewController.shouldShowErrorConsole()) {
mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
}
}
- // update the bookmark database for favicon
- if (favicon != null) {
- BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
- .getContentResolver(), null, url, favicon);
- }
-
- // reset sync timer to avoid sync starts during loading a page
- CookieSyncManager.getInstance().resetSync();
-
- if (!mActivity.isNetworkUp()) {
- view.setNetworkAvailable(false);
- }
// finally update the UI in the activity if it is in the foreground
- if (mInForeground) {
- mActivity.onPageStarted(view, url, favicon);
- }
+ mWebViewController.onPageStarted(Tab.this, view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
LogTag.logPageFinishedLoading(
url, SystemClock.uptimeMillis() - mLoadStartTime);
- mInLoad = false;
-
- if (mInForeground && !mActivity.didUserStopLoading()
- || !mInForeground) {
- // Only update the bookmark screenshot if the user did not
- // cancel the load early.
- mActivity.postMessage(
- BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL, 0, 0, view,
- 500);
- }
+ mInPageLoad = false;
- // finally update the UI in the activity if it is in the foreground
- if (mInForeground) {
- mActivity.onPageFinished(view, url);
- }
+ mWebViewController.onPageFinished(Tab.this, url);
}
// return true if want to hijack the url to let another app to handle it
@@ -549,7 +520,7 @@ class Tab {
mActivity.sendBroadcast(logIntent);
}
if (mInForeground) {
- return mActivity.shouldOverrideUrlLoading(view, url);
+ return mWebViewController.shouldOverrideUrlLoading(view, url);
} else {
return false;
}
@@ -566,11 +537,11 @@ class Tab {
if (url != null && url.length() > 0) {
// It is only if the page claims to be secure that we may have
// to update the lock:
- if (mLockIconType == BrowserActivity.LOCK_ICON_SECURE) {
+ if (mLockIconType == LOCK_ICON_SECURE) {
// If NOT a 'safe' url, change the lock to mixed content!
if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url)
|| URLUtil.isAboutUrl(url))) {
- mLockIconType = BrowserActivity.LOCK_ICON_MIXED;
+ mLockIconType = LOCK_ICON_MIXED;
}
}
}
@@ -590,12 +561,16 @@ class Tab {
errorCode != WebViewClient.ERROR_FILE) {
queueError(errorCode, description);
}
- Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
- + " " + description);
+
+ // Don't log URLs when in private browsing mode
+ if (!isPrivateBrowsingEnabled()) {
+ Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
+ + " " + description);
+ }
// We need to reset the title after an error if it is in foreground.
if (mInForeground) {
- mActivity.resetTitleAndRevertLockIcon();
+ mWebViewController.resetTitleAndRevertLockIcon(Tab.this);
}
}
@@ -661,31 +636,7 @@ class Tab {
@Override
public void doUpdateVisitedHistory(WebView view, String url,
boolean isReload) {
- if (url.regionMatches(true, 0, "about:", 0, 6)) {
- return;
- }
- // remove "client" before updating it to the history so that it wont
- // show up in the auto-complete list.
- int index = url.indexOf("client=ms-");
- if (index > 0 && url.contains(".google.")) {
- int end = url.indexOf('&', index);
- if (end > 0) {
- url = url.substring(0, index)
- .concat(url.substring(end + 1));
- } else {
- // the url.charAt(index-1) should be either '?' or '&'
- url = url.substring(0, index-1);
- }
- }
- final ContentResolver cr = mActivity.getContentResolver();
- final String newUrl = url;
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... unused) {
- Browser.updateVisitedHistory(cr, newUrl, true);
- return null;
- }
- }.execute();
- WebIconDatabase.getInstance().retainIconForPageUrl(url);
+ mWebViewController.doUpdateVisitedHistory(Tab.this, url, isReload);
}
/**
@@ -751,21 +702,21 @@ class Tab {
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
- mActivity.showSSLCertificateOnError(view,
+ mWebViewController.showSslCertificateOnError(view,
handler, error);
}
- }).setNegativeButton(R.string.cancel,
+ }).setNegativeButton(R.string.ssl_go_back,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
handler.cancel();
- mActivity.resetTitleAndRevertLockIcon();
+ mWebViewController.resetTitleAndRevertLockIcon(Tab.this);
}
}).setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
handler.cancel();
- mActivity.resetTitleAndRevertLockIcon();
+ mWebViewController.resetTitleAndRevertLockIcon(Tab.this);
}
}).show();
} else {
@@ -784,31 +735,7 @@ class Tab {
public void onReceivedHttpAuthRequest(WebView view,
final HttpAuthHandler handler, final String host,
final String realm) {
- String username = null;
- String password = null;
-
- boolean reuseHttpAuthUsernamePassword = handler
- .useHttpAuthUsernamePassword();
-
- if (reuseHttpAuthUsernamePassword && view != null) {
- String[] credentials = view.getHttpAuthUsernamePassword(
- host, realm);
- if (credentials != null && credentials.length == 2) {
- username = credentials[0];
- password = credentials[1];
- }
- }
-
- if (username != null && password != null) {
- handler.proceed(username, password);
- } else {
- if (mInForeground) {
- mActivity.showHttpAuthentication(handler, host, realm,
- null, null, null, 0);
- } else {
- handler.cancel();
- }
- }
+ mWebViewController.onReceivedHttpAuthRequest(Tab.this, view, handler, host, realm);
}
@Override
@@ -816,25 +743,15 @@ class Tab {
if (!mInForeground) {
return false;
}
- if (mActivity.isMenuDown()) {
- // only check shortcut key when MENU is held
- return mActivity.getWindow().isShortcutKey(event.getKeyCode(),
- event);
- } else {
- return false;
- }
+ return mWebViewController.shouldOverrideKeyEvent(event);
}
@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
- if (!mInForeground || mActivity.mActivityInPause) {
+ if (!mInForeground) {
return;
}
- if (event.isDown()) {
- mActivity.onKeyDown(event.getKeyCode(), event);
- } else {
- mActivity.onKeyUp(event.getKeyCode(), event);
- }
+ mWebViewController.onUnhandledKeyEvent(event);
}
};
@@ -849,11 +766,11 @@ class Tab {
(WebView.WebViewTransport) msg.obj;
if (dialog) {
createSubWindow();
- mActivity.attachSubWindow(Tab.this);
+ mWebViewController.attachSubWindow(Tab.this);
transport.setWebView(mSubView);
} else {
- final Tab newTab = mActivity.openTabAndShow(
- BrowserActivity.EMPTY_URL_DATA, false, null);
+ final Tab newTab = mWebViewController.openTabAndShow(
+ IntentHandler.EMPTY_URL_DATA, false, null);
if (newTab != Tab.this) {
Tab.this.addChildTab(newTab);
}
@@ -878,7 +795,7 @@ class Tab {
.setPositiveButton(R.string.ok, null)
.show();
return false;
- } else if (!mActivity.getTabControl().canCreateNewTab()) {
+ } else if (!mWebViewController.getTabControl().canCreateNewTab()) {
new AlertDialog.Builder(mActivity)
.setTitle(R.string.too_many_windows_dialog_title)
.setIcon(android.R.drawable.ic_dialog_alert)
@@ -930,7 +847,7 @@ class Tab {
@Override
public void onRequestFocus(WebView view) {
if (!mInForeground) {
- mActivity.switchToTab(mActivity.getTabControl().getTabIndex(
+ mWebViewController.switchToTab(mWebViewController.getTabControl().getTabIndex(
Tab.this));
}
}
@@ -940,94 +857,26 @@ class Tab {
if (mParentTab != null) {
// JavaScript can only close popup window.
if (mInForeground) {
- mActivity.switchToTab(mActivity.getTabControl()
+ mWebViewController.switchToTab(mWebViewController.getTabControl()
.getTabIndex(mParentTab));
}
- mActivity.closeTab(Tab.this);
+ mWebViewController.closeTab(Tab.this);
}
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
- if (newProgress == 100) {
- // sync cookies and cache promptly here.
- CookieSyncManager.getInstance().sync();
- }
- if (mInForeground) {
- mActivity.onProgressChanged(view, newProgress);
- }
+ mWebViewController.onProgressChanged(Tab.this, newProgress);
}
@Override
public void onReceivedTitle(WebView view, final String title) {
- final String pageUrl = view.getUrl();
- if (mInForeground) {
- // here, if url is null, we want to reset the title
- mActivity.setUrlTitle(pageUrl, title);
- }
- if (pageUrl == null || pageUrl.length()
- >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
- return;
- }
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... unused) {
- // See if we can find the current url in our history
- // database and add the new title to it.
- String url = pageUrl;
- if (url.startsWith("http://www.")) {
- url = url.substring(11);
- } else if (url.startsWith("http://")) {
- url = url.substring(4);
- }
- // Escape wildcards for LIKE operator.
- url = url.replace("\\", "\\\\").replace("%", "\\%")
- .replace("_", "\\_");
- Cursor c = null;
- try {
- final ContentResolver cr
- = mActivity.getContentResolver();
- url = "%" + url;
- String [] selArgs = new String[] { url };
- String where = Browser.BookmarkColumns.URL
- + " LIKE ? ESCAPE '\\' AND "
- + Browser.BookmarkColumns.BOOKMARK + " = 0";
- c = cr.query(Browser.BOOKMARKS_URI, new String[]
- { Browser.BookmarkColumns._ID }, where, selArgs,
- null);
- if (c.moveToFirst()) {
- // Current implementation of database only has one
- // entry per url.
- ContentValues map = new ContentValues();
- map.put(Browser.BookmarkColumns.TITLE, title);
- String[] projection = new String[]
- { Integer.valueOf(c.getInt(0)).toString() };
- cr.update(Browser.BOOKMARKS_URI, map, "_id = ?",
- projection);
- }
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "Tab onReceived title", e);
- } catch (SQLiteException ex) {
- Log.e(LOGTAG,
- "onReceivedTitle() caught SQLiteException: ",
- ex);
- } finally {
- if (c != null) c.close();
- }
- return null;
- }
- }.execute();
+ mWebViewController.onReceivedTitle(Tab.this, title);
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
- if (icon != null) {
- BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
- .getContentResolver(), view.getOriginalUrl(), view
- .getUrl(), icon);
- }
- if (mInForeground) {
- mActivity.setFavicon(icon);
- }
+ mWebViewController.onFavicon(Tab.this, view, icon);
}
@Override
@@ -1042,30 +891,22 @@ class Tab {
}
// Have only one async task at a time.
if (mTouchIconLoader == null) {
- mTouchIconLoader = new DownloadTouchIcon(Tab.this, mActivity, cr, view);
+ mTouchIconLoader = new DownloadTouchIcon(Tab.this,
+ mActivity, cr, view);
mTouchIconLoader.execute(url);
}
}
@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);
+ if (mInForeground) mWebViewController.showCustomView(Tab.this, view,
+ callback);
}
@Override
public void onHideCustomView() {
- if (mInForeground) mActivity.onHideCustomView();
+ if (mInForeground) mWebViewController.hideCustomView();
}
/**
@@ -1144,12 +985,16 @@ class Tab {
// call getErrorConsole(true) so it will create one if needed
ErrorConsoleView errorConsole = getErrorConsole(true);
errorConsole.addErrorMessage(consoleMessage);
- if (mActivity.shouldShowErrorConsole()
- && errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
+ if (mWebViewController.shouldShowErrorConsole()
+ && errorConsole.getShowState() !=
+ ErrorConsoleView.SHOW_MAXIMIZED) {
errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
}
}
+ // Don't log console messages in private browsing mode
+ if (isPrivateBrowsingEnabled()) return true;
+
String message = "Console: " + consoleMessage.message() + " "
+ consoleMessage.sourceId() + ":"
+ consoleMessage.lineNumber();
@@ -1183,7 +1028,7 @@ class Tab {
@Override
public Bitmap getDefaultVideoPoster() {
if (mInForeground) {
- return mActivity.getDefaultVideoPoster();
+ return mWebViewController.getDefaultVideoPoster();
}
return null;
}
@@ -1196,15 +1041,15 @@ class Tab {
@Override
public View getVideoLoadingProgressView() {
if (mInForeground) {
- return mActivity.getVideoLoadingProgressView();
+ return mWebViewController.getVideoLoadingProgressView();
}
return null;
}
@Override
- public void openFileChooser(ValueCallback<Uri> uploadMsg) {
+ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
if (mInForeground) {
- mActivity.openFileChooser(uploadMsg);
+ mWebViewController.openFileChooser(uploadMsg, acceptType);
} else {
uploadMsg.onReceiveValue(null);
}
@@ -1215,17 +1060,37 @@ class Tab {
*/
@Override
public void getVisitedHistory(final ValueCallback<String[]> callback) {
- AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() {
- public String[] doInBackground(Void... unused) {
- return Browser.getVisitedHistory(mActivity
- .getContentResolver());
- }
- public void onPostExecute(String[] result) {
- callback.onReceiveValue(result);
- };
- };
- task.execute();
- };
+ mWebViewController.getVisitedHistory(callback);
+ }
+
+ @Override
+ public void setupAutoFill(Message message) {
+ // Prompt the user to set up their profile.
+ final Message msg = message;
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
+ builder.setMessage(R.string.autofill_setup_dialog_message)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ // Take user to the AutoFill profile editor. When they return,
+ // we will send the message that we pass here which will trigger
+ // the form to get filled out with their new profile.
+ mWebViewController.setupAutoFill(msg);
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ // Disable autofill and show a toast with how to turn it on again.
+ BrowserSettings s = BrowserSettings.getInstance();
+ s.addObserver(mMainView.getSettings());
+ s.disableAutoFill(mActivity);
+ s.update();
+ Toast.makeText(mActivity, R.string.autofill_setup_dialog_negative_toast,
+ Toast.LENGTH_LONG).show();
+ }
+ }).show();
+ }
};
// -------------------------------------------------------------------------
@@ -1237,18 +1102,18 @@ class Tab {
private static class SubWindowClient extends WebViewClient {
// The main WebViewClient.
private final WebViewClient mClient;
- private final BrowserActivity mBrowserActivity;
+ private final WebViewController mController;
- SubWindowClient(WebViewClient client, BrowserActivity activity) {
+ SubWindowClient(WebViewClient client, WebViewController controller) {
mClient = client;
- mBrowserActivity = activity;
+ mController = controller;
}
@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();
+ mController.endActionMode();
}
@Override
public void doUpdateVisitedHistory(WebView view, String url,
@@ -1316,25 +1181,29 @@ class Tab {
if (window != mSubView) {
Log.e(LOGTAG, "Can't close the window");
}
- mActivity.dismissSubWindow(Tab.this);
+ mWebViewController.dismissSubWindow(Tab.this);
}
}
// -------------------------------------------------------------------------
+ // TODO temporarily use activity here
+ // remove later
+
// Construct a new tab
- Tab(BrowserActivity activity, WebView w, boolean closeOnExit, String appId,
+ Tab(WebViewController wvcontroller, WebView w, boolean closeOnExit, String appId,
String url) {
- mActivity = activity;
+ mWebViewController = wvcontroller;
+ mActivity = mWebViewController.getActivity();
mCloseOnExit = closeOnExit;
mAppId = appId;
mOriginalUrl = url;
- mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
- mPrevLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
- mInLoad = false;
+ mLockIconType = LOCK_ICON_UNSECURE;
+ mPrevLockIconType = LOCK_ICON_UNSECURE;
+ mInPageLoad = false;
mInForeground = false;
- mInflateService = LayoutInflater.from(activity);
+ mInflateService = LayoutInflater.from(mActivity);
// The tab consists of a container view, which contains the main
// WebView, as well as any other UI elements associated with the tab.
@@ -1344,20 +1213,8 @@ class Tab {
public void onDownloadStart(String url, String userAgent,
String contentDisposition, String mimetype,
long contentLength) {
- mActivity.onDownloadStart(url, userAgent, contentDisposition,
+ mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition,
mimetype, contentLength);
- if (mMainView.copyBackForwardList().getSize() == 0) {
- // This Tab was opened for the sole purpose of downloading a
- // file. Remove it.
- if (mActivity.getTabControl().getCurrentWebView()
- == mMainView) {
- // In this case, the Tab is still on top.
- mActivity.goBackOnePageOrQuit();
- } else {
- // In this case, it is not.
- mActivity.closeTab(Tab.this);
- }
- }
}
};
mWebBackForwardListClient = new WebBackForwardListClient() {
@@ -1449,17 +1306,9 @@ class Tab {
*/
boolean createSubWindow() {
if (mSubView == null) {
- mActivity.closeDialogs();
- mSubViewContainer = mInflateService.inflate(
- R.layout.browser_subwindow, null);
- mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview);
- mSubView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
- // use trackball directly
- mSubView.setMapTrackballToArrowKeys(false);
- // Enable the built-in zoom
- mSubView.getSettings().setBuiltInZoomControls(true);
+ mWebViewController.createSubWindow(this);
mSubView.setWebViewClient(new SubWindowClient(mWebViewClient,
- mActivity));
+ mWebViewController));
mSubView.setWebChromeClient(new SubWindowChromeClient(
mWebChromeClient));
// Set a different DownloadListener for the mSubView, since it will
@@ -1468,25 +1317,16 @@ class Tab {
public void onDownloadStart(String url, String userAgent,
String contentDisposition, String mimetype,
long contentLength) {
- mActivity.onDownloadStart(url, userAgent,
+ mWebViewController.onDownloadStart(Tab.this, url, userAgent,
contentDisposition, mimetype, contentLength);
if (mSubView.copyBackForwardList().getSize() == 0) {
// This subwindow was opened for the sole purpose of
// downloading a file. Remove it.
- mActivity.dismissSubWindow(Tab.this);
+ mWebViewController.dismissSubWindow(Tab.this);
}
}
});
mSubView.setOnCreateContextMenuListener(mActivity);
- final BrowserSettings s = BrowserSettings.getInstance();
- s.addObserver(mSubView.getSettings()).update(s, null);
- final ImageButton cancel = (ImageButton) mSubViewContainer
- .findViewById(R.id.subwindow_close);
- cancel.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- mSubView.getWebChromeClient().onCloseWindow(mSubView);
- }
- });
return true;
}
return false;
@@ -1497,7 +1337,7 @@ class Tab {
*/
void dismissSubWindow() {
if (mSubView != null) {
- mActivity.closeDialogs();
+ mWebViewController.endActionMode();
BrowserSettings.getInstance().deleteObserver(
mSubView.getSettings());
mSubView.destroy();
@@ -1506,84 +1346,6 @@ class Tab {
}
}
- /**
- * Attach the sub window to the content view.
- */
- void attachSubWindow(ViewGroup content) {
- if (mSubView != null) {
- content.addView(mSubViewContainer,
- BrowserActivity.COVER_SCREEN_PARAMS);
- }
- }
-
- /**
- * Remove the sub window from the content view.
- */
- void removeSubWindow(ViewGroup content) {
- if (mSubView != null) {
- content.removeView(mSubViewContainer);
- mActivity.closeDialogs();
- }
- }
-
- /**
- * This method attaches both the WebView and any sub window to the
- * given content view.
- */
- void attachTabToContentView(ViewGroup content) {
- if (mMainView == null) {
- return;
- }
-
- // Attach the WebView to the container and then attach the
- // container to the content view.
- FrameLayout wrapper =
- (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
- ViewGroup parent = (ViewGroup) mMainView.getParent();
- if (parent != wrapper) {
- if (parent != null) {
- Log.w(LOGTAG, "mMainView already has a parent in"
- + " attachTabToContentView!");
- parent.removeView(mMainView);
- }
- wrapper.addView(mMainView);
- } else {
- Log.w(LOGTAG, "mMainView is already attached to wrapper in"
- + " attachTabToContentView!");
- }
- parent = (ViewGroup) mContainer.getParent();
- if (parent != content) {
- if (parent != null) {
- Log.w(LOGTAG, "mContainer already has a parent in"
- + " attachTabToContentView!");
- parent.removeView(mContainer);
- }
- content.addView(mContainer, BrowserActivity.COVER_SCREEN_PARAMS);
- } else {
- Log.w(LOGTAG, "mContainer is already attached to content in"
- + " attachTabToContentView!");
- }
- attachSubWindow(content);
- }
-
- /**
- * Remove the WebView and any sub window from the given content view.
- */
- void removeTabFromContentView(ViewGroup content) {
- if (mMainView == null) {
- return;
- }
-
- // Remove the container from the content and then remove the
- // WebView from the container. This will trigger a focus change
- // needed by WebView.
- FrameLayout wrapper =
- (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
- wrapper.removeView(mMainView);
- content.removeView(mContainer);
- mActivity.closeDialogs();
- removeSubWindow(content);
- }
/**
* Set the parent tab of this tab.
@@ -1598,7 +1360,7 @@ class Tab {
if (parent == null) {
mSavedState.remove(PARENTTAB);
} else {
- mSavedState.putInt(PARENTTAB, mActivity.getTabControl()
+ mSavedState.putInt(PARENTTAB, mWebViewController.getTabControl()
.getTabIndex(parent));
}
}
@@ -1661,6 +1423,10 @@ class Tab {
}
}
+ boolean inForeground() {
+ return mInForeground;
+ }
+
/**
* Return the top window of this tab; either the subwindow if it is not
* null or the main window.
@@ -1683,6 +1449,23 @@ class Tab {
return mMainView;
}
+ View getViewContainer() {
+ return mContainer;
+ }
+
+ /**
+ * Return whether private browsing is enabled for the main window of
+ * this tab.
+ * @return True if private browsing is enabled.
+ */
+ boolean isPrivateBrowsingEnabled() {
+ WebView webView = getWebView();
+ if (webView == null) {
+ return false;
+ }
+ return webView.isPrivateBrowsingEnabled();
+ }
+
/**
* Return the subwindow of this tab or null if there is no subwindow.
* @return The subwindow of this tab or null.
@@ -1691,6 +1474,18 @@ class Tab {
return mSubView;
}
+ void setSubWebView(WebView subView) {
+ mSubView = subView;
+ }
+
+ View getSubViewContainer() {
+ return mSubViewContainer;
+ }
+
+ void setSubViewContainer(View subViewContainer) {
+ mSubViewContainer = subViewContainer;
+ }
+
/**
* @return The geolocation permissions prompt for this tab.
*/
@@ -1735,6 +1530,28 @@ class Tab {
}
/**
+ * set the title for the tab
+ */
+ void setCurrentTitle(String title) {
+ mCurrentTitle = title;
+ }
+
+ /**
+ * set url for this tab
+ * @param url
+ */
+ void setCurrentUrl(String url) {
+ mCurrentUrl = url;
+ }
+
+ String getCurrentTitle() {
+ return mCurrentTitle;
+ }
+
+ String getCurrentUrl() {
+ return mCurrentUrl;
+ }
+ /**
* Get the url of this tab. Valid after calling populatePickerData, but
* before calling wipePickerData, or if the webview has been destroyed.
* @return The WebView's url or null.
@@ -1771,6 +1588,7 @@ class Tab {
return null;
}
+
/**
* Return the tab's error console. Creates the console if createIfNEcessary
* is true and we haven't already created the console.
@@ -1811,9 +1629,9 @@ class Tab {
*/
void resetLockIcon(String url) {
mPrevLockIconType = mLockIconType;
- mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
+ mLockIconType = LOCK_ICON_UNSECURE;
if (URLUtil.isHttpsUrl(url)) {
- mLockIconType = BrowserActivity.LOCK_ICON_SECURE;
+ mLockIconType = LOCK_ICON_SECURE;
}
}
@@ -1836,14 +1654,14 @@ class Tab {
* @return TRUE if onPageStarted is called while onPageFinished is not
* called yet.
*/
- boolean inLoad() {
- return mInLoad;
+ boolean inPageLoad() {
+ return mInPageLoad;
}
// force mInLoad to be false. This should only be called before closing the
// tab to ensure BrowserActivity's pauseWebViewTimers() is called correctly.
- void clearInLoad() {
- mInLoad = false;
+ void clearInPageLoad() {
+ mInPageLoad = false;
}
void populatePickerData() {
@@ -1855,6 +1673,9 @@ class Tab {
// FIXME: The only place we cared about subwindow was for
// bookmarking (i.e. not when saving state). Was this deliberate?
final WebBackForwardList list = mMainView.copyBackForwardList();
+ if (list == null) {
+ Log.w(LOGTAG, "populatePickerData called and WebBackForwardList is null");
+ }
final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
populatePickerData(item);
}
@@ -1863,7 +1684,9 @@ class Tab {
// WebView.
private void populatePickerData(WebHistoryItem item) {
mPickerData = new PickerData();
- if (item != null) {
+ if (item == null) {
+ Log.w(LOGTAG, "populatePickerData called with a null WebHistoryItem");
+ } else {
mPickerData.mUrl = item.getUrl();
mPickerData.mTitle = item.getTitle();
mPickerData.mFavicon = item.getFavicon();
@@ -1934,7 +1757,7 @@ class Tab {
}
// Remember the parent tab so the relationship can be restored.
if (mParentTab != null) {
- mSavedState.putInt(PARENTTAB, mActivity.getTabControl().getTabIndex(
+ mSavedState.putInt(PARENTTAB, mWebViewController.getTabControl().getTabIndex(
mParentTab));
}
return true;
@@ -1962,35 +1785,4 @@ class Tab {
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/TabBar.java b/src/com/android/browser/TabBar.java
new file mode 100644
index 000000000..69e0bd2a1
--- /dev/null
+++ b/src/com/android/browser/TabBar.java
@@ -0,0 +1,472 @@
+/*
+ * 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 com.android.browser.ScrollWebView.ScrollListener;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.PaintDrawable;
+import android.view.ContextMenu;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.webkit.WebView;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * tabbed title bar for xlarge screen browser
+ */
+public class TabBar extends LinearLayout
+ implements ScrollListener, OnClickListener {
+
+ private static final int PROGRESS_MAX = 100;
+
+ private Activity mActivity;
+ private UiController mUiController;
+ private TabControl mTabControl;
+ private BaseUi mUi;
+
+ private final int mTabWidthSelected;
+ private final int mTabWidthUnselected;
+
+ private TabScrollView mTabs;
+
+ private ImageButton mNewTab;
+ private int mButtonWidth;
+
+ private Map<Tab, TabViewData> mTabMap;
+
+ private boolean mUserRequestedUrlbar;
+ private boolean mTitleVisible;
+ private boolean mShowUrlMode;
+ private boolean mHasReceivedTitle;
+
+ private Drawable mGenericFavicon;
+ private String mLoadingText;
+
+ public TabBar(Activity activity, UiController controller, BaseUi ui) {
+ super(activity);
+ mActivity = activity;
+ mUiController = controller;
+ mTabControl = mUiController.getTabControl();
+ mUi = ui;
+ Resources res = activity.getResources();
+ mTabWidthSelected = (int) res.getDimension(R.dimen.tab_width_selected);
+ mTabWidthUnselected = (int) res.getDimension(R.dimen.tab_width_unselected);
+
+ mTabMap = new HashMap<Tab, TabViewData>();
+ Resources resources = activity.getResources();
+ LayoutInflater factory = LayoutInflater.from(activity);
+ factory.inflate(R.layout.tab_bar, this);
+ mTabs = (TabScrollView) findViewById(R.id.tabs);
+ mNewTab = (ImageButton) findViewById(R.id.newtab);
+ mNewTab.setOnClickListener(this);
+ mGenericFavicon = res.getDrawable(R.drawable.app_web_browser_sm);
+ mLoadingText = res.getString(R.string.title_bar_loading);
+
+ // TODO: Change enabled states based on whether you can go
+ // back/forward. Probably should be done inside onPageStarted.
+
+ updateTabs(mUiController.getTabs());
+
+ mUserRequestedUrlbar = false;
+ mTitleVisible = true;
+ mButtonWidth = -1;
+ }
+
+ void updateTabs(List<Tab> tabs) {
+ mTabs.clearTabs();
+ mTabMap.clear();
+ for (Tab tab : tabs) {
+ TabViewData data = buildTab(tab);
+ TabView tv = buildView(data);
+ }
+ mTabs.setSelectedTab(mTabControl.getCurrentIndex());
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mButtonWidth == -1) {
+ mButtonWidth = mNewTab.getMeasuredWidth();
+ }
+ int sw = mTabs.getMeasuredWidth();
+ int w = right-left;
+ if (w-sw < mButtonWidth) {
+ sw = w - mButtonWidth;
+ }
+ mTabs.layout(0, 0, sw, bottom-top );
+ mNewTab.layout(sw, 0, sw+mButtonWidth, bottom-top);
+ }
+
+ public void onClick(View view) {
+ mUi.hideComboView();
+ if (mNewTab == view) {
+ mUiController.openTabToHomePage();
+ } else if (mTabs.getSelectedTab() == view) {
+ if (mUi.isFakeTitleBarShowing() && !isLoading()) {
+ mUi.hideFakeTitleBar();
+ } else {
+ showUrlBar();
+ }
+ } else {
+ int ix = mTabs.getChildIndex(view);
+ if (ix >= 0) {
+ mTabs.setSelectedTab(ix);
+ mUiController.switchToTab(ix);
+ }
+ }
+ }
+
+ private void showUrlBar() {
+ mUi.stopWebViewScrolling();
+ mUi.showFakeTitleBar();
+ mUserRequestedUrlbar = true;
+ }
+
+ private void setShowUrlMode(boolean showUrl) {
+ mShowUrlMode = showUrl;
+ }
+
+ // callback after fake titlebar is shown
+ void onShowTitleBar() {
+ setShowUrlMode(false);
+ }
+
+ // callback after fake titlebar is hidden
+ void onHideTitleBar() {
+ setShowUrlMode(!mTitleVisible);
+ Tab tab = mTabControl.getCurrentTab();
+ tab.getWebView().requestFocus();
+ mUserRequestedUrlbar = false;
+ }
+
+ // webview scroll listener
+
+ @Override
+ public void onScroll(boolean titleVisible) {
+ // isLoading is using the current tab, which initially might not be set yet
+ if (mTabControl.getCurrentTab() != null) {
+ mTitleVisible = titleVisible;
+ if (!mShowUrlMode && !mTitleVisible && !isLoading()) {
+ if (mUserRequestedUrlbar) {
+ mUi.hideFakeTitleBar();
+ } else {
+ setShowUrlMode(true);
+ }
+ } else if (mTitleVisible && !isLoading()) {
+ if (mShowUrlMode) {
+ setShowUrlMode(false);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void createContextMenu(ContextMenu menu) {
+ MenuInflater inflater = mActivity.getMenuInflater();
+ inflater.inflate(R.menu.title_context, menu);
+ mActivity.onCreateContextMenu(menu, this, null);
+ }
+
+ private TabViewData buildTab(Tab tab) {
+ TabViewData data = new TabViewData(tab);
+ mTabMap.put(tab, data);
+ return data;
+ }
+
+ private TabView buildView(final TabViewData data) {
+ TabView tv = new TabView(mActivity, data);
+ tv.setTag(data);
+ tv.setOnClickListener(this);
+ mTabs.addTab(tv);
+ return tv;
+ }
+
+ /**
+ * View used in the tab bar
+ */
+ class TabView extends LinearLayout implements OnClickListener {
+
+ TabViewData mTabData;
+ View mTabContent;
+ TextView mTitle;
+ View mIncognito;
+ ImageView mIconView;
+ ImageView mLock;
+ ImageView mClose;
+ boolean mSelected;
+ boolean mInLoad;
+
+ /**
+ * @param context
+ */
+ public TabView(Context context, TabViewData tab) {
+ super(context);
+ mTabData = tab;
+ setGravity(Gravity.CENTER_VERTICAL);
+ setOrientation(LinearLayout.HORIZONTAL);
+ setBackgroundResource(R.drawable.tab_background);
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ mTabContent = inflater.inflate(R.layout.tab_title, this, true);
+ mTitle = (TextView) mTabContent.findViewById(R.id.title);
+ mIconView = (ImageView) mTabContent.findViewById(R.id.favicon);
+ mLock = (ImageView) mTabContent.findViewById(R.id.lock);
+ mClose = (ImageView) mTabContent.findViewById(R.id.close);
+ mClose.setOnClickListener(this);
+ mIncognito = mTabContent.findViewById(R.id.incognito);
+ mSelected = false;
+ mInLoad = false;
+ // update the status
+ updateFromData();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mClose) {
+ closeTab();
+ }
+ }
+
+ private void updateFromData() {
+ mTabData.mTabView = this;
+ if (mTabData.mUrl != null) {
+ setDisplayTitle(mTabData.mUrl);
+ }
+ if (mTabData.mTitle != null) {
+ setDisplayTitle(mTabData.mTitle);
+ }
+ setProgress(mTabData.mProgress);
+ if (mTabData.mIcon != null) {
+ setFavicon(mTabData.mIcon);
+ }
+ if (mTabData.mLock != null) {
+ setLock(mTabData.mLock);
+ }
+ if (mTabData.mTab != null) {
+ mIncognito.setVisibility(
+ mTabData.mTab.isPrivateBrowsingEnabled() ?
+ View.VISIBLE : View.GONE);
+ }
+ }
+
+ @Override
+ public void setActivated(boolean selected) {
+ mSelected = selected;
+ mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE);
+ mTitle.setTextAppearance(mActivity, mSelected ?
+ R.style.TabTitleSelected : R.style.TabTitleUnselected);
+ setHorizontalFadingEdgeEnabled(!mSelected);
+ setFadingEdgeLength(50);
+ super.setActivated(selected);
+ setLayoutParams(new LayoutParams(selected ?
+ mTabWidthSelected : mTabWidthUnselected,
+ LayoutParams.MATCH_PARENT));
+ }
+
+ void setDisplayTitle(String title) {
+ mTitle.setText(title);
+ }
+
+ void setFavicon(Drawable d) {
+ mIconView.setImageDrawable(d);
+ }
+
+ void setLock(Drawable d) {
+ if (null == d) {
+ mLock.setVisibility(View.GONE);
+ } else {
+ mLock.setImageDrawable(d);
+ mLock.setVisibility(View.VISIBLE);
+ }
+ }
+
+ void setProgress(int newProgress) {
+ if (newProgress >= PROGRESS_MAX) {
+ mInLoad = false;
+ } else {
+ if (!mInLoad && getWindowToken() != null) {
+ mInLoad = true;
+ }
+ }
+ }
+
+ private void closeTab() {
+ if (mTabData.mTab == mTabControl.getCurrentTab()) {
+ mUiController.closeCurrentTab();
+ } else {
+ mUiController.closeTab(mTabData.mTab);
+ }
+ }
+
+ }
+
+ /**
+ * Store tab state within the title bar
+ */
+ class TabViewData {
+
+ Tab mTab;
+ TabView mTabView;
+ int mProgress;
+ Drawable mIcon;
+ Drawable mLock;
+ String mTitle;
+ String mUrl;
+
+ TabViewData(Tab tab) {
+ mTab = tab;
+ WebView web = tab.getWebView();
+ if (web != null) {
+ setUrlAndTitle(web.getUrl(), web.getTitle());
+ }
+ }
+
+ void setUrlAndTitle(String url, String title) {
+ mUrl = url;
+ mTitle = title;
+ if (mTabView != null) {
+ if (title != null) {
+ mTabView.setDisplayTitle(title);
+ } else if (url != null) {
+ mTabView.setDisplayTitle(UrlUtils.stripUrl(url));
+ }
+ }
+ }
+
+ void setProgress(int newProgress) {
+ mProgress = newProgress;
+ if (mTabView != null) {
+ mTabView.setProgress(mProgress);
+ }
+ }
+
+ void setFavicon(Bitmap icon) {
+ Drawable[] array = new Drawable[3];
+ array[0] = new PaintDrawable(Color.BLACK);
+ array[1] = new PaintDrawable(Color.WHITE);
+ if (icon == null) {
+ array[2] = mGenericFavicon;
+ } else {
+ array[2] = new BitmapDrawable(icon);
+ }
+ LayerDrawable d = new LayerDrawable(array);
+ d.setLayerInset(1, 1, 1, 1, 1);
+ d.setLayerInset(2, 2, 2, 2, 2);
+ mIcon = d;
+ if (mTabView != null) {
+ mTabView.setFavicon(mIcon);
+ }
+ }
+
+ }
+
+ // TabChangeListener implementation
+
+ public void onSetActiveTab(Tab tab) {
+ mTabs.setSelectedTab(mTabControl.getTabIndex(tab));
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setProgress(tvd.mProgress);
+ // update the scroll state
+ WebView webview = tab.getWebView();
+ onScroll(webview.getVisibleTitleHeight() > 0);
+ }
+ }
+
+ public void onFavicon(Tab tab, Bitmap favicon) {
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setFavicon(favicon);
+ }
+ }
+
+ public void onNewTab(Tab tab) {
+ TabViewData tvd = buildTab(tab);
+ buildView(tvd);
+ }
+
+ public void onProgress(Tab tab, int progress) {
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setProgress(progress);
+ }
+ }
+
+ public void onRemoveTab(Tab tab) {
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ TabView tv = tvd.mTabView;
+ if (tv != null) {
+ mTabs.removeTab(tv);
+ }
+ }
+ mTabMap.remove(tab);
+ }
+
+ public void onUrlAndTitle(Tab tab, String url, String title) {
+ mHasReceivedTitle = true;
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setUrlAndTitle(url, title);
+ }
+ }
+
+ public void onPageFinished(Tab tab) {
+ if (!mHasReceivedTitle) {
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setUrlAndTitle(tvd.mUrl, null);
+ }
+ }
+ }
+
+ public void onPageStarted(Tab tab, String url, Bitmap favicon) {
+ mHasReceivedTitle = false;
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setUrlAndTitle(url, null);
+ tvd.setFavicon(favicon);
+ tvd.setUrlAndTitle(url, mLoadingText);
+ }
+ }
+
+ private boolean isLoading() {
+ TabViewData tvd = mTabMap.get(mTabControl.getCurrentTab());
+ if ((tvd != null) && (tvd.mTabView != null)) {
+ return tvd.mTabView.mInLoad;
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index afd4ea827..2d90d2317 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -16,57 +16,51 @@
package com.android.browser;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapShader;
-import android.graphics.Paint;
-import android.graphics.Shader;
+import com.android.browser.IntentHandler.UrlData;
+
import android.os.Bundle;
import android.util.Log;
-import android.view.View;
import android.webkit.WebBackForwardList;
import android.webkit.WebView;
import java.io.File;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import java.util.Vector;
class TabControl {
// Log Tag
private static final String LOGTAG = "TabControl";
// Maximum number of tabs.
- private static final int MAX_TABS = 8;
+ private int mMaxTabs;
// Private array of WebViews that are used as tabs.
- private ArrayList<Tab> mTabs = new ArrayList<Tab>(MAX_TABS);
+ private ArrayList<Tab> mTabs;
// Queue of most recently viewed tabs.
- private ArrayList<Tab> mTabQueue = new ArrayList<Tab>(MAX_TABS);
+ private ArrayList<Tab> mTabQueue;
// Current position in mTabs.
private int mCurrentTab = -1;
- // A private instance of BrowserActivity to interface with when adding and
- // switching between tabs.
- private final BrowserActivity mActivity;
- // Directory to store thumbnails for each WebView.
+ // the main browser controller
+ private final Controller mController;
+
private final File mThumbnailDir;
/**
- * Construct a new TabControl object that interfaces with the given
- * BrowserActivity instance.
- * @param activity A BrowserActivity instance that TabControl will interface
- * with.
+ * Construct a new TabControl object
*/
- TabControl(BrowserActivity activity) {
- mActivity = activity;
- mThumbnailDir = activity.getDir("thumbnails", 0);
+ TabControl(Controller controller) {
+ mController = controller;
+ mThumbnailDir = mController.getActivity()
+ .getDir("thumbnails", 0);
+ mMaxTabs = mController.getMaxTabs();
+ mTabs = new ArrayList<Tab>(mMaxTabs);
+ mTabQueue = new ArrayList<Tab>(mMaxTabs);
}
File getThumbnailDir() {
return mThumbnailDir;
}
- BrowserActivity getBrowserActivity() {
- return mActivity;
- }
-
/**
* Return the current tab's main WebView. This will always return the main
* WebView for a given tab and not a subwindow.
@@ -106,6 +100,13 @@ class TabControl {
}
/**
+ * return the list of tabs
+ */
+ List<Tab> getTabs() {
+ return mTabs;
+ }
+
+ /**
* Return the tab at the specified index.
* @return The Tab for the specified index or null if the tab does not
* exist.
@@ -132,7 +133,7 @@ class TabControl {
int getCurrentIndex() {
return mCurrentTab;
}
-
+
/**
* Given a Tab, find it's index
* @param Tab to find
@@ -146,7 +147,20 @@ class TabControl {
}
boolean canCreateNewTab() {
- return MAX_TABS != mTabs.size();
+ return mMaxTabs != mTabs.size();
+ }
+
+ /**
+ * Returns true if there are any incognito tabs open.
+ * @return True when any incognito tabs are open, false otherwise.
+ */
+ boolean hasAnyOpenIncognitoTabs() {
+ for (Tab tab : mTabs) {
+ if (tab.getWebView() != null && tab.getWebView().isPrivateBrowsingEnabled()) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -154,16 +168,17 @@ class TabControl {
* @return The newly createTab or null if we have reached the maximum
* number of open tabs.
*/
- Tab createNewTab(boolean closeOnExit, String appId, String url) {
+ Tab createNewTab(boolean closeOnExit, String appId, String url,
+ boolean privateBrowsing) {
int size = mTabs.size();
// Return false if we have maxed out on tabs
- if (MAX_TABS == size) {
+ if (mMaxTabs == size) {
return null;
}
- final WebView w = createNewWebView();
+ final WebView w = createNewWebView(privateBrowsing);
// Create a new tab and add it to the tab list
- Tab t = new Tab(mActivity, w, closeOnExit, appId, url);
+ Tab t = new Tab(mController, w, closeOnExit, appId, url);
mTabs.add(t);
// Initially put the tab in the background.
t.putInBackground();
@@ -172,10 +187,10 @@ class TabControl {
/**
* Create a new tab with default values for closeOnExit(false),
- * appId(null), and url(null).
+ * appId(null), url(null), and privateBrowsing(false).
*/
Tab createNewTab() {
- return createNewTab(false, null, null);
+ return createNewTab(false, null, null, false);
}
/**
@@ -274,32 +289,65 @@ class TabControl {
/**
* Restore the state of all the tabs.
* @param inState The saved state of all the tabs.
+ * @param restoreIncognitoTabs Restoring private browsing tabs
+ * @param restoreAll All webviews get restored, not just the current tab
+ * (this does not override handling of incognito tabs)
* @return True if there were previous tabs that were restored. False if
* there was no saved state or restoring the state failed.
*/
- boolean restoreState(Bundle inState) {
+ boolean restoreState(Bundle inState, boolean restoreIncognitoTabs,
+ boolean restoreAll) {
final int numTabs = (inState == null)
? -1 : inState.getInt(Tab.NUMTABS, -1);
if (numTabs == -1) {
return false;
} else {
- final int currentTab = inState.getInt(Tab.CURRTAB, -1);
+ final int oldCurrentTab = inState.getInt(Tab.CURRTAB, -1);
+
+ // Determine whether the saved current tab can be restored, and
+ // if not, which tab will take its place.
+ int currentTab = -1;
+ if (restoreIncognitoTabs
+ || !inState.getBundle(Tab.WEBVIEW + oldCurrentTab).getBoolean(Tab.INCOGNITO)) {
+ currentTab = oldCurrentTab;
+ } else {
+ for (int i = 0; i < numTabs; i++) {
+ if (!inState.getBundle(Tab.WEBVIEW + i).getBoolean(Tab.INCOGNITO)) {
+ currentTab = i;
+ break;
+ }
+ }
+ }
+ if (currentTab < 0) {
+ return false;
+ }
+
+ // Map saved tab indices to new indices, in case any incognito tabs
+ // need to not be restored.
+ HashMap<Integer, Integer> originalTabIndices = new HashMap<Integer, Integer>();
+ originalTabIndices.put(-1, -1);
for (int i = 0; i < numTabs; i++) {
- if (i == currentTab) {
+ Bundle state = inState.getBundle(Tab.WEBVIEW + i);
+
+ if (!restoreIncognitoTabs && state != null && state.getBoolean(Tab.INCOGNITO)) {
+ originalTabIndices.put(i, -1);
+ } else if (i == currentTab || restoreAll) {
Tab t = createNewTab();
// Me must set the current tab before restoring the state
// so that all the client classes are set.
- setCurrentTab(t);
- if (!t.restoreState(inState.getBundle(Tab.WEBVIEW + i))) {
+ if (i == currentTab) {
+ setCurrentTab(t);
+ }
+ if (!t.restoreState(state)) {
Log.w(LOGTAG, "Fail in restoreState, load home page.");
t.getWebView().loadUrl(BrowserSettings.getInstance()
.getHomePage());
}
+ originalTabIndices.put(i, getTabCount() - 1);
} else {
// Create a new tab and don't restore the state yet, add it
// to the tab list
- Tab t = new Tab(mActivity, null, false, null, null);
- Bundle state = inState.getBundle(Tab.WEBVIEW + i);
+ Tab t = new Tab(mController, null, false, null, null);
if (state != null) {
t.setSavedState(state);
t.populatePickerDataFromSavedState();
@@ -311,15 +359,17 @@ class TabControl {
mTabs.add(t);
// added the tab to the front as they are not current
mTabQueue.add(0, t);
+ originalTabIndices.put(i, getTabCount() - 1);
}
}
+
// Rebuild the tree of tabs. Do this after all tabs have been
// created/restored so that the parent tab exists.
for (int i = 0; i < numTabs; i++) {
final Bundle b = inState.getBundle(Tab.WEBVIEW + i);
final Tab t = getTab(i);
if (b != null && t != null) {
- final int parentIndex = b.getInt(Tab.PARENTTAB, -1);
+ final Integer parentIndex = originalTabIndices.get(b.getInt(Tab.PARENTTAB, -1));
if (parentIndex != -1) {
final Tab parent = getTab(parentIndex);
if (parent != null) {
@@ -491,7 +541,7 @@ class TabControl {
* requires a load, whether it was due to the fact that it was deleted, or
* it is because it was a voice search.
*/
- boolean recreateWebView(Tab t, BrowserActivity.UrlData urlData) {
+ boolean recreateWebView(Tab t, UrlData urlData) {
final String url = urlData.mUrl;
final WebView w = t.getWebView();
if (w != null) {
@@ -529,30 +579,16 @@ class TabControl {
* Creates a new WebView and registers it with the global settings.
*/
private WebView createNewWebView() {
- // Create a new WebView
- WebView w = new WebView(mActivity);
- w.setScrollbarFadingEnabled(true);
- w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
- w.setMapTrackballToArrowKeys(false); // use trackball directly
- // Enable the built-in zoom
- w.getSettings().setBuiltInZoomControls(true);
- // Add this WebView to the settings observer list and update the
- // settings
- final BrowserSettings s = BrowserSettings.getInstance();
- s.addObserver(w.getSettings()).update(s, null);
-
- // pick a default
- if (false) {
- MeshTracker mt = new MeshTracker(2);
- Paint paint = new Paint();
- Bitmap bm = BitmapFactory.decodeResource(mActivity.getResources(),
- R.drawable.pattern_carbon_fiber_dark);
- paint.setShader(new BitmapShader(bm, Shader.TileMode.REPEAT,
- Shader.TileMode.REPEAT));
- mt.setBGPaint(paint);
- w.setDragTracker(mt);
- }
- return w;
+ return createNewWebView(false);
+ }
+
+ /**
+ * Creates a new WebView and registers it with the global settings.
+ * @param privateBrowsing When true, enables private browsing in the new
+ * WebView.
+ */
+ private WebView createNewWebView(boolean privateBrowsing) {
+ return mController.getWebViewFactory().createWebView(privateBrowsing);
}
/**
@@ -619,4 +655,5 @@ class TabControl {
}
return true;
}
+
}
diff --git a/src/com/android/browser/TabScrollView.java b/src/com/android/browser/TabScrollView.java
new file mode 100644
index 000000000..fbb40aa9c
--- /dev/null
+++ b/src/com/android/browser/TabScrollView.java
@@ -0,0 +1,214 @@
+/*
+ * 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.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+
+/**
+ * custom view for displaying tabs in the tabbed title bar
+ */
+public class TabScrollView extends HorizontalScrollView {
+
+ private Context mContext;
+ private LinearLayout mContentView;
+ private int mSelected;
+ private Drawable mArrowLeft;
+ private Drawable mArrowRight;
+ private int mAnimationDuration;
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public TabScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public TabScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /**
+ * @param context
+ */
+ public TabScrollView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ mContext = ctx;
+ mAnimationDuration = ctx.getResources().getInteger(
+ R.integer.tab_animation_duration);
+ setHorizontalScrollBarEnabled(false);
+ mContentView = new LinearLayout(mContext);
+ mContentView.setOrientation(LinearLayout.HORIZONTAL);
+ mContentView.setLayoutParams(
+ new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
+ addView(mContentView);
+ mSelected = -1;
+ mArrowLeft = ctx.getResources().getDrawable(R.drawable.ic_arrow_left);
+ mArrowRight = ctx.getResources().getDrawable(R.drawable.ic_arrow_right);
+ // prevent ProGuard from removing the property methods
+ setScroll(getScroll());
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ ensureChildVisible(getSelectedTab());
+ }
+
+ void setSelectedTab(int position) {
+ View v = getSelectedTab();
+ if (v != null) {
+ v.setActivated(false);
+ }
+ mSelected = position;
+ v = getSelectedTab();
+ if (v != null) {
+ v.setActivated(true);
+ }
+ requestLayout();
+ }
+
+ int getChildIndex(View v) {
+ return mContentView.indexOfChild(v);
+ }
+
+ View getSelectedTab() {
+ if ((mSelected >= 0) && (mSelected < mContentView.getChildCount())) {
+ return mContentView.getChildAt(mSelected);
+ } else {
+ return null;
+ }
+ }
+
+ void clearTabs() {
+ mContentView.removeAllViews();
+ }
+
+ void addTab(View tab) {
+ mContentView.addView(tab);
+ animateIn(tab);
+ tab.setActivated(false);
+ }
+
+ void removeTab(View tab) {
+ int ix = mContentView.indexOfChild(tab);
+ if (ix == mSelected) {
+ mSelected = -1;
+ } else if (ix < mSelected) {
+ mSelected--;
+ }
+ animateOut(tab);
+ }
+
+ private void ensureChildVisible(View child) {
+ if (child != null) {
+ int childl = child.getLeft();
+ int childr = childl + child.getWidth();
+ int viewl = getScrollX();
+ int viewr = viewl + getWidth();
+ if (childl < viewl) {
+ // need scrolling to left
+ animateScroll(childl);
+ } else if (childr > viewr) {
+ // need scrolling to right
+ animateScroll(childr - viewr + viewl);
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ int l = getScrollX();
+ int r = l + getWidth();
+ int dis = 8;
+ if (l > 0) {
+ int aw = mArrowLeft.getIntrinsicWidth();
+ mArrowLeft.setBounds(l + dis, 0, l + dis + aw, getHeight());
+ mArrowLeft.draw(canvas);
+ }
+ if (r < mContentView.getWidth()) {
+ int aw = mArrowRight.getIntrinsicWidth();
+ mArrowRight.setBounds(r - dis - aw, 0, r - dis, getHeight());
+ mArrowRight.draw(canvas);
+ }
+ }
+
+ private void animateIn(View tab) {
+ ObjectAnimator animator = ObjectAnimator.ofInt(tab, "TranslationX", 500, 0);
+ animator.setDuration(mAnimationDuration);
+ animator.start();
+ }
+
+ private void animateOut(final View tab) {
+ ObjectAnimator animator = ObjectAnimator.ofInt(
+ tab, "TranslationX", 0, getScrollX() - tab.getRight());
+ animator.setDuration(mAnimationDuration);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mContentView.removeView(tab);
+ }
+ });
+ animator.setInterpolator(new AccelerateInterpolator());
+ animator.start();
+ }
+
+ private void animateScroll(int newscroll) {
+ ObjectAnimator animator = ObjectAnimator.ofInt(this, "scroll", getScrollX(), newscroll);
+ animator.setDuration(mAnimationDuration);
+ animator.start();
+ }
+
+ /**
+ * required for animation
+ */
+ public void setScroll(int newscroll) {
+ scrollTo(newscroll, getScrollY());
+ }
+
+ /**
+ * required for animation
+ */
+ public int getScroll() {
+ return getScrollX();
+ }
+
+}
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
index dc4979bd3..6dabd76f2 100644
--- a/src/com/android/browser/TitleBar.java
+++ b/src/com/android/browser/TitleBar.java
@@ -16,19 +16,15 @@
package com.android.browser;
-import android.content.Context;
+import com.android.common.speech.LoggingEvents;
+
+import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Rect;
import android.graphics.drawable.Animatable;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.PaintDrawable;
import android.os.Handler;
import android.os.Message;
import android.speech.RecognizerIntent;
@@ -45,50 +41,45 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
-import com.android.common.speech.LoggingEvents;
-
/**
* This class represents a title bar for a particular "tab" or "window" in the
* browser.
*/
-public class TitleBar extends LinearLayout {
- private TextView mTitle;
- private Drawable mCloseDrawable;
- private ImageView mRtButton;
- private Drawable mCircularProgress;
- private ProgressBar mHorizontalProgress;
- private ImageView mFavicon;
- private ImageView mLockIcon;
- private ImageView mStopButton;
- private Drawable mBookmarkDrawable;
- private Drawable mVoiceDrawable;
- private boolean mInLoad;
- private BrowserActivity mBrowserActivity;
- private Drawable mGenericFavicon;
- private int mIconDimension;
- private View mTitleBg;
- private MyHandler mHandler;
- private Intent mVoiceSearchIntent;
- private boolean mInVoiceMode;
- private Drawable mVoiceModeBackground;
- private Drawable mNormalBackground;
- private Drawable mLoadingBackground;
- private ImageSpan mArcsSpan;
- private int mLeftMargin;
- private int mRightMargin;
+public class TitleBar extends TitleBarBase {
+
+ private Activity mActivity;
+ private UiController mController;
+ private TextView mTitle;
+ private ImageView mRtButton;
+ private Drawable mCircularProgress;
+ private ProgressBar mHorizontalProgress;
+ private ImageView mStopButton;
+ private Drawable mBookmarkDrawable;
+ private Drawable mVoiceDrawable;
+ private boolean mInLoad;
+ private View mTitleBg;
+ private MyHandler mHandler;
+ private Intent mVoiceSearchIntent;
+ private boolean mInVoiceMode;
+ private Drawable mVoiceModeBackground;
+ private Drawable mNormalBackground;
+ private Drawable mLoadingBackground;
+ private ImageSpan mArcsSpan;
+ private int mLeftMargin;
+ private int mRightMargin;
private static int LONG_PRESS = 1;
- public TitleBar(BrowserActivity context) {
- super(context, null);
+ public TitleBar(Activity activity, UiController controller) {
+ super(activity);
mHandler = new MyHandler();
- LayoutInflater factory = LayoutInflater.from(context);
+ LayoutInflater factory = LayoutInflater.from(activity);
factory.inflate(R.layout.title_bar, this);
- mBrowserActivity = context;
+ mActivity = activity;
+ mController = controller;
mTitle = (TextView) findViewById(R.id.title);
mTitle.setCompoundDrawablePadding(5);
@@ -99,21 +90,19 @@ public class TitleBar extends LinearLayout {
mStopButton = (ImageView) findViewById(R.id.stop);
mRtButton = (ImageView) findViewById(R.id.rt_btn);
- Resources resources = context.getResources();
- mCircularProgress = (Drawable) resources.getDrawable(
+ Resources resources = activity.getResources();
+ mCircularProgress = resources.getDrawable(
com.android.internal.R.drawable.search_spinner);
DisplayMetrics metrics = resources.getDisplayMetrics();
mLeftMargin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 8f, metrics);
mRightMargin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 6f, metrics);
- mIconDimension = (int) TypedValue.applyDimension(
+ int iconDimension = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 20f, metrics);
- mCircularProgress.setBounds(0, 0, mIconDimension, mIconDimension);
+ mCircularProgress.setBounds(0, 0, iconDimension, iconDimension);
mHorizontalProgress = (ProgressBar) findViewById(
R.id.progress_horizontal);
- mGenericFavicon = context.getResources().getDrawable(
- R.drawable.app_web_browser_sm);
mVoiceSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
mVoiceSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
@@ -121,8 +110,9 @@ public class TitleBar extends LinearLayout {
// results intent - http://b/2546173
//
// TODO: Make a constant for this extra.
- mVoiceSearchIntent.putExtra("android.speech.extras.SEND_APPLICATION_ID_EXTRA", false);
- PackageManager pm = context.getPackageManager();
+ mVoiceSearchIntent.putExtra("android.speech.extras.SEND_APPLICATION_ID_EXTRA",
+ false);
+ PackageManager pm = activity.getPackageManager();
ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
PackageManager.MATCH_DEFAULT_ONLY);
if (ri == null) {
@@ -136,11 +126,12 @@ public class TitleBar extends LinearLayout {
R.drawable.title_voice);
mNormalBackground = mTitleBg.getBackground();
mLoadingBackground = resources.getDrawable(R.drawable.title_loading);
- mArcsSpan = new ImageSpan(context, R.drawable.arcs,
+ mArcsSpan = new ImageSpan(activity, R.drawable.arcs,
ImageSpan.ALIGN_BASELINE);
}
private class MyHandler extends Handler {
+ @Override
public void handleMessage(Message msg) {
if (msg.what == LONG_PRESS) {
// Prevent the normal action from happening by setting the title
@@ -149,16 +140,20 @@ public class TitleBar extends LinearLayout {
// Need to call a special method on BrowserActivity for when the
// fake title bar is up, because its ViewGroup does not show a
// context menu.
- mBrowserActivity.showTitleBarContextMenu();
+ // TODO:
+ // this test is not valid for all UIs; fix later
+ if (getParent() != null) {
+ mActivity.openContextMenu(TitleBar.this);
+ }
}
}
};
@Override
public void createContextMenu(ContextMenu menu) {
- MenuInflater inflater = mBrowserActivity.getMenuInflater();
+ MenuInflater inflater = mActivity.getMenuInflater();
inflater.inflate(R.menu.title_context, menu);
- mBrowserActivity.onCreateContextMenu(menu, this, null);
+ mActivity.onCreateContextMenu(menu, this, null);
}
@Override
@@ -179,7 +174,7 @@ public class TitleBar extends LinearLayout {
}
break;
case MotionEvent.ACTION_MOVE:
- int slop = ViewConfiguration.get(mBrowserActivity)
+ int slop = ViewConfiguration.get(mActivity)
.getScaledTouchSlop();
if ((int) event.getY() > getHeight() + slop) {
// We only trigger the actions in ACTION_UP if one or the
@@ -207,36 +202,37 @@ public class TitleBar extends LinearLayout {
case MotionEvent.ACTION_UP:
if (button.isPressed()) {
if (mInVoiceMode) {
- if (mBrowserActivity.getTabControl().getCurrentTab()
+ if (mController.getTabControl().getCurrentTab()
.voiceSearchSourceIsGoogle()) {
Intent i = new Intent(
LoggingEvents.ACTION_LOG_EVENT);
i.putExtra(LoggingEvents.EXTRA_EVENT,
LoggingEvents.VoiceSearch.RETRY);
- mBrowserActivity.sendBroadcast(i);
+ mActivity.sendBroadcast(i);
}
- mBrowserActivity.startActivity(mVoiceSearchIntent);
+ mActivity.startActivity(mVoiceSearchIntent);
} else if (mInLoad) {
- mBrowserActivity.stopLoading();
+ mController.stopLoading();
} else {
- mBrowserActivity.bookmarksOrHistoryPicker(false);
+ mController.bookmarkCurrentPage(
+ AddBookmarkPage.DEFAULT_FOLDER_ID);
}
button.setPressed(false);
} else if (mTitleBg.isPressed()) {
mHandler.removeMessages(LONG_PRESS);
if (mInVoiceMode) {
- if (mBrowserActivity.getTabControl().getCurrentTab()
+ if (mController.getTabControl().getCurrentTab()
.voiceSearchSourceIsGoogle()) {
Intent i = new Intent(
LoggingEvents.ACTION_LOG_EVENT);
i.putExtra(LoggingEvents.EXTRA_EVENT,
LoggingEvents.VoiceSearch.N_BEST_REVEAL);
- mBrowserActivity.sendBroadcast(i);
+ mActivity.sendBroadcast(i);
}
- mBrowserActivity.showVoiceSearchResults(
+ mController.showVoiceSearchResults(
mTitle.getText().toString().trim());
} else {
- mBrowserActivity.editUrl();
+ mController.editUrl();
}
mTitleBg.setPressed(false);
}
@@ -248,29 +244,11 @@ public class TitleBar extends LinearLayout {
}
/**
- * Set a new Bitmap for the Favicon.
- */
- /* package */ void setFavicon(Bitmap icon) {
- Drawable[] array = new Drawable[3];
- array[0] = new PaintDrawable(Color.BLACK);
- PaintDrawable p = new PaintDrawable(Color.WHITE);
- array[1] = p;
- if (icon == null) {
- array[2] = mGenericFavicon;
- } else {
- array[2] = new BitmapDrawable(icon);
- }
- LayerDrawable d = new LayerDrawable(array);
- d.setLayerInset(1, 1, 1, 1, 1);
- d.setLayerInset(2, 2, 2, 2, 2);
- mFavicon.setImageDrawable(d);
- }
-
- /**
* Change the TitleBar to or from voice mode. If there is no package to
* handle voice search, the TitleBar cannot be set to voice mode.
*/
- /* package */ void setInVoiceMode(boolean inVoiceMode) {
+ @Override
+ void setInVoiceMode(boolean inVoiceMode) {
if (mInVoiceMode == inVoiceMode) return;
mInVoiceMode = inVoiceMode && mVoiceSearchIntent != null;
Drawable titleDrawable;
@@ -302,21 +280,10 @@ public class TitleBar extends LinearLayout {
}
/**
- * Set the Drawable for the lock icon, or null to hide it.
- */
- /* package */ void setLock(Drawable d) {
- if (null == d) {
- mLockIcon.setVisibility(View.GONE);
- } else {
- mLockIcon.setImageDrawable(d);
- mLockIcon.setVisibility(View.VISIBLE);
- }
- }
-
- /**
* Update the progress, from 0 to 100.
*/
- /* package */ void setProgress(int newProgress) {
+ @Override
+ void setProgress(int newProgress) {
if (newProgress >= mHorizontalProgress.getMax()) {
mTitle.setCompoundDrawables(null, null, null, null);
((Animatable) mCircularProgress).stop();
@@ -356,7 +323,8 @@ public class TitleBar extends LinearLayout {
* @param title String to display. If null, the loading string will be
* shown.
*/
- /* package */ void setDisplayTitle(String title) {
+ @Override
+ void setDisplayTitle(String title) {
if (title == null) {
mTitle.setText(R.string.title_bar_loading);
} else {
@@ -374,11 +342,4 @@ public class TitleBar extends LinearLayout {
}
}
}
-
- /* package */ void setToTabPicker() {
- mTitle.setText(R.string.tab_picker_title);
- setFavicon(null);
- setLock(null);
- mHorizontalProgress.setVisibility(View.GONE);
- }
}
diff --git a/src/com/android/browser/TitleBarBase.java b/src/com/android/browser/TitleBarBase.java
new file mode 100644
index 000000000..7016dc020
--- /dev/null
+++ b/src/com/android/browser/TitleBarBase.java
@@ -0,0 +1,78 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.PaintDrawable;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+/**
+ * Base class for a title bar used by the browser.
+ */
+public class TitleBarBase extends LinearLayout {
+ // These need to be set by the subclass.
+ protected ImageView mFavicon;
+ protected ImageView mLockIcon;
+
+ protected Drawable mGenericFavicon;
+
+ public TitleBarBase(Context context) {
+ super(context, null);
+ mGenericFavicon = context.getResources().getDrawable(
+ R.drawable.app_web_browser_sm);
+ }
+
+ /* package */ void setProgress(int newProgress) {}
+ /* package */ void setDisplayTitle(String title) {}
+
+ /* package */ void setLock(Drawable d) {
+ assert mLockIcon != null;
+ if (null == d) {
+ mLockIcon.setVisibility(View.GONE);
+ } else {
+ mLockIcon.setImageDrawable(d);
+ mLockIcon.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /* package */ void setFavicon(Bitmap icon) {
+ assert mFavicon != null;
+ Drawable[] array = new Drawable[3];
+ array[0] = new PaintDrawable(Color.BLACK);
+ PaintDrawable p = new PaintDrawable(Color.WHITE);
+ array[1] = p;
+ if (icon == null) {
+ array[2] = mGenericFavicon;
+ } else {
+ array[2] = new BitmapDrawable(icon);
+ }
+ LayerDrawable d = new LayerDrawable(array);
+ d.setLayerInset(1, 1, 1, 1, 1);
+ d.setLayerInset(2, 2, 2, 2, 2);
+ mFavicon.setImageDrawable(d);
+ }
+
+ /* package */ void setInVoiceMode(boolean inVoiceMode) {}
+
+}
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
new file mode 100644
index 000000000..7e54710c9
--- /dev/null
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -0,0 +1,258 @@
+/*
+ * 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 com.android.browser.UrlInputView.UrlInputListener;
+
+import android.app.Activity;
+import android.app.SearchManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * tabbed title bar for xlarge screen browser
+ */
+public class TitleBarXLarge extends TitleBarBase
+ implements UrlInputListener, OnClickListener, OnFocusChangeListener {
+
+ private static final int PROGRESS_MAX = 100;
+
+ private Activity mActivity;
+ private UiController mUiController;
+
+ private Drawable mStopDrawable;
+ private Drawable mReloadDrawable;
+
+ private View mContainer;
+ private View mBackButton;
+ private View mForwardButton;
+ private View mStar;
+ private View mSearchButton;
+ private View mFocusContainer;
+ private View mUnfocusContainer;
+ private View mGoButton;
+ private ImageView mStopButton;
+ private View mAllButton;
+ private View mClearButton;
+ private PageProgressView mProgressView;
+ private UrlInputView mUrlFocused;
+ private TextView mUrlUnfocused;
+ private boolean mInLoad;
+
+ public TitleBarXLarge(Activity activity, UiController controller) {
+ super(activity);
+ mActivity = activity;
+ mUiController = controller;
+ Resources resources = activity.getResources();
+ mStopDrawable = resources.getDrawable(R.drawable.ic_stop_normal);
+ mReloadDrawable = resources.getDrawable(R.drawable.ic_refresh_normal);
+ rebuildLayout(activity, true);
+ }
+
+ private void rebuildLayout(Context context, boolean rebuildData) {
+ LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.url_bar, this);
+
+ mContainer = findViewById(R.id.taburlbar);
+ mUrlFocused = (UrlInputView) findViewById(R.id.url_focused);
+ mUrlUnfocused = (TextView) findViewById(R.id.url_unfocused);
+ mAllButton = findViewById(R.id.all_btn);
+ // TODO: Change enabled states based on whether you can go
+ // back/forward. Probably should be done inside onPageStarted.
+ mBackButton = findViewById(R.id.back);
+ mForwardButton = findViewById(R.id.forward);
+ mStar = findViewById(R.id.star);
+ mStopButton = (ImageView) findViewById(R.id.stop);
+ mSearchButton = findViewById(R.id.search);
+ mLockIcon = (ImageView) findViewById(R.id.lock);
+ mGoButton = findViewById(R.id.go);
+ mClearButton = findViewById(R.id.clear);
+ mProgressView = (PageProgressView) findViewById(R.id.progress);
+ mFocusContainer = findViewById(R.id.urlbar_focused);
+ mUnfocusContainer = findViewById(R.id.urlbar_unfocused);
+
+ mBackButton.setOnClickListener(this);
+ mForwardButton.setOnClickListener(this);
+ mStar.setOnClickListener(this);
+ mAllButton.setOnClickListener(this);
+ mStopButton.setOnClickListener(this);
+ mSearchButton.setOnClickListener(this);
+ mGoButton.setOnClickListener(this);
+ mClearButton.setOnClickListener(this);
+ mUrlFocused.setUrlInputListener(this);
+ mUrlUnfocused.setOnFocusChangeListener(this);
+ mUrlFocused.setContainer(mFocusContainer);
+ mUnfocusContainer.setOnClickListener(this);
+ }
+
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ setUrlMode(true);
+ mUrlFocused.selectAll();
+ mUrlFocused.requestFocus();
+ mUrlFocused.setDropDownWidth(mUnfocusContainer.getWidth());
+ mUrlFocused.setDropDownHorizontalOffset(-mUrlFocused.getLeft());
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mUnfocusContainer == v) {
+ mUrlUnfocused.requestFocus();
+ } else if (mBackButton == v) {
+ mUiController.getCurrentTopWebView().goBack();
+ } else if (mForwardButton == v) {
+ mUiController.getCurrentTopWebView().goForward();
+ } else if (mStar == v) {
+ mUiController.bookmarkCurrentPage(
+ AddBookmarkPage.DEFAULT_FOLDER_ID);
+ } else if (mAllButton == v) {
+ mUiController.bookmarksOrHistoryPicker(false);
+ } else if (mSearchButton == v) {
+ search();
+ } else if (mStopButton == v) {
+ stopOrRefresh();
+ } else if (mGoButton == v) {
+ if (!TextUtils.isEmpty(mUrlFocused.getText())) {
+ onAction(mUrlFocused.getText().toString(), null);
+ }
+ } else if (mClearButton == v) {
+ mUrlFocused.setText("");
+ }
+ }
+
+ int getHeightWithoutProgress() {
+ return mContainer.getHeight();
+ }
+
+ @Override
+ void setFavicon(Bitmap icon) { }
+
+ // UrlInputListener implementation
+
+ @Override
+ public void onAction(String text, String extra) {
+ mUiController.getCurrentTopWebView().requestFocus();
+ ((BaseUi) mUiController.getUi()).hideFakeTitleBar();
+ Intent i = new Intent();
+ i.setAction(Intent.ACTION_SEARCH);
+ i.putExtra(SearchManager.QUERY, text);
+ if (extra != null) {
+ i.putExtra(SearchManager.EXTRA_DATA_KEY, extra);
+ }
+ mUiController.handleNewIntent(i);
+ setUrlMode(false);
+ setDisplayTitle(text);
+ }
+
+ @Override
+ public void onDismiss() {
+ mUiController.getCurrentTopWebView().requestFocus();
+ ((BaseUi) mUiController.getUi()).hideFakeTitleBar();
+ setUrlMode(false);
+ setDisplayTitle(mUiController.getCurrentWebView().getUrl());
+ }
+
+ @Override
+ public void onEdit(String text) {
+ setDisplayTitle(text, true);
+ if (text != null) {
+ mUrlFocused.setSelection(text.length());
+ }
+ }
+
+ private void setUrlMode(boolean focused) {
+ swapUrlContainer(focused);
+ if (focused) {
+ mSearchButton.setVisibility(View.GONE);
+ mGoButton.setVisibility(View.VISIBLE);
+ } else {
+ mSearchButton.setVisibility(View.VISIBLE);
+ mGoButton.setVisibility(View.GONE);
+ }
+ }
+
+ private void swapUrlContainer(boolean focus) {
+ mUnfocusContainer.setVisibility(focus ? View.GONE : View.VISIBLE);
+ mFocusContainer.setVisibility(focus ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void createContextMenu(ContextMenu menu) {
+ MenuInflater inflater = mActivity.getMenuInflater();
+ inflater.inflate(R.menu.title_context, menu);
+ mActivity.onCreateContextMenu(menu, this, null);
+ }
+
+ private void search() {
+ setDisplayTitle("");
+ mUrlUnfocused.requestFocus();
+ }
+
+ private void stopOrRefresh() {
+ if (mInLoad) {
+ mUiController.stopLoading();
+ } else {
+ mUiController.getCurrentTopWebView().reload();
+ }
+ }
+
+ /**
+ * Update the progress, from 0 to 100.
+ */
+ @Override
+ void setProgress(int newProgress) {
+ if (newProgress >= PROGRESS_MAX) {
+ mProgressView.setProgress(PageProgressView.MAX_PROGRESS);
+ mProgressView.setVisibility(View.GONE);
+ mInLoad = false;
+ mStopButton.setImageDrawable(mReloadDrawable);
+ } else {
+ if (!mInLoad) {
+ mProgressView.setVisibility(View.VISIBLE);
+ mInLoad = true;
+ mStopButton.setImageDrawable(mStopDrawable);
+ }
+ mProgressView.setProgress(newProgress * PageProgressView.MAX_PROGRESS
+ / PROGRESS_MAX);
+ }
+ }
+
+ @Override
+ /* package */ void setDisplayTitle(String title) {
+ mUrlFocused.setText(title, false);
+ mUrlUnfocused.setText(title);
+ }
+
+ void setDisplayTitle(String title, boolean filter) {
+ mUrlFocused.setText(title, filter);
+ mUrlUnfocused.setText(title);
+ }
+
+}
diff --git a/src/com/android/browser/UI.java b/src/com/android/browser/UI.java
new file mode 100644
index 000000000..3a8a5cd4c
--- /dev/null
+++ b/src/com/android/browser/UI.java
@@ -0,0 +1,131 @@
+/*
+ * 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 com.android.browser.ScrollWebView.ScrollListener;
+
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.View;
+import android.webkit.WebChromeClient.CustomViewCallback;
+import android.webkit.WebView;
+
+import java.util.List;
+
+/**
+ * UI interface definitions
+ */
+public interface UI extends ScrollListener {
+
+ public void onPause();
+
+ public void onResume();
+
+ public void onDestroy();
+
+ public void onConfigurationChanged(Configuration config);
+
+ public boolean onBackKey();
+
+ public boolean needsRestoreAllTabs();
+
+ public void addTab(Tab tab);
+
+ public void removeTab(Tab tab);
+
+ public void setActiveTab(Tab tab);
+
+ public void updateTabs(List<Tab> tabs);
+
+ public void detachTab(Tab tab);
+
+ public void attachTab(Tab tab);
+
+ public void createSubWindow(Tab tab, WebView subWebView);
+
+ public void attachSubWindow(View subContainer);
+
+ public void removeSubWindow(View subContainer);
+
+ // TODO: consolidate
+ public void setUrlTitle(Tab tab, String url, String title);
+
+ // TODO: consolidate
+ public void setFavicon(Tab tab, Bitmap icon);
+
+ public void resetTitleAndRevertLockIcon(Tab tab);
+
+ public void resetTitleAndIcon(Tab tab);
+
+ public void onPageStarted(Tab tab, String url, Bitmap favicon);
+
+ public void onPageFinished(Tab tab, String url);
+
+ public void onPageStopped(Tab tab);
+
+ public void onProgressChanged(Tab tab, int progress);
+
+ public void showActiveTabsPage();
+
+ public void removeActiveTabsPage();
+
+ public void showComboView(boolean startWithHistory, Bundle extra);
+
+ public void hideComboView();
+
+ public void showCustomView(View view, CustomViewCallback callback);
+
+ public void onHideCustomView();
+
+ public boolean isCustomViewShowing();
+
+ public void showVoiceTitleBar(String title);
+
+ public void revertVoiceTitleBar(Tab tab);
+
+ // allow the ui to update state
+ public void onPrepareOptionsMenu(Menu menu);
+
+ public void onOptionsMenuOpened();
+
+ public void onExtendedMenuOpened();
+
+ public void onOptionsMenuClosed(boolean inLoad);
+
+ public void onExtendedMenuClosed(boolean inLoad);
+
+ public void onContextMenuCreated(Menu menu);
+
+ public void onContextMenuClosed(Menu menu, boolean inLoad);
+
+ public void onActionModeStarted(ActionMode mode);
+
+ public void onActionModeFinished(boolean inLoad);
+
+ public void setShouldShowErrorConsole(Tab tab, boolean show);
+
+ // returns if the web page is clear of any overlays (not including sub windows)
+ public boolean showsWeb();
+
+ Bitmap getDefaultVideoPoster();
+
+ View getVideoLoadingProgressView();
+
+}
diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java
new file mode 100644
index 000000000..dffebbae5
--- /dev/null
+++ b/src/com/android/browser/UiController.java
@@ -0,0 +1,78 @@
+/*
+ * 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.Intent;
+import android.webkit.WebView;
+
+import java.util.List;
+
+
+/**
+ * UI aspect of the controller
+ */
+public interface UiController extends BookmarksHistoryCallbacks {
+
+ UI getUi();
+
+ WebView getCurrentWebView();
+
+ WebView getCurrentTopWebView();
+
+ TabControl getTabControl();
+
+ List<Tab> getTabs();
+
+ Tab openTabToHomePage();
+
+ Tab openIncognitoTab();
+
+ boolean switchToTab(int tabIndex);
+
+ void closeCurrentTab();
+
+ void closeTab(Tab tab);
+
+ void stopLoading();
+
+ void bookmarkCurrentPage(long folderId);
+
+ void bookmarksOrHistoryPicker(boolean openHistory);
+
+ void showVoiceSearchResults(String title);
+
+ void editUrl();
+
+ void removeActiveTabsPage(boolean attach);
+
+ void handleNewIntent(Intent intent);
+
+ boolean shouldShowErrorConsole();
+
+ void removeComboView();
+
+ void hideCustomView();
+
+ void attachSubWindow(Tab tab);
+
+ void removeSubWindow(Tab tab);
+
+ boolean isInCustomActionMode();
+
+ void endActionMode();
+
+}
diff --git a/src/com/android/browser/UploadHandler.java b/src/com/android/browser/UploadHandler.java
new file mode 100644
index 000000000..d9b387fbf
--- /dev/null
+++ b/src/com/android/browser/UploadHandler.java
@@ -0,0 +1,214 @@
+/*
+ * 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.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.webkit.ValueCallback;
+
+import java.io.File;
+import java.util.Vector;
+
+/**
+ * Handle the file upload callbacks from WebView here
+ */
+public class UploadHandler {
+
+ /*
+ * The Object used to inform the WebView of the file to upload.
+ */
+ private ValueCallback<Uri> mUploadMessage;
+ private String mCameraFilePath;
+
+ private Controller mController;
+
+ public UploadHandler(Controller controller) {
+ mController = controller;
+ }
+
+ String getFilePath() {
+ return mCameraFilePath;
+ }
+
+ void onResult(int resultCode, Intent intent) {
+ Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
+ : intent.getData();
+
+ // As we ask the camera to save the result of the user taking
+ // a picture, the camera application does not return anything other
+ // than RESULT_OK. So we need to check whether the file we expected
+ // was written to disk in the in the case that we
+ // did not get an intent returned but did get a RESULT_OK. If it was,
+ // we assume that this result has came back from the camera.
+ if (result == null && intent == null && resultCode == Activity.RESULT_OK) {
+ File cameraFile = new File(mCameraFilePath);
+ if (cameraFile.exists()) {
+ result = Uri.fromFile(cameraFile);
+ // Broadcast to the media scanner that we have a new photo
+ // so it will be added into the gallery for the user.
+ mController.getActivity().sendBroadcast(
+ new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
+ }
+ }
+
+ mUploadMessage.onReceiveValue(result);
+ }
+
+ void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
+
+ final String imageMimeType = "image/*";
+ final String videoMimeType = "video/*";
+ final String audioMimeType = "audio/*";
+ final String mediaSourceKey = "capture";
+ final String mediaSourceValueCamera = "camera";
+ final String mediaSourceValueFileSystem = "filesystem";
+ final String mediaSourceValueCamcorder = "camcorder";
+ final String mediaSourceValueMicrophone = "microphone";
+
+ // media source can be 'filesystem' or 'camera' or 'camcorder' or 'microphone'.
+ String mediaSource = "";
+
+ // We add the camera intent if there was no accept type (or '*/*' or 'image/*').
+ boolean addCameraIntent = true;
+ // We add the camcorder intent if there was no accept type (or '*/*' or 'video/*').
+ boolean addCamcorderIntent = true;
+
+ if (mUploadMessage != null) {
+ // Already a file picker operation in progress.
+ return;
+ }
+
+ mUploadMessage = uploadMsg;
+
+ // Parse the accept type.
+ String params[] = acceptType.split(";");
+ String mimeType = params[0];
+
+ for (String p : params) {
+ String[] keyValue = p.split("=");
+ if (keyValue.length == 2) {
+ // Process key=value parameters.
+ if (mediaSourceKey.equals(keyValue[0])) {
+ mediaSource = keyValue[1];
+ }
+ }
+ }
+
+ // This intent will display the standard OPENABLE file picker.
+ Intent i = new Intent(Intent.ACTION_GET_CONTENT);
+ i.addCategory(Intent.CATEGORY_OPENABLE);
+
+ // Create an intent to add to the standard file picker that will
+ // capture an image from the camera. We'll combine this intent with
+ // the standard OPENABLE picker unless the web developer specifically
+ // requested the camera or gallery be opened by passing a parameter
+ // in the accept type.
+ Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ File externalDataDir = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DCIM);
+ File cameraDataDir = new File(externalDataDir.getAbsolutePath() +
+ File.separator + "browser-photos");
+ cameraDataDir.mkdirs();
+ mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator +
+ System.currentTimeMillis() + ".jpg";
+ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath)));
+
+ Intent camcorderIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+
+ Intent soundRecIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
+
+ if (mimeType.equals(imageMimeType)) {
+ i.setType(imageMimeType);
+ addCamcorderIntent = false;
+ if (mediaSource.equals(mediaSourceValueCamera)) {
+ // Specified 'image/*' and requested the camera, so go ahead and launch the camera
+ // directly.
+ startActivity(cameraIntent);
+ return;
+ } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+ // Specified filesytem as the source, so don't want to consider the camera.
+ addCameraIntent = false;
+ }
+ } else if (mimeType.equals(videoMimeType)) {
+ i.setType(videoMimeType);
+ addCameraIntent = false;
+ // The camcorder saves it's own file and returns it to us in the intent, so
+ // we don't need to generate one here.
+ mCameraFilePath = null;
+
+ if (mediaSource.equals(mediaSourceValueCamcorder)) {
+ // Specified 'video/*' and requested the camcorder, so go ahead and launch the
+ // camcorder directly.
+ startActivity(camcorderIntent);
+ return;
+ } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+ // Specified filesystem as the source, so don't want to consider the camcorder.
+ addCamcorderIntent = false;
+ }
+ } else if (mimeType.equals(audioMimeType)) {
+ i.setType(audioMimeType);
+ addCameraIntent = false;
+ addCamcorderIntent = false;
+ if (mediaSource.equals(mediaSourceValueMicrophone)) {
+ // Specified 'audio/*' and requested microphone, so go ahead and launch the sound
+ // recorder.
+ startActivity(soundRecIntent);
+ return;
+ }
+ // On a default system, there is no single option to open an audio "gallery". Both the
+ // sound recorder and music browser respond to the OPENABLE/audio/* intent unlike the
+ // image/* and video/* OPENABLE intents where the image / video gallery are the only
+ // respondants (and so the user is not prompted by default).
+ } else {
+ i.setType("*/*");
+ }
+
+ // Combine the chooser and the extra choices (like camera or camcorder)
+ Intent chooser = new Intent(Intent.ACTION_CHOOSER);
+ chooser.putExtra(Intent.EXTRA_INTENT, i);
+
+ Vector<Intent> extraInitialIntents = new Vector<Intent>(0);
+
+ if (addCameraIntent) {
+ extraInitialIntents.add(cameraIntent);
+ }
+
+ if (addCamcorderIntent) {
+ extraInitialIntents.add(camcorderIntent);
+ }
+
+ if (extraInitialIntents.size() > 0) {
+ Intent[] extraIntents = new Intent[extraInitialIntents.size()];
+ chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS,
+ extraInitialIntents.toArray(extraIntents));
+ }
+
+ chooser.putExtra(Intent.EXTRA_TITLE,
+ mController.getActivity().getResources()
+ .getString(R.string.choose_upload));
+ startActivity(chooser);
+ }
+
+ private void startActivity(Intent intent) {
+ mController.getActivity().startActivityForResult(intent,
+ Controller.FILE_SELECTED);
+ }
+
+}
diff --git a/src/com/android/browser/UrlHandler.java b/src/com/android/browser/UrlHandler.java
new file mode 100644
index 000000000..72704e012
--- /dev/null
+++ b/src/com/android/browser/UrlHandler.java
@@ -0,0 +1,238 @@
+/*
+ * 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.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+import android.webkit.WebView;
+
+import java.net.URISyntaxException;
+
+/**
+ *
+ */
+public class UrlHandler {
+
+ // Use in overrideUrlLoading
+ /* package */ final static String SCHEME_WTAI = "wtai://wp/";
+ /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
+ /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
+ /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
+
+ Controller mController;
+ Activity mActivity;
+
+ private Boolean mIsProviderPresent = null;
+ private Uri mRlzUri = null;
+
+ public UrlHandler(Controller controller) {
+ mController = controller;
+ mActivity = mController.getActivity();
+ }
+
+ boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (view.isPrivateBrowsingEnabled()) {
+ // Don't allow urls to leave the browser app when in
+ // private browsing mode
+ mController.loadUrl(view, url);
+ return true;
+ }
+
+ if (url.startsWith(SCHEME_WTAI)) {
+ // wtai://wp/mc;number
+ // number=string(phone-number)
+ if (url.startsWith(SCHEME_WTAI_MC)) {
+ Intent intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse(WebView.SCHEME_TEL +
+ url.substring(SCHEME_WTAI_MC.length())));
+ mActivity.startActivity(intent);
+ // before leaving BrowserActivity, close the empty child tab.
+ // If a new tab is created through JavaScript open to load this
+ // url, we would like to close it as we will load this url in a
+ // different Activity.
+ mController.closeEmptyChildTab();
+ return true;
+ }
+ // wtai://wp/sd;dtmf
+ // dtmf=string(dialstring)
+ if (url.startsWith(SCHEME_WTAI_SD)) {
+ // TODO: only send when there is active voice connection
+ return false;
+ }
+ // wtai://wp/ap;number;name
+ // number=string(phone-number)
+ // name=string
+ if (url.startsWith(SCHEME_WTAI_AP)) {
+ // TODO
+ return false;
+ }
+ }
+
+ // The "about:" schemes are internal to the browser; don't want these to
+ // be dispatched to other apps.
+ if (url.startsWith("about:")) {
+ return false;
+ }
+
+ // If this is a Google search, attempt to add an RLZ string
+ // (if one isn't already present).
+ if (rlzProviderPresent()) {
+ Uri siteUri = Uri.parse(url);
+ if (needsRlzString(siteUri)) {
+ String rlz = null;
+ Cursor cur = null;
+ try {
+ cur = mActivity.getContentResolver()
+ .query(getRlzUri(), null, null, null, null);
+ if (cur != null && cur.moveToFirst() && !cur.isNull(0)) {
+ url = siteUri.buildUpon()
+ .appendQueryParameter("rlz", cur.getString(0))
+ .build().toString();
+ }
+ } finally {
+ if (cur != null) {
+ cur.close();
+ }
+ }
+ mController.loadUrl(view, url);
+ return true;
+ }
+ }
+
+ Intent intent;
+ // perform generic parsing of the URI to turn it into an Intent.
+ try {
+ intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
+ } catch (URISyntaxException ex) {
+ Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
+ return false;
+ }
+
+ // check whether the intent can be resolved. If not, we will see
+ // whether we can download it from the Market.
+ if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
+ String packagename = intent.getPackage();
+ if (packagename != null) {
+ intent = new Intent(Intent.ACTION_VIEW, Uri
+ .parse("market://search?q=pname:" + packagename));
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ mActivity.startActivity(intent);
+ // before leaving BrowserActivity, close the empty child tab.
+ // If a new tab is created through JavaScript open to load this
+ // url, we would like to close it as we will load this url in a
+ // different Activity.
+ mController.closeEmptyChildTab();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // sanitize the Intent, ensuring web pages can not bypass browser
+ // security (only access to BROWSABLE activities).
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ intent.setComponent(null);
+ try {
+ if (mActivity.startActivityIfNeeded(intent, -1)) {
+ // before leaving BrowserActivity, close the empty child tab.
+ // If a new tab is created through JavaScript open to load this
+ // url, we would like to close it as we will load this url in a
+ // different Activity.
+ mController.closeEmptyChildTab();
+ return true;
+ }
+ } catch (ActivityNotFoundException ex) {
+ // ignore the error. If no application can handle the URL,
+ // eg about:blank, assume the browser can handle it.
+ }
+
+ if (mController.isMenuDown()) {
+ mController.openTab(url, false);
+ mActivity.closeOptionsMenu();
+ return true;
+ }
+ return false;
+ }
+
+ // Determine whether the RLZ provider is present on the system.
+ private boolean rlzProviderPresent() {
+ if (mIsProviderPresent == null) {
+ PackageManager pm = mActivity.getPackageManager();
+ mIsProviderPresent = pm.resolveContentProvider(
+ BrowserSettings.RLZ_PROVIDER, 0) != null;
+ }
+ return mIsProviderPresent;
+ }
+
+ // Retrieve the RLZ access point string and cache the URI used to
+ // retrieve RLZ values.
+ private Uri getRlzUri() {
+ if (mRlzUri == null) {
+ String ap = mActivity.getResources()
+ .getString(R.string.rlz_access_point);
+ mRlzUri = Uri.withAppendedPath(BrowserSettings.RLZ_PROVIDER_URI, ap);
+ }
+ return mRlzUri;
+ }
+
+ // Determine if this URI appears to be for a Google search
+ // and does not have an RLZ parameter.
+ // Taken largely from Chrome source, src/chrome/browser/google_url_tracker.cc
+ private static boolean needsRlzString(Uri uri) {
+ String scheme = uri.getScheme();
+ if (("http".equals(scheme) || "https".equals(scheme)) &&
+ (uri.getQueryParameter("q") != null) &&
+ (uri.getQueryParameter("rlz") == null)) {
+ String host = uri.getHost();
+ if (host == null) {
+ return false;
+ }
+ String[] hostComponents = host.split("\\.");
+
+ if (hostComponents.length < 2) {
+ return false;
+ }
+ int googleComponent = hostComponents.length - 2;
+ String component = hostComponents[googleComponent];
+ if (!"google".equals(component)) {
+ if (hostComponents.length < 3 ||
+ (!"co".equals(component) && !"com".equals(component))) {
+ return false;
+ }
+ googleComponent = hostComponents.length - 3;
+ if (!"google".equals(hostComponents[googleComponent])) {
+ return false;
+ }
+ }
+
+ // Google corp network handling.
+ if (googleComponent > 0 && "corp".equals(
+ hostComponents[googleComponent - 1])) {
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
new file mode 100644
index 000000000..2e29f261c
--- /dev/null
+++ b/src/com/android/browser/UrlInputView.java
@@ -0,0 +1,190 @@
+/*
+ * 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 com.android.browser.SuggestionsAdapter.CompletionListener;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AutoCompleteTextView;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+/**
+ * url/search input view
+ * handling suggestions
+ */
+public class UrlInputView extends AutoCompleteTextView
+ implements OnFocusChangeListener, OnEditorActionListener, CompletionListener {
+
+ private UrlInputListener mListener;
+ private InputMethodManager mInputManager;
+ private SuggestionsAdapter mAdapter;
+ private OnFocusChangeListener mWrappedFocusListener;
+ private View mContainer;
+ private boolean mLandscape;
+
+ public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ public UrlInputView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public UrlInputView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
+ setOnEditorActionListener(this);
+ super.setOnFocusChangeListener(this);
+ mAdapter = new SuggestionsAdapter(ctx, this);
+ setAdapter(mAdapter);
+ setSelectAllOnFocus(false);
+ onConfigurationChanged(ctx.getResources().getConfiguration());
+ setThreshold(1);
+ }
+
+ void setContainer(View container) {
+ mContainer = container;
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration config) {
+ super.onConfigurationChanged(config);
+ mLandscape = (config.orientation &
+ Configuration.ORIENTATION_LANDSCAPE) > 0;
+ mAdapter.setLandscapeMode(mLandscape);
+ if (isPopupShowing() && (getVisibility() == View.VISIBLE)) {
+ setupDropDown();
+ }
+ }
+
+ @Override
+ public void showDropDown() {
+ setupDropDown();
+ super.showDropDown();
+ }
+
+ @Override
+ public void dismissDropDown() {
+ super.dismissDropDown();
+ mAdapter.clearCache();
+ }
+
+ private void setupDropDown() {
+ int width = mContainer.getWidth();
+ if (width != getDropDownWidth()) {
+ setDropDownWidth(width);
+ }
+ if (getLeft() != -getDropDownHorizontalOffset()) {
+ setDropDownHorizontalOffset(-getLeft());
+ }
+ }
+
+ @Override
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ // suppress selection action mode
+ return null;
+ }
+
+ @Override
+ public void setOnFocusChangeListener(OnFocusChangeListener focusListener) {
+ mWrappedFocusListener = focusListener;
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ finishInput(getText().toString(), null);
+ return true;
+ }
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ forceIme();
+ } else {
+ finishInput(null, null);
+ }
+ if (mWrappedFocusListener != null) {
+ mWrappedFocusListener.onFocusChange(v, hasFocus);
+ }
+ }
+
+ public void setUrlInputListener(UrlInputListener listener) {
+ mListener = listener;
+ }
+
+ public void forceIme() {
+ mInputManager.showSoftInput(this, 0);
+ }
+
+ private void finishInput(String url, String extra) {
+ this.dismissDropDown();
+ this.setSelection(0,0);
+ mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
+ if (url == null) {
+ mListener.onDismiss();
+ } else {
+ mListener.onAction(url, extra);
+ }
+ }
+
+ // Completion Listener
+
+ @Override
+ public void onSearch(String search) {
+ mListener.onEdit(search);
+ }
+
+ @Override
+ public void onSelect(String url, String extra) {
+ finishInput(url, extra);
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // catch back key in order to do slightly more cleanup than usual
+ finishInput(null, null);
+ return true;
+ }
+ return super.onKeyPreIme(keyCode, evt);
+ }
+
+ interface UrlInputListener {
+
+ public void onDismiss();
+
+ public void onAction(String text, String extra);
+
+ public void onEdit(String text);
+
+ }
+
+}
diff --git a/src/com/android/browser/UrlUtils.java b/src/com/android/browser/UrlUtils.java
new file mode 100644
index 000000000..2df0a616b
--- /dev/null
+++ b/src/com/android/browser/UrlUtils.java
@@ -0,0 +1,148 @@
+/*
+ * 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.net.Uri;
+import android.util.Patterns;
+import android.webkit.URLUtil;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods for Url manipulation
+ */
+public class UrlUtils {
+
+ static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
+ "(?i)" + // switch on case insensitive matching
+ "(" + // begin group for schema
+ "(?:http|https|file):\\/\\/" +
+ "|(?:inline|data|about|content|javascript):" +
+ ")" +
+ "(.*)" );
+
+ // Google search
+ private final static String QUICKSEARCH_G = "http://www.google.com/m?q=%s";
+ private final static String QUERY_PLACE_HOLDER = "%s";
+
+ // Regular expression which matches http://, followed by some stuff, followed by
+ // optionally a trailing slash, all matched as separate groups.
+ private static final Pattern STRIP_URL_PATTERN = Pattern.compile("^(http://)(.*?)(/$)?");
+
+ private UrlUtils() { /* cannot be instantiated */ }
+
+ /**
+ * Strips the provided url of preceding "http://" and any trailing "/". Does not
+ * strip "https://". If the provided string cannot be stripped, the original string
+ * is returned.
+ *
+ * TODO: Put this in TextUtils to be used by other packages doing something similar.
+ *
+ * @param url a url to strip, like "http://www.google.com/"
+ * @return a stripped url like "www.google.com", or the original string if it could
+ * not be stripped
+ */
+ /* package */ static String stripUrl(String url) {
+ if (url == null) return null;
+ Matcher m = STRIP_URL_PATTERN.matcher(url);
+ if (m.matches() && m.groupCount() == 3) {
+ return m.group(2);
+ } else {
+ return url;
+ }
+ }
+
+ protected static String smartUrlFilter(Uri inUri) {
+ if (inUri != null) {
+ return smartUrlFilter(inUri.toString());
+ }
+ return null;
+ }
+
+ /**
+ * Attempts to determine whether user input is a URL or search
+ * terms. Anything with a space is passed to search.
+ *
+ * Converts to lowercase any mistakenly uppercased schema (i.e.,
+ * "Http://" converts to "http://"
+ *
+ * @return Original or modified URL
+ *
+ */
+ protected static String smartUrlFilter(String url) {
+
+ String inUrl = url.trim();
+ boolean hasSpace = inUrl.indexOf(' ') != -1;
+
+ Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
+ if (matcher.matches()) {
+ // force scheme to lowercase
+ String scheme = matcher.group(1);
+ String lcScheme = scheme.toLowerCase();
+ if (!lcScheme.equals(scheme)) {
+ inUrl = lcScheme + matcher.group(2);
+ }
+ if (hasSpace) {
+ inUrl = inUrl.replace(" ", "%20");
+ }
+ return inUrl;
+ }
+ 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(mBrowser.getContentResolver(), inUrl);
+ return URLUtil.composeSearchUrl(inUrl, QUICKSEARCH_G, QUERY_PLACE_HOLDER);
+ }
+
+ /* package */ static String fixUrl(String inUrl) {
+ // FIXME: Converting the url to lower case
+ // duplicates functionality in smartUrlFilter().
+ // However, changing all current callers of fixUrl to
+ // call smartUrlFilter in addition may have unwanted
+ // consequences, and is deferred for now.
+ int colon = inUrl.indexOf(':');
+ boolean allLower = true;
+ for (int index = 0; index < colon; index++) {
+ char ch = inUrl.charAt(index);
+ if (!Character.isLetter(ch)) {
+ break;
+ }
+ allLower &= Character.isLowerCase(ch);
+ if (index == colon - 1 && !allLower) {
+ inUrl = inUrl.substring(0, colon).toLowerCase()
+ + inUrl.substring(colon);
+ }
+ }
+ if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
+ return inUrl;
+ if (inUrl.startsWith("http:") ||
+ inUrl.startsWith("https:")) {
+ if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
+ inUrl = inUrl.replaceFirst("/", "//");
+ } else inUrl = inUrl.replaceFirst(":", "://");
+ }
+ return inUrl;
+ }
+
+}
diff --git a/src/com/android/browser/WallpaperHandler.java b/src/com/android/browser/WallpaperHandler.java
new file mode 100644
index 000000000..0c88a50fd
--- /dev/null
+++ b/src/com/android/browser/WallpaperHandler.java
@@ -0,0 +1,127 @@
+/*
+ * 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.app.ProgressDialog;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Handle setWallpaper requests
+ *
+ */
+public class WallpaperHandler extends Thread
+ implements OnMenuItemClickListener, DialogInterface.OnCancelListener {
+
+
+ private static final String LOGTAG = "WallpaperHandler";
+
+ private Context mContext;
+ private URL mUrl;
+ private ProgressDialog mWallpaperProgress;
+ private boolean mCanceled = false;
+
+ public WallpaperHandler(Context context, String url) {
+ mContext = context;
+ try {
+ mUrl = new URL(url);
+ } catch (MalformedURLException e) {
+ mUrl = null;
+ }
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mCanceled = true;
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ if (mUrl != null) {
+ // The user may have tried to set a image with a large file size as
+ // their background so it may take a few moments to perform the
+ // operation.
+ // Display a progress spinner while it is working.
+ mWallpaperProgress = new ProgressDialog(mContext);
+ mWallpaperProgress.setIndeterminate(true);
+ mWallpaperProgress.setMessage(mContext.getResources()
+ .getText(R.string.progress_dialog_setting_wallpaper));
+ mWallpaperProgress.setCancelable(true);
+ mWallpaperProgress.setOnCancelListener(this);
+ mWallpaperProgress.show();
+ start();
+ }
+ return true;
+ }
+
+ @Override
+ public void run() {
+ Drawable oldWallpaper =
+ WallpaperManager.getInstance(mContext).getDrawable();
+ try {
+ // TODO: This will cause the resource to be downloaded again, when
+ // we should in most cases be able to grab it from the cache. To fix
+ // this we should query WebCore to see if we can access a cached
+ // version and instead open an input stream on that. This pattern
+ // could also be used in the download manager where the same problem
+ // exists.
+ InputStream inputstream = mUrl.openStream();
+ if (inputstream != null) {
+ WallpaperManager.getInstance(mContext).setStream(inputstream);
+ }
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Unable to set new wallpaper");
+ // Act as though the user canceled the operation so we try to
+ // restore the old wallpaper.
+ mCanceled = true;
+ }
+
+ if (mCanceled) {
+ // Restore the old wallpaper if the user cancelled whilst we were
+ // setting
+ // the new wallpaper.
+ int width = oldWallpaper.getIntrinsicWidth();
+ int height = oldWallpaper.getIntrinsicHeight();
+ Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+ Canvas canvas = new Canvas(bm);
+ oldWallpaper.setBounds(0, 0, width, height);
+ oldWallpaper.draw(canvas);
+ try {
+ WallpaperManager.getInstance(mContext).setBitmap(bm);
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Unable to restore old wallpaper.");
+ }
+ mCanceled = false;
+ }
+
+ if (mWallpaperProgress.isShowing()) {
+ mWallpaperProgress.dismiss();
+ }
+ }
+}
diff --git a/src/com/android/browser/WebDialog.java b/src/com/android/browser/WebDialog.java
deleted file mode 100644
index 9995e8f31..000000000
--- a/src/com/android/browser/WebDialog.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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/WebViewController.java b/src/com/android/browser/WebViewController.java
new file mode 100644
index 000000000..eeeee1850
--- /dev/null
+++ b/src/com/android/browser/WebViewController.java
@@ -0,0 +1,108 @@
+/*
+ * 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 com.android.browser.IntentHandler.UrlData;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.net.http.SslError;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.view.View;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+
+/**
+ * WebView aspect of the controller
+ */
+public interface WebViewController {
+
+ Activity getActivity();
+
+ TabControl getTabControl();
+
+ WebViewFactory getWebViewFactory();
+
+ void createSubWindow(Tab tab);
+
+ void onPageStarted(Tab tab, WebView view, String url, Bitmap favicon);
+
+ void onPageFinished(Tab tab, String url);
+
+ void onProgressChanged(Tab tab, int newProgress);
+
+ void onReceivedTitle(Tab tab, final String title);
+
+ void onFavicon(Tab tab, WebView view, Bitmap icon);
+
+ boolean shouldOverrideUrlLoading(WebView view, String url);
+
+ boolean shouldOverrideKeyEvent(KeyEvent event);
+
+ void onUnhandledKeyEvent(KeyEvent event);
+
+ void doUpdateVisitedHistory(Tab tab, String url, boolean isReload);
+
+ void getVisitedHistory(final ValueCallback<String[]> callback);
+
+ void onReceivedHttpAuthRequest(Tab tab, WebView view, final HttpAuthHandler handler,
+ final String host, final String realm);
+
+ void onDownloadStart(Tab tab, String url, String useragent, String contentDisposition,
+ String mimeType, long contentLength);
+
+ void showCustomView(Tab tab, View view, WebChromeClient.CustomViewCallback callback);
+
+ void hideCustomView();
+
+ Bitmap getDefaultVideoPoster();
+
+ View getVideoLoadingProgressView();
+
+ void showSslCertificateOnError(WebView view, SslErrorHandler handler,
+ SslError error);
+
+ void activateVoiceSearchMode(String title);
+
+ void revertVoiceSearchMode(Tab tab);
+
+ boolean shouldShowErrorConsole();
+
+ void resetTitleAndRevertLockIcon(Tab tab);
+
+ void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType);
+
+ void endActionMode();
+
+ void attachSubWindow(Tab tab);
+
+ void dismissSubWindow(Tab tab);
+
+ Tab openTabAndShow(UrlData urlData, boolean closeOnExit, String appId);
+
+ boolean switchToTab(int tabindex);
+
+ void closeTab(Tab tab);
+
+ void setupAutoFill(Message message);
+
+}
diff --git a/src/com/android/browser/WebViewFactory.java b/src/com/android/browser/WebViewFactory.java
new file mode 100644
index 000000000..1186e65bc
--- /dev/null
+++ b/src/com/android/browser/WebViewFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.webkit.WebView;
+
+/**
+ * Factory for WebViews
+ */
+public interface WebViewFactory {
+
+ public WebView createWebView(boolean privateBrowsing);
+
+ public WebView createSubWebView(boolean privateBrowsing);
+
+}
diff --git a/src/com/android/browser/WebsiteSettingsActivity.java b/src/com/android/browser/WebsiteSettingsActivity.java
index 1e27092b9..95f8fb02c 100644
--- a/src/com/android/browser/WebsiteSettingsActivity.java
+++ b/src/com/android/browser/WebsiteSettingsActivity.java
@@ -24,8 +24,9 @@ import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
-import android.provider.Browser;
+import android.provider.BrowserContract.Bookmarks;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -242,67 +243,94 @@ public class WebsiteSettingsActivity extends ListActivity {
public void populateIcons(Map<String, Site> sites) {
// Create a map from host to origin. This is used to add metadata
- // (title, icon) for this origin from the bookmarks DB.
- HashMap<String, Set<Site>> hosts = new HashMap<String, Set<Site>>();
- Set<Map.Entry<String, Site>> elements = sites.entrySet();
- Iterator<Map.Entry<String, Site>> originIter = elements.iterator();
- while (originIter.hasNext()) {
- Map.Entry<String, Site> entry = originIter.next();
- Site site = entry.getValue();
- String host = Uri.parse(entry.getKey()).getHost();
- Set<Site> hostSites = null;
- if (hosts.containsKey(host)) {
- hostSites = (Set<Site>)hosts.get(host);
- } else {
- hostSites = new HashSet<Site>();
- hosts.put(host, hostSites);
- }
- hostSites.add(site);
+ // (title, icon) for this origin from the bookmarks DB. We must do
+ // the DB access on a background thread.
+ new UpdateFromBookmarksDbTask(this.getContext(), sites).execute();
+ }
+
+ private class UpdateFromBookmarksDbTask extends AsyncTask<Void, Void, Void> {
+
+ private Context mContext;
+ private boolean mDataSetChanged;
+ private Map<String, Site> mSites;
+
+ public UpdateFromBookmarksDbTask(Context ctx, Map<String, Site> sites) {
+ mContext = ctx;
+ mSites = sites;
}
- // Check the bookmark DB. If we have data for a host used by any of
- // our origins, use it to set their title and favicon
- Cursor c = getContext().getContentResolver().query(Browser.BOOKMARKS_URI,
- new String[] { Browser.BookmarkColumns.URL, Browser.BookmarkColumns.TITLE,
- Browser.BookmarkColumns.FAVICON }, "bookmark = 1", null, null);
-
- if (c != null) {
- if (c.moveToFirst()) {
- int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
- int titleIndex = c.getColumnIndex(Browser.BookmarkColumns.TITLE);
- int faviconIndex = c.getColumnIndex(Browser.BookmarkColumns.FAVICON);
- do {
- String url = c.getString(urlIndex);
- String host = Uri.parse(url).getHost();
- if (hosts.containsKey(host)) {
- String title = c.getString(titleIndex);
- Bitmap bmp = null;
- byte[] data = c.getBlob(faviconIndex);
- if (data != null) {
- bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
- }
- Set matchingSites = (Set) hosts.get(host);
- Iterator<Site> sitesIter = matchingSites.iterator();
- while (sitesIter.hasNext()) {
- Site site = sitesIter.next();
- // We should only set the title if the bookmark is for the root
- // (i.e. www.google.com), as website settings act on the origin
- // as a whole rather than a single page under that origin. If the
- // user has bookmarked a page under the root but *not* the root,
- // then we risk displaying the title of that page which may or
- // may not have any relevance to the origin.
- if (url.equals(site.getOrigin()) ||
- (new String(site.getOrigin()+"/")).equals(url)) {
- site.setTitle(title);
+ protected Void doInBackground(Void... unused) {
+ HashMap<String, Set<Site>> hosts = new HashMap<String, Set<Site>>();
+ Set<Map.Entry<String, Site>> elements = mSites.entrySet();
+ Iterator<Map.Entry<String, Site>> originIter = elements.iterator();
+ while (originIter.hasNext()) {
+ Map.Entry<String, Site> entry = originIter.next();
+ Site site = entry.getValue();
+ String host = Uri.parse(entry.getKey()).getHost();
+ Set<Site> hostSites = null;
+ if (hosts.containsKey(host)) {
+ hostSites = (Set<Site>)hosts.get(host);
+ } else {
+ hostSites = new HashSet<Site>();
+ hosts.put(host, hostSites);
+ }
+ hostSites.add(site);
+ }
+
+ // Check the bookmark DB. If we have data for a host used by any of
+ // our origins, use it to set their title and favicon
+ Cursor c = mContext.getContentResolver().query(Bookmarks.CONTENT_URI,
+ new String[] { Bookmarks.URL, Bookmarks.TITLE, Bookmarks.FAVICON },
+ Bookmarks.IS_FOLDER + " == 0", null, null);
+
+ if (c != null) {
+ if (c.moveToFirst()) {
+ int urlIndex = c.getColumnIndex(Bookmarks.URL);
+ int titleIndex = c.getColumnIndex(Bookmarks.TITLE);
+ int faviconIndex = c.getColumnIndex(Bookmarks.FAVICON);
+ do {
+ String url = c.getString(urlIndex);
+ String host = Uri.parse(url).getHost();
+ if (hosts.containsKey(host)) {
+ String title = c.getString(titleIndex);
+ Bitmap bmp = null;
+ byte[] data = c.getBlob(faviconIndex);
+ if (data != null) {
+ bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
}
- if (bmp != null) {
- site.setIcon(bmp);
+ Set matchingSites = (Set) hosts.get(host);
+ Iterator<Site> sitesIter = matchingSites.iterator();
+ while (sitesIter.hasNext()) {
+ Site site = sitesIter.next();
+ // We should only set the title if the bookmark is for the root
+ // (i.e. www.google.com), as website settings act on the origin
+ // as a whole rather than a single page under that origin. If the
+ // user has bookmarked a page under the root but *not* the root,
+ // then we risk displaying the title of that page which may or
+ // may not have any relevance to the origin.
+ if (url.equals(site.getOrigin()) ||
+ (new String(site.getOrigin()+"/")).equals(url)) {
+ mDataSetChanged = true;
+ site.setTitle(title);
+ }
+
+ if (bmp != null) {
+ mDataSetChanged = true;
+ site.setIcon(bmp);
+ }
}
}
- }
- } while (c.moveToNext());
+ } while (c.moveToNext());
+ }
+ c.close();
+ }
+ return null;
+ }
+
+ protected void onPostExecute(Void unused) {
+ if (mDataSetChanged) {
+ notifyDataSetChanged();
}
- c.close();
}
}
diff --git a/src/com/android/browser/preferences/AdvancedPreferencesFragment.java b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
new file mode 100644
index 000000000..59b6ce1f5
--- /dev/null
+++ b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
@@ -0,0 +1,91 @@
+/*
+ * 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.preferences;
+
+import com.android.browser.BrowserSettings;
+import com.android.browser.R;
+import com.android.browser.WebsiteSettingsActivity;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
+import android.webkit.WebStorage;
+
+import java.util.Map;
+import java.util.Set;
+
+public class AdvancedPreferencesFragment extends PreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.advanced_preferences);
+
+ PreferenceScreen websiteSettings = (PreferenceScreen) findPreference(
+ BrowserSettings.PREF_WEBSITE_SETTINGS);
+ Intent intent = new Intent(getActivity(), WebsiteSettingsActivity.class);
+ websiteSettings.setIntent(intent);
+ }
+
+ /*
+ * We need to set the PreferenceScreen state in onResume(), as the number of
+ * origins with active features (WebStorage, Geolocation etc) could have
+ * changed after calling the WebsiteSettingsActivity.
+ */
+ @Override
+ public void onResume() {
+ super.onResume();
+ final PreferenceScreen websiteSettings = (PreferenceScreen) findPreference(
+ BrowserSettings.PREF_WEBSITE_SETTINGS);
+ websiteSettings.setEnabled(false);
+ WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
+ @Override
+ public void onReceiveValue(Map webStorageOrigins) {
+ if ((webStorageOrigins != null) && !webStorageOrigins.isEmpty()) {
+ websiteSettings.setEnabled(true);
+ }
+ }
+ });
+ GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set<String> >() {
+ @Override
+ public void onReceiveValue(Set<String> geolocationOrigins) {
+ if ((geolocationOrigins != null) && !geolocationOrigins.isEmpty()) {
+ websiteSettings.setEnabled(true);
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference pref, Object objValue) {
+ if (pref.getKey().equals(BrowserSettings.PREF_EXTRAS_RESET_DEFAULTS)) {
+ Boolean value = (Boolean) objValue;
+ if (value.booleanValue() == true) {
+ getActivity().finish();
+ return true;
+ }
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/browser/preferences/DebugPreferencesFragment.java b/src/com/android/browser/preferences/DebugPreferencesFragment.java
new file mode 100644
index 000000000..d643a9718
--- /dev/null
+++ b/src/com/android/browser/preferences/DebugPreferencesFragment.java
@@ -0,0 +1,32 @@
+/*
+ * 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.preferences;
+
+import com.android.browser.R;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+public class DebugPreferencesFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.debug_preferences);
+ }
+}
diff --git a/src/com/android/browser/preferences/PageContentPreferencesFragment.java b/src/com/android/browser/preferences/PageContentPreferencesFragment.java
new file mode 100644
index 000000000..1b5d0feee
--- /dev/null
+++ b/src/com/android/browser/preferences/PageContentPreferencesFragment.java
@@ -0,0 +1,151 @@
+/*
+ * 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.preferences;
+
+import com.android.browser.BrowserHomepagePreference;
+import com.android.browser.BrowserPreferencesPage;
+import com.android.browser.BrowserSettings;
+import com.android.browser.R;
+
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.util.Log;
+
+public class PageContentPreferencesFragment extends PreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.page_content_preferences);
+
+ Preference e = findPreference(BrowserSettings.PREF_HOMEPAGE);
+ e.setOnPreferenceChangeListener(this);
+ e.setSummary(getPreferenceScreen().getSharedPreferences()
+ .getString(BrowserSettings.PREF_HOMEPAGE, null));
+ ((BrowserHomepagePreference) e).setCurrentPage(
+ getActivity().getIntent().getStringExtra(BrowserPreferencesPage.CURRENT_PAGE));
+
+ e = findPreference(BrowserSettings.PREF_TEXT_SIZE);
+ e.setOnPreferenceChangeListener(this);
+ e.setSummary(getVisualTextSizeName(
+ getPreferenceScreen().getSharedPreferences()
+ .getString(BrowserSettings.PREF_TEXT_SIZE, null)) );
+
+ e = findPreference(BrowserSettings.PREF_DEFAULT_ZOOM);
+ e.setOnPreferenceChangeListener(this);
+ e.setSummary(getVisualDefaultZoomName(
+ getPreferenceScreen().getSharedPreferences()
+ .getString(BrowserSettings.PREF_DEFAULT_ZOOM, null)) );
+
+ e = findPreference(BrowserSettings.PREF_DEFAULT_TEXT_ENCODING);
+ e.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference pref, Object objValue) {
+ if (getActivity() == null) {
+ // We aren't attached, so don't accept preferences changes from the
+ // invisible UI.
+ Log.w("PageContentPreferencesFragment", "onPreferenceChange called from detached fragment!");
+ return false;
+ }
+
+ if (pref.getKey().equals(BrowserSettings.PREF_HOMEPAGE)) {
+ String value = (String) objValue;
+ boolean needUpdate = value.indexOf(' ') != -1;
+ if (needUpdate) {
+ value = value.trim().replace(" ", "%20");
+ }
+ if (value.length() != 0 && Uri.parse(value).getScheme() == null) {
+ value = "http://" + value;
+ needUpdate = true;
+ }
+ // Set the summary value.
+ pref.setSummary(value);
+ if (needUpdate) {
+ // Update through the EditText control as it has a cached copy
+ // of the string and it will handle persisting the value
+ ((EditTextPreference) pref).setText(value);
+
+ // as we update the value above, we need to return false
+ // here so that setText() is not called by EditTextPref
+ // with the old value.
+ return false;
+ } else {
+ return true;
+ }
+ } else if (pref.getKey().equals(BrowserSettings.PREF_TEXT_SIZE)) {
+ pref.setSummary(getVisualTextSizeName((String) objValue));
+ return true;
+ } else if (pref.getKey().equals(BrowserSettings.PREF_DEFAULT_ZOOM)) {
+ pref.setSummary(getVisualDefaultZoomName((String) objValue));
+ return true;
+ } else if (pref.getKey().equals(BrowserSettings.PREF_DEFAULT_TEXT_ENCODING)) {
+ pref.setSummary((String) objValue);
+ return true;
+ }
+
+ return false;
+ }
+
+ private CharSequence getVisualTextSizeName(String enumName) {
+ Resources res = getActivity().getResources();
+ CharSequence[] visualNames = res.getTextArray(R.array.pref_text_size_choices);
+ CharSequence[] enumNames = res.getTextArray(R.array.pref_text_size_values);
+
+ // Sanity check
+ if (visualNames.length != enumNames.length) {
+ return "";
+ }
+
+ int length = enumNames.length;
+ for (int i = 0; i < length; i++) {
+ if (enumNames[i].equals(enumName)) {
+ return visualNames[i];
+ }
+ }
+
+ return "";
+ }
+
+ private CharSequence getVisualDefaultZoomName(String enumName) {
+ Resources res = getActivity().getResources();
+ CharSequence[] visualNames = res.getTextArray(R.array.pref_default_zoom_choices);
+ CharSequence[] enumNames = res.getTextArray(R.array.pref_default_zoom_values);
+
+ // Sanity check
+ if (visualNames.length != enumNames.length) {
+ return "";
+ }
+
+ int length = enumNames.length;
+ for (int i = 0; i < length; i++) {
+ if (enumNames[i].equals(enumName)) {
+ return visualNames[i];
+ }
+ }
+
+ return "";
+ }
+} \ No newline at end of file
diff --git a/src/com/android/browser/preferences/PersonalPreferencesFragment.java b/src/com/android/browser/preferences/PersonalPreferencesFragment.java
new file mode 100644
index 000000000..a0c8ea022
--- /dev/null
+++ b/src/com/android/browser/preferences/PersonalPreferencesFragment.java
@@ -0,0 +1,371 @@
+/*
+ * 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.preferences;
+
+import com.android.browser.BrowserBookmarksPage;
+import com.android.browser.BrowserSettings;
+import com.android.browser.R;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.OperationApplicationException;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.ChromeSyncColumns;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+
+public class PersonalPreferencesFragment extends PreferenceFragment
+ implements OnPreferenceClickListener {
+ static final String TAG = "PersonalPreferencesFragment";
+
+ static final String PREF_CHROME_SYNC = "sync_with_chrome";
+
+ Preference mChromeSync;
+ boolean mEnabled;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.personal_preferences);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Setup the proper state for the sync with chrome item
+ Context context = getActivity();
+ mChromeSync = findPreference(PREF_CHROME_SYNC);
+ refreshUi(context);
+ }
+
+ private class GetAccountsTask extends AsyncTask<Void, Void, Void> {
+ private Context mContext;
+
+ GetAccountsTask(Context ctx) {
+ mContext = ctx;
+ }
+
+ protected Void doInBackground(Void... unused) {
+ AccountManager am = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
+ Account[] accounts = am.getAccountsByType("com.google");
+ if (accounts == null || accounts.length == 0) {
+ // No Google accounts setup, don't offer Chrome sync
+ if (mChromeSync != null) {
+ getPreferenceScreen().removePreference(mChromeSync);
+ }
+ } else {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ Bundle args = mChromeSync.getExtras();
+ args.putParcelableArray("accounts", accounts);
+ mEnabled = BrowserContract.Settings.isSyncEnabled(mContext);
+ if (!mEnabled) {
+ // Google accounts are present, but Chrome sync isn't enabled yet.
+ // Setup a link to the enable wizard
+ mChromeSync.setSummary(R.string.pref_personal_sync_with_chrome_summary);
+ } else {
+ // Chrome sync is enabled, setup a link to account switcher
+ String accountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME,
+ null);
+ mChromeSync.setSummary(accountName);
+ args.putString("curAccount", accountName);
+ }
+ mChromeSync.setOnPreferenceClickListener(PersonalPreferencesFragment.this);
+ }
+
+ return null;
+ }
+ }
+
+ void refreshUi(Context context) {
+ new GetAccountsTask(context).execute();
+
+ PreferenceScreen autoFillSettings =
+ (PreferenceScreen)findPreference(BrowserSettings.PREF_AUTOFILL_PROFILE);
+ autoFillSettings.setDependency(BrowserSettings.PREF_AUTOFILL_ENABLED);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ Fragment frag;
+ if (mEnabled) {
+ frag = new AccountChooserDialog();
+ } else {
+ frag = new ImportWizardDialog();
+ }
+ frag.setArguments(preference.getExtras());
+ getFragmentManager().openTransaction()
+ .add(frag, null)
+ .commit();
+ return true;
+ }
+
+ final class AccountChooserDialog extends DialogFragment
+ implements DialogInterface.OnClickListener {
+
+ AlertDialog mDialog;
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Bundle args = getArguments();
+ Account[] accounts = (Account[]) args.getParcelableArray("accounts");
+ String curAccount = args.getString("curAccount");
+ int length = accounts.length;
+ int curAccountOffset = 0;
+ CharSequence[] accountNames = new CharSequence[length];
+ for (int i = 0; i < length; i++) {
+ String name = accounts[i].name;
+ if (name.equals(curAccount)) {
+ curAccountOffset = i;
+ }
+ accountNames[i] = name;
+ }
+
+ mDialog = new AlertDialog.Builder(getActivity())
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle("Choose account") // STOPSHIP localize
+ .setSingleChoiceItems(accountNames, curAccountOffset, this)
+ .create();
+ return mDialog;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String accountName = mDialog.getListView().getAdapter().getItem(which).toString();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ prefs.edit().putString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, accountName).apply();
+ refreshUi(getActivity());
+ dismiss();
+ }
+ }
+
+ final class ImportWizardDialog extends DialogFragment implements OnClickListener {
+ View mRemoveButton;
+ View mCancelButton;
+ String mDefaultAccount;
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Context context = getActivity();
+ Dialog dialog = new Dialog(context);
+ dialog.setTitle(R.string.import_bookmarks_dialog_title);
+ dialog.setContentView(R.layout.import_bookmarks_dialog);
+ mRemoveButton = dialog.findViewById(R.id.remove);
+ mRemoveButton.setOnClickListener(this);
+ mCancelButton = dialog.findViewById(R.id.cancel);
+ mCancelButton.setOnClickListener(this);
+
+ LayoutInflater inflater = dialog.getLayoutInflater();
+ LinearLayout accountList = (LinearLayout) dialog.findViewById(R.id.accountList);
+ Account[] accounts = (Account[]) getArguments().getParcelableArray("accounts");
+ mDefaultAccount = accounts[0].name;
+ int length = accounts.length;
+ for (int i = 0; i < length; i++) {
+ Button button = (Button) inflater.inflate(R.layout.import_bookmarks_dialog_button,
+ null);
+ button.setText(context.getString(R.string.import_bookmarks_dialog_import,
+ accounts[i].name));
+ button.setTag(accounts[i].name);
+ button.setOnClickListener(this);
+ accountList.addView(button);
+ }
+
+ return dialog;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view == mCancelButton) {
+ dismiss();
+ return;
+ }
+
+ ContentResolver resolver = getActivity().getContentResolver();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ String accountName;
+ if (view == mRemoveButton) {
+ // The user chose to remove their old bookmarks, delete them now
+ resolver.delete(Bookmarks.CONTENT_URI,
+ Bookmarks.PARENT + "=1 AND " + Bookmarks.ACCOUNT_NAME + " IS NULL", null);
+ accountName = mDefaultAccount;
+ } else {
+ // The user chose to migrate their old bookmarks to the account they're syncing
+ accountName = view.getTag().toString();
+ migrateBookmarks(resolver, accountName);
+ }
+
+ // Record the fact that we turned on sync
+ BrowserContract.Settings.setSyncEnabled(getActivity(), true);
+ prefs.edit()
+ .putString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, "com.google")
+ .putString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, accountName)
+ .apply();
+
+ // Enable bookmark sync on all accounts
+ Account[] accounts = (Account[]) getArguments().getParcelableArray("accounts");
+ for (Account account : accounts) {
+ ContentResolver.setIsSyncable(account, BrowserContract.AUTHORITY, 1);
+ }
+
+ refreshUi(getActivity());
+ dismiss();
+ }
+
+ /**
+ * Migrates bookmarks to the given account
+ */
+ void migrateBookmarks(ContentResolver resolver, String accountName) {
+ Cursor cursor = null;
+ try {
+ // Re-parent the bookmarks in the default root folder
+ cursor = resolver.query(Bookmarks.CONTENT_URI, new String[] { Bookmarks._ID },
+ Bookmarks.ACCOUNT_NAME + " =? AND " +
+ ChromeSyncColumns.SERVER_UNIQUE + " =?",
+ new String[] { accountName,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR },
+ null);
+ ContentValues values = new ContentValues();
+ if (cursor == null || !cursor.moveToFirst()) {
+ // The root folders don't exist for the account, create them now
+ ArrayList<ContentProviderOperation> ops =
+ new ArrayList<ContentProviderOperation>();
+
+ // Chrome sync root folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_ROOT);
+ values.put(Bookmarks.TITLE, "Google Chrome");
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(
+ Bookmarks.CONTENT_URI.buildUpon().appendQueryParameter(
+ BrowserContract.CALLER_IS_SYNCADAPTER, "true").build())
+ .withValues(values)
+ .build());
+
+ // Bookmarks folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Bookmarks");
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withValueBackReference(Bookmarks.PARENT, 0)
+ .build());
+
+ // Bookmarks Bar folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR);
+ values.put(Bookmarks.TITLE, "Bookmarks Bar");
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withValueBackReference(Bookmarks.PARENT, 1)
+ .build());
+
+ // Other Bookmarks folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Other Bookmarks");
+ values.put(Bookmarks.POSITION, 1000);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withValueBackReference(Bookmarks.PARENT, 1)
+ .build());
+
+ // Re-parent the existing bookmarks to the newly create bookmarks bar folder
+ ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI)
+ .withValueBackReference(Bookmarks.PARENT, 2)
+ .withSelection(Bookmarks.PARENT + "=?",
+ new String[] { Integer.toString(1) })
+ .build());
+
+ // Mark all non-root folder items as belonging to the new account
+ values.clear();
+ values.put(Bookmarks.ACCOUNT_TYPE, "com.google");
+ values.put(Bookmarks.ACCOUNT_NAME, accountName);
+ ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withSelection(Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
+ Bookmarks._ID + "<>1", null)
+ .build());
+
+ try {
+ resolver.applyBatch(BrowserContract.AUTHORITY, ops);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to create root folder for account " + accountName, e);
+ return;
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, "failed to create root folder for account " + accountName, e);
+ return;
+ }
+ } else {
+ values.put(Bookmarks.PARENT, cursor.getLong(0));
+ resolver.update(Bookmarks.CONTENT_URI, values, Bookmarks.PARENT + "=?",
+ new String[] { Integer.toString(1) });
+
+ // Mark all bookmarks at all levels as part of the new account
+ values.clear();
+ values.put(Bookmarks.ACCOUNT_TYPE, "com.google");
+ values.put(Bookmarks.ACCOUNT_NAME, accountName);
+ resolver.update(Bookmarks.CONTENT_URI, values,
+ Bookmarks.ACCOUNT_NAME + " IS NULL AND " + Bookmarks._ID + "<>1",
+ null);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
+ }
+}
diff --git a/src/com/android/browser/preferences/PrivacyPreferencesFragment.java b/src/com/android/browser/preferences/PrivacyPreferencesFragment.java
new file mode 100644
index 000000000..79f20842e
--- /dev/null
+++ b/src/com/android/browser/preferences/PrivacyPreferencesFragment.java
@@ -0,0 +1,55 @@
+/*
+ * 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.preferences;
+
+import com.android.browser.BrowserSettings;
+import com.android.browser.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+
+public class PrivacyPreferencesFragment extends PreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.privacy_preferences);
+
+ Preference e = findPreference(BrowserSettings.PREF_CLEAR_HISTORY);
+ e.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference pref, Object objValue) {
+ if (pref.getKey().equals(BrowserSettings.PREF_CLEAR_HISTORY)
+ && ((Boolean) objValue).booleanValue() == true) {
+ // Need to tell the browser to remove the parent/child relationship
+ // between tabs
+ getActivity().setResult(Activity.RESULT_OK, (new Intent()).putExtra(Intent.EXTRA_TEXT,
+ pref.getKey()));
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/com/android/browser/preferences/SecurityPreferencesFragment.java b/src/com/android/browser/preferences/SecurityPreferencesFragment.java
new file mode 100644
index 000000000..d20a50c9a
--- /dev/null
+++ b/src/com/android/browser/preferences/SecurityPreferencesFragment.java
@@ -0,0 +1,32 @@
+/*
+ * 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.preferences;
+
+import com.android.browser.R;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+public class SecurityPreferencesFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.security_preferences);
+ }
+}
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
new file mode 100644
index 000000000..8d9f1fe4e
--- /dev/null
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -0,0 +1,1186 @@
+/*
+ * Copyright (C) 2010 he 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.provider;
+
+import com.android.browser.R;
+import com.android.common.content.SyncStateContentProviderHelper;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Accounts;
+import android.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.ChromeSyncColumns;
+import android.provider.BrowserContract.Combined;
+import android.provider.BrowserContract.History;
+import android.provider.BrowserContract.Images;
+import android.provider.BrowserContract.Searches;
+import android.provider.BrowserContract.Settings;
+import android.provider.BrowserContract.SyncState;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.SyncStateContract;
+import android.text.TextUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+
+public class BrowserProvider2 extends SQLiteContentProvider {
+
+ static final String LEGACY_AUTHORITY = "browser";
+ static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder().authority(LEGACY_AUTHORITY).build();
+
+ static final String TABLE_BOOKMARKS = "bookmarks";
+ static final String TABLE_HISTORY = "history";
+ static final String TABLE_IMAGES = "images";
+ static final String TABLE_SEARCHES = "searches";
+ static final String TABLE_SYNC_STATE = "syncstate";
+ static final String TABLE_SETTINGS = "settings";
+ static final String VIEW_COMBINED = "combined";
+
+ static final String TABLE_BOOKMARKS_JOIN_IMAGES = "bookmarks LEFT OUTER JOIN images " +
+ "ON bookmarks.url = images." + Images.URL;
+ static final String TABLE_HISTORY_JOIN_IMAGES = "history LEFT OUTER JOIN images " +
+ "ON history.url = images." + Images.URL;
+
+ static final String DEFAULT_SORT_HISTORY = History.DATE_LAST_VISITED + " DESC";
+
+ static final String DEFAULT_SORT_SEARCHES = Searches.DATE + " DESC";
+
+ static final int BOOKMARKS = 1000;
+ static final int BOOKMARKS_ID = 1001;
+ static final int BOOKMARKS_FOLDER = 1002;
+ static final int BOOKMARKS_FOLDER_ID = 1003;
+
+ static final int HISTORY = 2000;
+ static final int HISTORY_ID = 2001;
+
+ static final int SEARCHES = 3000;
+ static final int SEARCHES_ID = 3001;
+
+ static final int SYNCSTATE = 4000;
+ static final int SYNCSTATE_ID = 4001;
+
+ static final int IMAGES = 5000;
+
+ static final int COMBINED = 6000;
+ static final int COMBINED_ID = 6001;
+
+ static final int ACCOUNTS = 7000;
+
+ static final int SETTINGS = 8000;
+
+ public static final long FIXED_ID_ROOT = 1;
+
+ // Default sort order for unsync'd bookmarks
+ static final String DEFAULT_BOOKMARKS_SORT_ORDER =
+ Bookmarks.IS_FOLDER + " DESC, position ASC, _id ASC";
+
+ // Default sort order for sync'd bookmarks
+ static final String DEFAULT_BOOKMARKS_SORT_ORDER_SYNC = "position ASC, _id ASC";
+
+ static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+ static final HashMap<String, String> ACCOUNTS_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> OTHER_BOOKMARKS_PROJECTION_MAP =
+ new HashMap<String, String>();
+ static final HashMap<String, String> HISTORY_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> IMAGES_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> COMBINED_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> SEARCHES_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> SETTINGS_PROJECTION_MAP = new HashMap<String, String>();
+
+ static {
+ final UriMatcher matcher = URI_MATCHER;
+ final String authority = BrowserContract.AUTHORITY;
+ matcher.addURI(authority, "accounts", ACCOUNTS);
+ matcher.addURI(authority, "bookmarks", BOOKMARKS);
+ matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID);
+ matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER);
+ matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
+ matcher.addURI(authority, "history", HISTORY);
+ matcher.addURI(authority, "history/#", HISTORY_ID);
+ matcher.addURI(authority, "searches", SEARCHES);
+ matcher.addURI(authority, "searches/#", SEARCHES_ID);
+ matcher.addURI(authority, "syncstate", SYNCSTATE);
+ matcher.addURI(authority, "syncstate/#", SYNCSTATE_ID);
+ matcher.addURI(authority, "images", IMAGES);
+ matcher.addURI(authority, "combined", COMBINED);
+ matcher.addURI(authority, "combined/#", COMBINED_ID);
+ matcher.addURI(authority, "settings", SETTINGS);
+
+ // Projection maps
+ HashMap<String, String> map;
+
+ // Accounts
+ map = ACCOUNTS_PROJECTION_MAP;
+ map.put(Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_TYPE);
+ map.put(Accounts.ACCOUNT_NAME, Accounts.ACCOUNT_NAME);
+
+ // Bookmarks
+ map = BOOKMARKS_PROJECTION_MAP;
+ map.put(Bookmarks._ID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID));
+ map.put(Bookmarks.TITLE, Bookmarks.TITLE);
+ map.put(Bookmarks.URL, Bookmarks.URL);
+ map.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
+ map.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL);
+ map.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON);
+ map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER);
+ map.put(Bookmarks.PARENT, Bookmarks.PARENT);
+ map.put(Bookmarks.POSITION, Bookmarks.POSITION);
+ map.put(Bookmarks.INSERT_AFTER, Bookmarks.INSERT_AFTER);
+ map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED);
+ map.put(Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_NAME);
+ map.put(Bookmarks.ACCOUNT_TYPE, Bookmarks.ACCOUNT_TYPE);
+ map.put(Bookmarks.SOURCE_ID, Bookmarks.SOURCE_ID);
+ map.put(Bookmarks.VERSION, Bookmarks.VERSION);
+ map.put(Bookmarks.DATE_CREATED, Bookmarks.DATE_CREATED);
+ map.put(Bookmarks.DATE_MODIFIED, Bookmarks.DATE_MODIFIED);
+ map.put(Bookmarks.DIRTY, Bookmarks.DIRTY);
+ map.put(Bookmarks.SYNC1, Bookmarks.SYNC1);
+ map.put(Bookmarks.SYNC2, Bookmarks.SYNC2);
+ map.put(Bookmarks.SYNC3, Bookmarks.SYNC3);
+ map.put(Bookmarks.SYNC4, Bookmarks.SYNC4);
+ map.put(Bookmarks.SYNC5, Bookmarks.SYNC5);
+ map.put(Bookmarks.PARENT_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
+ " FROM " + TABLE_BOOKMARKS + " A WHERE " +
+ "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.PARENT +
+ ") AS " + Bookmarks.PARENT_SOURCE_ID);
+ map.put(Bookmarks.INSERT_AFTER_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
+ " FROM " + TABLE_BOOKMARKS + " A WHERE " +
+ "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.INSERT_AFTER +
+ ") AS " + Bookmarks.INSERT_AFTER_SOURCE_ID);
+
+ // Other bookmarks
+ OTHER_BOOKMARKS_PROJECTION_MAP.putAll(BOOKMARKS_PROJECTION_MAP);
+ OTHER_BOOKMARKS_PROJECTION_MAP.put(Bookmarks.POSITION,
+ Long.toString(Long.MAX_VALUE) + " AS " + Bookmarks.POSITION);
+
+ // History
+ map = HISTORY_PROJECTION_MAP;
+ map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID));
+ map.put(History.TITLE, History.TITLE);
+ map.put(History.URL, History.URL);
+ map.put(History.FAVICON, History.FAVICON);
+ map.put(History.THUMBNAIL, History.THUMBNAIL);
+ map.put(History.TOUCH_ICON, History.TOUCH_ICON);
+ map.put(History.DATE_CREATED, History.DATE_CREATED);
+ map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
+ map.put(History.VISITS, History.VISITS);
+ map.put(History.USER_ENTERED, History.USER_ENTERED);
+
+ // Sync state
+ map = SYNC_STATE_PROJECTION_MAP;
+ map.put(SyncState._ID, SyncState._ID);
+ map.put(SyncState.ACCOUNT_NAME, SyncState.ACCOUNT_NAME);
+ map.put(SyncState.ACCOUNT_TYPE, SyncState.ACCOUNT_TYPE);
+ map.put(SyncState.DATA, SyncState.DATA);
+
+ // Images
+ map = IMAGES_PROJECTION_MAP;
+ map.put(Images.URL, Images.URL);
+ map.put(Images.FAVICON, Images.FAVICON);
+ map.put(Images.THUMBNAIL, Images.THUMBNAIL);
+ map.put(Images.TOUCH_ICON, Images.TOUCH_ICON);
+
+ // Combined history half
+ map = COMBINED_PROJECTION_MAP;
+ map.put(Combined._ID, Combined._ID);
+ map.put(Combined.TITLE, Combined.TITLE);
+ map.put(Combined.URL, Combined.URL);
+ map.put(Combined.DATE_CREATED, Combined.DATE_CREATED);
+ map.put(Combined.DATE_LAST_VISITED, Combined.DATE_LAST_VISITED);
+ map.put(Combined.IS_BOOKMARK, Combined.IS_BOOKMARK);
+ map.put(Combined.VISITS, Combined.VISITS);
+ map.put(Combined.FAVICON, Combined.FAVICON);
+ map.put(Combined.THUMBNAIL, Combined.THUMBNAIL);
+ map.put(Combined.TOUCH_ICON, Combined.TOUCH_ICON);
+ map.put(Combined.USER_ENTERED, Combined.USER_ENTERED);
+
+ // Searches
+ map = SEARCHES_PROJECTION_MAP;
+ map.put(Searches._ID, Searches._ID);
+ map.put(Searches.SEARCH, Searches.SEARCH);
+ map.put(Searches.DATE, Searches.DATE);
+
+ // Settings
+ map = SETTINGS_PROJECTION_MAP;
+ map.put(Settings.KEY, Settings.KEY);
+ map.put(Settings.VALUE, Settings.VALUE);
+ }
+
+ static final String bookmarkOrHistoryColumn(String column) {
+ return "CASE WHEN bookmarks." + column + " IS NOT NULL THEN " +
+ "bookmarks." + column + " ELSE history." + column + " END AS " + column;
+ }
+
+ static final String qualifyColumn(String table, String column) {
+ return table + "." + column + " AS " + column;
+ }
+
+ DatabaseHelper mOpenHelper;
+ SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
+
+ final class DatabaseHelper extends SQLiteOpenHelper {
+ static final String DATABASE_NAME = "browser2.db";
+ static final int DATABASE_VERSION = 25;
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
+ Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Bookmarks.TITLE + " TEXT," +
+ Bookmarks.URL + " TEXT," +
+ Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," +
+ Bookmarks.PARENT + " INTEGER," +
+ Bookmarks.POSITION + " INTEGER NOT NULL," +
+ Bookmarks.INSERT_AFTER + " INTEGER," +
+ Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0," +
+ Bookmarks.ACCOUNT_NAME + " TEXT," +
+ Bookmarks.ACCOUNT_TYPE + " TEXT," +
+ Bookmarks.SOURCE_ID + " TEXT," +
+ Bookmarks.VERSION + " INTEGER NOT NULL DEFAULT 1," +
+ Bookmarks.DATE_CREATED + " INTEGER," +
+ Bookmarks.DATE_MODIFIED + " INTEGER," +
+ Bookmarks.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
+ Bookmarks.SYNC1 + " TEXT," +
+ Bookmarks.SYNC2 + " TEXT," +
+ Bookmarks.SYNC3 + " TEXT," +
+ Bookmarks.SYNC4 + " TEXT," +
+ Bookmarks.SYNC5 + " TEXT" +
+ ");");
+
+ // TODO indices
+
+ db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
+ History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ History.TITLE + " TEXT," +
+ History.URL + " TEXT NOT NULL," +
+ History.DATE_CREATED + " INTEGER," +
+ History.DATE_LAST_VISITED + " INTEGER," +
+ History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
+ History.USER_ENTERED + " INTEGER" +
+ ");");
+
+ db.execSQL("CREATE TABLE " + TABLE_IMAGES + " (" +
+ Images.URL + " TEXT UNIQUE NOT NULL," +
+ Images.FAVICON + " BLOB," +
+ Images.THUMBNAIL + " BLOB," +
+ Images.TOUCH_ICON + " BLOB" +
+ ");");
+ db.execSQL("CREATE INDEX imagesUrlIndex ON " + TABLE_IMAGES +
+ "(" + Images.URL + ")");
+
+ db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" +
+ Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Searches.SEARCH + " TEXT," +
+ Searches.DATE + " LONG" +
+ ");");
+
+ db.execSQL("CREATE TABLE " + TABLE_SETTINGS + " (" +
+ Settings.KEY + " TEXT PRIMARY KEY," +
+ Settings.VALUE + " TEXT NOT NULL" +
+ ");");
+
+ db.execSQL("CREATE VIEW " + VIEW_COMBINED + " AS " +
+ "SELECT " +
+ bookmarkOrHistoryColumn(Combined._ID) + ", " +
+ bookmarkOrHistoryColumn(Combined.TITLE) + ", " +
+ qualifyColumn(TABLE_HISTORY, Combined.URL) + ", " +
+ qualifyColumn(TABLE_HISTORY, Combined.DATE_CREATED) + ", " +
+ Combined.DATE_LAST_VISITED + ", " +
+ "CASE WHEN bookmarks._id IS NOT NULL THEN 1 ELSE 0 END AS " + Combined.IS_BOOKMARK + ", " +
+ Combined.VISITS + ", " +
+ Combined.FAVICON + ", " +
+ Combined.THUMBNAIL + ", " +
+ Combined.TOUCH_ICON + ", " +
+ "NULL AS " + Combined.USER_ENTERED + " "+
+ "FROM history LEFT OUTER JOIN bookmarks ON history.url = bookmarks.url LEFT OUTER JOIN images ON history.url = images.url_key " +
+
+ "UNION ALL " +
+
+ "SELECT " +
+ Combined._ID + ", " +
+ Combined.TITLE + ", " +
+ Combined.URL + ", " +
+ Combined.DATE_CREATED + ", " +
+ "NULL AS " + Combined.DATE_LAST_VISITED + ", "+
+ "1 AS " + Combined.IS_BOOKMARK + ", " +
+ "0 AS " + Combined.VISITS + ", "+
+ Combined.FAVICON + ", " +
+ Combined.THUMBNAIL + ", " +
+ Combined.TOUCH_ICON + ", " +
+ "NULL AS " + Combined.USER_ENTERED + " "+
+ "FROM bookmarks LEFT OUTER JOIN images ON bookmarks.url = images.url_key WHERE url NOT IN (SELECT url FROM history)");
+
+ mSyncHelper.createDatabase(db);
+
+ createDefaultBookmarks(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO write upgrade logic
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_SETTINGS);
+ db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
+ mSyncHelper.onAccountsChanged(db, new Account[] {}); // remove all sync info
+ onCreate(db);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ mSyncHelper.onDatabaseOpened(db);
+ }
+
+ private void createDefaultBookmarks(SQLiteDatabase db) {
+ ContentValues values = new ContentValues();
+ // TODO figure out how to deal with localization for the defaults
+
+ // Bookmarks folder
+ values.put(Bookmarks._ID, FIXED_ID_ROOT);
+ values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Bookmarks");
+ values.putNull(Bookmarks.PARENT);
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ db.insertOrThrow(TABLE_BOOKMARKS, null, values);
+
+ addDefaultBookmarks(db, FIXED_ID_ROOT);
+ }
+
+ private void addDefaultBookmarks(SQLiteDatabase db, long parentId) {
+ Resources res = getContext().getResources();
+ final CharSequence[] bookmarks = res.getTextArray(
+ R.array.bookmarks);
+ int size = bookmarks.length;
+ TypedArray preloads = res.obtainTypedArray(R.array.bookmark_preloads);
+ try {
+ String parent = Long.toString(parentId);
+ String now = Long.toString(System.currentTimeMillis());
+ for (int i = 0; i < size; i = i + 2) {
+ CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(),
+ bookmarks[i + 1]);
+ db.execSQL("INSERT INTO bookmarks (" +
+ Bookmarks.TITLE + ", " +
+ Bookmarks.URL + ", " +
+ Bookmarks.IS_FOLDER + "," +
+ Bookmarks.PARENT + "," +
+ Bookmarks.POSITION + "," +
+ Bookmarks.DATE_CREATED +
+ ") VALUES (" +
+ "'" + bookmarks[i] + "', " +
+ "'" + bookmarkDestination + "', " +
+ "0," +
+ parent + "," +
+ Integer.toString(i) + "," +
+ now +
+ ");");
+
+ int faviconId = preloads.getResourceId(i, 0);
+ int thumbId = preloads.getResourceId(i + 1, 0);
+ byte[] thumb = null, favicon = null;
+ try {
+ thumb = readRaw(res, thumbId);
+ } catch (IOException e) {
+ }
+ try {
+ favicon = readRaw(res, faviconId);
+ } catch (IOException e) {
+ }
+ if (thumb != null || favicon != null) {
+ ContentValues imageValues = new ContentValues();
+ imageValues.put(Images.URL, bookmarkDestination.toString());
+ if (favicon != null) {
+ imageValues.put(Images.FAVICON, favicon);
+ }
+ if (thumb != null) {
+ imageValues.put(Images.THUMBNAIL, thumb);
+ }
+ db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ }
+ }
+
+ private byte[] readRaw(Resources res, int id) throws IOException {
+ InputStream is = res.openRawResource(id);
+ try {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];
+ int read;
+ while ((read = is.read(buf)) > 0) {
+ bos.write(buf, 0, read);
+ }
+ bos.flush();
+ return bos.toByteArray();
+ } finally {
+ is.close();
+ }
+ }
+
+ // XXX: This is a major hack to remove our dependency on gsf constants and
+ // its content provider. http://b/issue?id=2425179
+ private String getClientId(ContentResolver cr) {
+ String ret = "android-google";
+ Cursor c = null;
+ try {
+ c = cr.query(Uri.parse("content://com.google.settings/partner"),
+ new String[] { "value" }, "name='client_id'", null, null);
+ if (c != null && c.moveToNext()) {
+ ret = c.getString(0);
+ }
+ } catch (RuntimeException ex) {
+ // fall through to return the default
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return ret;
+ }
+
+ private CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) {
+ StringBuffer sb = new StringBuffer();
+ int lastCharLoc = 0;
+
+ final String client_id = getClientId(context.getContentResolver());
+
+ for (int i = 0; i < srcString.length(); ++i) {
+ char c = srcString.charAt(i);
+ if (c == '{') {
+ sb.append(srcString.subSequence(lastCharLoc, i));
+ lastCharLoc = i;
+ inner:
+ for (int j = i; j < srcString.length(); ++j) {
+ char k = srcString.charAt(j);
+ if (k == '}') {
+ String propertyKeyValue = srcString.subSequence(i + 1, j).toString();
+ if (propertyKeyValue.equals("CLIENT_ID")) {
+ sb.append(client_id);
+ } else {
+ sb.append("unknown");
+ }
+ lastCharLoc = j + 1;
+ i = j;
+ break inner;
+ }
+ }
+ }
+ }
+ if (srcString.length() - lastCharLoc > 0) {
+ // Put on the tail, if there is one
+ sb.append(srcString.subSequence(lastCharLoc, srcString.length()));
+ }
+ return sb;
+ }
+ }
+
+ @Override
+ public SQLiteOpenHelper getDatabaseHelper(Context context) {
+ synchronized (this) {
+ if (mOpenHelper == null) {
+ mOpenHelper = new DatabaseHelper(context);
+ }
+ return mOpenHelper;
+ }
+ }
+
+ @Override
+ public boolean isCallerSyncAdapter(Uri uri) {
+ return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false);
+ }
+
+ @Override
+ public void notifyChange(boolean callerIsSyncAdapter) {
+ ContentResolver resolver = getContext().getContentResolver();
+ resolver.notifyChange(BrowserContract.AUTHORITY_URI, null, !callerIsSyncAdapter);
+ resolver.notifyChange(LEGACY_AUTHORITY_URI, null, !callerIsSyncAdapter);
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ final int match = URI_MATCHER.match(uri);
+ switch (match) {
+ case BOOKMARKS:
+ return Bookmarks.CONTENT_TYPE;
+ case BOOKMARKS_ID:
+ return Bookmarks.CONTENT_ITEM_TYPE;
+ case HISTORY:
+ return History.CONTENT_TYPE;
+ case HISTORY_ID:
+ return History.CONTENT_ITEM_TYPE;
+ case SEARCHES:
+ return Searches.CONTENT_TYPE;
+ case SEARCHES_ID:
+ return Searches.CONTENT_ITEM_TYPE;
+// case SUGGEST:
+// return SearchManager.SUGGEST_MIME_TYPE;
+ }
+ return null;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ final int match = URI_MATCHER.match(uri);
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
+ switch (match) {
+ case ACCOUNTS: {
+ qb.setTables(TABLE_BOOKMARKS);
+ qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
+ qb.setDistinct(true);
+ qb.appendWhere(Bookmarks.ACCOUNT_NAME + " IS NOT NULL");
+ break;
+ }
+
+ case BOOKMARKS_FOLDER_ID:
+ case BOOKMARKS_ID:
+ case BOOKMARKS: {
+ // Only show deleted bookmarks if requested to do so
+ if (!uri.getBooleanQueryParameter(Bookmarks.QUERY_PARAMETER_SHOW_DELETED, false)) {
+ selection = DatabaseUtils.concatenateWhere(
+ Bookmarks.IS_DELETED + "=0", selection);
+ }
+
+ if (match == BOOKMARKS_ID) {
+ // Tack on the ID of the specific bookmark requested
+ selection = DatabaseUtils.concatenateWhere(selection,
+ TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ } else if (match == BOOKMARKS_FOLDER_ID) {
+ // Tack on the ID of the specific folder requested
+ selection = DatabaseUtils.concatenateWhere(selection,
+ TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ }
+
+ // Look for account info
+ String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
+ String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
+ if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+ selection = DatabaseUtils.concatenateWhere(selection,
+ Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? ");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { accountType, accountName });
+ }
+
+ // Set a default sort order if one isn't specified
+ if (TextUtils.isEmpty(sortOrder)) {
+ if (!TextUtils.isEmpty(accountType)
+ && !TextUtils.isEmpty(accountName)) {
+ sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER_SYNC;
+ } else {
+ sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
+ }
+ }
+
+ qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+ qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
+ break;
+ }
+
+ case BOOKMARKS_FOLDER: {
+ // Don't allow selections to be applied to the default folder
+ if (!TextUtils.isEmpty(selection) || selectionArgs != null) {
+ throw new UnsupportedOperationException(
+ "selections aren't supported on this URI");
+ }
+
+ // Look for an account
+ boolean useAccount = false;
+ String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
+ String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
+ if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+ useAccount = true;
+ }
+
+ qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
+ String[] args;
+ String query;
+ // Set a default sort order if one isn't specified
+ if (TextUtils.isEmpty(sortOrder)) {
+ if (useAccount) {
+ sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER_SYNC;
+ } else {
+ sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
+ }
+ }
+ if (!useAccount) {
+ qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+ query = qb.buildQuery(projection,
+ Bookmarks.PARENT + "=? AND " + Bookmarks.IS_DELETED + "=0",
+ null, null, null, sortOrder, null);
+
+ args = new String[] { Long.toString(FIXED_ID_ROOT) };
+ } else {
+ qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+ String bookmarksBarQuery = qb.buildQuery(projection,
+ Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? " +
+ "AND parent = " +
+ "(SELECT _id FROM " + TABLE_BOOKMARKS + " WHERE " +
+ ChromeSyncColumns.SERVER_UNIQUE + "=" +
+ "'" + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "' " +
+ "AND account_type = ? AND account_name = ?) " +
+ "AND " + Bookmarks.IS_DELETED + "=0",
+ null, null, null, null, null);
+
+ qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
+ String otherBookmarksQuery = qb.buildQuery(projection,
+ Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=?" +
+ " AND " + ChromeSyncColumns.SERVER_UNIQUE + "=?",
+ null, null, null, null, null);
+
+ query = qb.buildUnionQuery(
+ new String[] { bookmarksBarQuery, otherBookmarksQuery },
+ sortOrder, limit);
+
+ args = new String[] {
+ accountType, accountName, accountType, accountName,
+ accountType, accountName, ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS,
+ };
+ }
+
+ Cursor cursor = db.rawQuery(query, args);
+ if (cursor != null) {
+ cursor.setNotificationUri(getContext().getContentResolver(),
+ BrowserContract.AUTHORITY_URI);
+ }
+ return cursor;
+ }
+
+ case HISTORY_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case HISTORY: {
+ if (sortOrder == null) {
+ sortOrder = DEFAULT_SORT_HISTORY;
+ }
+ qb.setProjectionMap(HISTORY_PROJECTION_MAP);
+ qb.setTables(TABLE_HISTORY_JOIN_IMAGES);
+ break;
+ }
+
+ case SEARCHES_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case SEARCHES: {
+ if (sortOrder == null) {
+ sortOrder = DEFAULT_SORT_SEARCHES;
+ }
+ qb.setTables(TABLE_SEARCHES);
+ qb.setProjectionMap(SEARCHES_PROJECTION_MAP);
+ break;
+ }
+
+ case SYNCSTATE: {
+ return mSyncHelper.query(db, projection, selection, selectionArgs, sortOrder);
+ }
+
+ case SYNCSTATE_ID: {
+ selection = appendAccountToSelection(uri, selection);
+ String selectionWithId =
+ (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
+ + (selection == null ? "" : " AND (" + selection + ")");
+ return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder);
+ }
+
+ case IMAGES: {
+ qb.setTables(TABLE_IMAGES);
+ qb.setProjectionMap(IMAGES_PROJECTION_MAP);
+ break;
+ }
+
+ case COMBINED_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, VIEW_COMBINED + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case COMBINED: {
+ qb.setTables(VIEW_COMBINED);
+ qb.setProjectionMap(COMBINED_PROJECTION_MAP);
+ break;
+ }
+
+ case SETTINGS: {
+ qb.setTables(TABLE_SETTINGS);
+ qb.setProjectionMap(SETTINGS_PROJECTION_MAP);
+ break;
+ }
+
+ default: {
+ throw new UnsupportedOperationException("Unknown URL " + uri.toString());
+ }
+ }
+
+ Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder,
+ limit);
+ cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI);
+ return cursor;
+ }
+
+ @Override
+ public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+ boolean callerIsSyncAdapter) {
+ final int match = URI_MATCHER.match(uri);
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ switch (match) {
+ case BOOKMARKS_ID:
+ case BOOKMARKS: {
+ //TODO cascade deletes down from folders
+ if (!callerIsSyncAdapter) {
+ // If the caller isn't a sync adapter just go through and update all the
+ // bookmarks to have the deleted flag set.
+ ContentValues values = new ContentValues();
+ values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
+ values.put(Bookmarks.IS_DELETED, 1);
+ return updateInTransaction(uri, values, selection, selectionArgs,
+ callerIsSyncAdapter);
+ } else {
+ // Sync adapters are allowed to actually delete things
+ if (match == BOOKMARKS_ID) {
+ selection = DatabaseUtils.concatenateWhere(selection,
+ TABLE_BOOKMARKS + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ }
+ return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
+ }
+ }
+
+ case HISTORY_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case HISTORY: {
+ return db.delete(TABLE_HISTORY, selection, selectionArgs);
+ }
+
+ case SEARCHES_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case SEARCHES: {
+ return db.delete(TABLE_SEARCHES, selection, selectionArgs);
+ }
+
+ case SYNCSTATE: {
+ return mSyncHelper.delete(db, selection, selectionArgs);
+ }
+ case SYNCSTATE_ID: {
+ String selectionWithId =
+ (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
+ + (selection == null ? "" : " AND (" + selection + ")");
+ return mSyncHelper.delete(db, selectionWithId, selectionArgs);
+ }
+ }
+ throw new UnsupportedOperationException("Unknown update URI " + uri);
+ }
+
+ @Override
+ public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
+ final int match = URI_MATCHER.match(uri);
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long id = -1;
+ switch (match) {
+ case BOOKMARKS: {
+ // Mark rows dirty if they're not coming from a sync adapter
+ if (!callerIsSyncAdapter) {
+ long now = System.currentTimeMillis();
+ values.put(Bookmarks.DATE_CREATED, now);
+ values.put(Bookmarks.DATE_MODIFIED, now);
+ values.put(Bookmarks.DIRTY, 1);
+
+ // If no parent is set default to the "Bookmarks Bar" folder
+ // TODO set the parent based on the account info
+ if (!values.containsKey(Bookmarks.PARENT)) {
+ values.put(Bookmarks.PARENT, FIXED_ID_ROOT);
+ }
+ }
+
+ // If no position is requested put the bookmark at the beginning of the list
+ if (!values.containsKey(Bookmarks.POSITION)) {
+ values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE));
+ }
+
+ // Extract out the image values so they can be inserted into the images table
+ String url = values.getAsString(Bookmarks.URL);
+ ContentValues imageValues = extractImageValues(values, url);
+ Boolean isFolder = values.getAsBoolean(Bookmarks.IS_FOLDER);
+ if ((isFolder == null || !isFolder)
+ && imageValues != null && !TextUtils.isEmpty(url)) {
+ int count = db.update(TABLE_IMAGES, imageValues, Images.URL + "=?",
+ new String[] { url });
+ if (count == 0) {
+ db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
+ }
+ }
+
+ id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
+ break;
+ }
+
+ case HISTORY: {
+ // If no created time is specified set it to now
+ if (!values.containsKey(History.DATE_CREATED)) {
+ values.put(History.DATE_CREATED, System.currentTimeMillis());
+ }
+
+ // Extract out the image values so they can be inserted into the images table
+ ContentValues imageValues = extractImageValues(values,
+ values.getAsString(History.URL));
+ if (imageValues != null) {
+ db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
+ }
+
+ id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
+ break;
+ }
+
+ case SEARCHES: {
+ id = insertSearchesInTransaction(db, values);
+ break;
+ }
+
+ case SYNCSTATE: {
+ id = mSyncHelper.insert(db, values);
+ break;
+ }
+
+ case SETTINGS: {
+ id = 0;
+ insertSettingsInTransaction(db, values);
+ break;
+ }
+
+ default: {
+ throw new UnsupportedOperationException("Unknown insert URI " + uri);
+ }
+ }
+
+ if (id >= 0) {
+ return ContentUris.withAppendedId(uri, id);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Searches are unique, so perform an UPSERT manually since SQLite doesn't support them.
+ */
+ private long insertSearchesInTransaction(SQLiteDatabase db, ContentValues values) {
+ String search = values.getAsString(Searches.SEARCH);
+ if (TextUtils.isEmpty(search)) {
+ throw new IllegalArgumentException("Must include the SEARCH field");
+ }
+ Cursor cursor = null;
+ try {
+ cursor = db.query(TABLE_SEARCHES, new String[] { Searches._ID },
+ Searches.SEARCH + "=?", new String[] { search }, null, null, null);
+ if (cursor.moveToNext()) {
+ long id = cursor.getLong(0);
+ db.update(TABLE_SEARCHES, values, Searches._ID + "=?",
+ new String[] { Long.toString(id) });
+ return id;
+ } else {
+ return db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
+
+ /**
+ * Settings are unique, so perform an UPSERT manually since SQLite doesn't support them.
+ */
+ private long insertSettingsInTransaction(SQLiteDatabase db, ContentValues values) {
+ String key = values.getAsString(Settings.KEY);
+ if (TextUtils.isEmpty(key)) {
+ throw new IllegalArgumentException("Must include the KEY field");
+ }
+ String[] keyArray = new String[] { key };
+ Cursor cursor = null;
+ try {
+ cursor = db.query(TABLE_SETTINGS, new String[] { Settings.KEY },
+ Settings.KEY + "=?", keyArray, null, null, null);
+ if (cursor.moveToNext()) {
+ long id = cursor.getLong(0);
+ db.update(TABLE_SETTINGS, values, Settings.KEY + "=?", keyArray);
+ return id;
+ } else {
+ return db.insertOrThrow(TABLE_SETTINGS, Settings.VALUE, values);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
+
+ @Override
+ public int updateInTransaction(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter) {
+ final int match = URI_MATCHER.match(uri);
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ switch (match) {
+ case BOOKMARKS_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection,
+ TABLE_BOOKMARKS + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case BOOKMARKS: {
+ return updateBookmarksInTransaction(values, selection, selectionArgs,
+ callerIsSyncAdapter);
+ }
+
+ case HISTORY_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case HISTORY: {
+ return updateHistoryInTransaction(values, selection, selectionArgs);
+ }
+
+ case SYNCSTATE: {
+ return mSyncHelper.update(mDb, values,
+ appendAccountToSelection(uri, selection), selectionArgs);
+ }
+
+ case SYNCSTATE_ID: {
+ selection = appendAccountToSelection(uri, selection);
+ String selectionWithId =
+ (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
+ + (selection == null ? "" : " AND (" + selection + ")");
+ return mSyncHelper.update(mDb, values,
+ selectionWithId, selectionArgs);
+ }
+
+ case IMAGES: {
+ String url = values.getAsString(Images.URL);
+ if (TextUtils.isEmpty(url)) {
+ throw new IllegalArgumentException("Images.URL is required");
+ }
+ int count = db.update(TABLE_IMAGES, values, Images.URL + "=?",
+ new String[] { url });
+ if (count == 0) {
+ db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values);
+ count = 1;
+ }
+ return count;
+ }
+ }
+ throw new UnsupportedOperationException("Unknown update URI " + uri);
+ }
+
+ /**
+ * Does a query to find the matching bookmarks and updates each one with the provided values.
+ */
+ int updateBookmarksInTransaction(ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter) {
+ int count = 0;
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor cursor = query(Bookmarks.CONTENT_URI,
+ new String[] { Bookmarks._ID, Bookmarks.VERSION, Bookmarks.URL },
+ selection, selectionArgs, null);
+ try {
+ String[] args = new String[1];
+ // Mark the bookmark dirty if the caller isn't a sync adapter
+ if (!callerIsSyncAdapter) {
+ values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
+ values.put(Bookmarks.DIRTY, 1);
+ }
+
+ boolean updatingUrl = values.containsKey(Bookmarks.URL);
+ String url = null;
+ if (updatingUrl) {
+ url = values.getAsString(Bookmarks.URL);
+ }
+ ContentValues imageValues = extractImageValues(values, url);
+
+ while (cursor.moveToNext()) {
+ args[0] = cursor.getString(0);
+ if (!callerIsSyncAdapter) {
+ // increase the local version for non-sync changes
+ values.put(Bookmarks.VERSION, cursor.getLong(1) + 1);
+ }
+ count += db.update(TABLE_BOOKMARKS, values, "_id=?", args);
+
+ // Update the images over in their table
+ if (imageValues != null) {
+ if (!updatingUrl) {
+ url = cursor.getString(2);
+ imageValues.put(Images.URL, url);
+ }
+
+ if (!TextUtils.isEmpty(url)) {
+ args[0] = url;
+ if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
+ db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
+ }
+ }
+ }
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return count;
+ }
+
+ /**
+ * Does a query to find the matching bookmarks and updates each one with the provided values.
+ */
+ int updateHistoryInTransaction(ContentValues values, String selection, String[] selectionArgs) {
+ int count = 0;
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor cursor = query(History.CONTENT_URI,
+ new String[] { History._ID, History.URL },
+ selection, selectionArgs, null);
+ try {
+ String[] args = new String[1];
+
+ boolean updatingUrl = values.containsKey(History.URL);
+ String url = null;
+ if (updatingUrl) {
+ url = values.getAsString(History.URL);
+ }
+ ContentValues imageValues = extractImageValues(values, url);
+
+ while (cursor.moveToNext()) {
+ args[0] = cursor.getString(0);
+ count += db.update(TABLE_HISTORY, values, "_id=?", args);
+
+ // Update the images over in their table
+ if (imageValues != null) {
+ if (!updatingUrl) {
+ url = cursor.getString(1);
+ imageValues.put(Images.URL, url);
+ }
+ args[0] = url;
+ if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
+ db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
+ }
+ }
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return count;
+ }
+
+ String appendAccountToSelection(Uri uri, String selection) {
+ final String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
+ final String accountType = uri.getQueryParameter(RawContacts.ACCOUNT_TYPE);
+
+ final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType);
+ if (partialUri) {
+ // Throw when either account is incomplete
+ throw new IllegalArgumentException(
+ "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE for " + uri);
+ }
+
+ // Accounts are valid by only checking one parameter, since we've
+ // already ruled out partial accounts.
+ final boolean validAccount = !TextUtils.isEmpty(accountName);
+ if (validAccount) {
+ StringBuilder selectionSb = new StringBuilder(RawContacts.ACCOUNT_NAME + "="
+ + DatabaseUtils.sqlEscapeString(accountName) + " AND "
+ + RawContacts.ACCOUNT_TYPE + "="
+ + DatabaseUtils.sqlEscapeString(accountType));
+ if (!TextUtils.isEmpty(selection)) {
+ selectionSb.append(" AND (");
+ selectionSb.append(selection);
+ selectionSb.append(')');
+ }
+ return selectionSb.toString();
+ } else {
+ return selection;
+ }
+ }
+
+ ContentValues extractImageValues(ContentValues values, String url) {
+ ContentValues imageValues = null;
+ // favicon
+ if (values.containsKey(Bookmarks.FAVICON)) {
+ imageValues = new ContentValues();
+ imageValues.put(Images.FAVICON, values.getAsByteArray(Bookmarks.FAVICON));
+ values.remove(Bookmarks.FAVICON);
+ }
+
+ // thumbnail
+ if (values.containsKey(Bookmarks.THUMBNAIL)) {
+ if (imageValues == null) {
+ imageValues = new ContentValues();
+ }
+ imageValues.put(Images.THUMBNAIL, values.getAsByteArray(Bookmarks.THUMBNAIL));
+ values.remove(Bookmarks.THUMBNAIL);
+ }
+
+ // touch icon
+ if (values.containsKey(Bookmarks.TOUCH_ICON)) {
+ if (imageValues == null) {
+ imageValues = new ContentValues();
+ }
+ imageValues.put(Images.TOUCH_ICON, values.getAsByteArray(Bookmarks.TOUCH_ICON));
+ values.remove(Bookmarks.TOUCH_ICON);
+ }
+
+ if (imageValues != null) {
+ imageValues.put(Images.URL, url);
+ }
+ return imageValues;
+ }
+}
diff --git a/src/com/android/browser/provider/SQLiteContentProvider.java b/src/com/android/browser/provider/SQLiteContentProvider.java
new file mode 100644
index 000000000..a50894a41
--- /dev/null
+++ b/src/com/android/browser/provider/SQLiteContentProvider.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2009 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.provider;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteTransactionListener;
+import android.net.Uri;
+
+import java.util.ArrayList;
+
+/**
+ * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage.
+ */
+public abstract class SQLiteContentProvider extends ContentProvider
+ implements SQLiteTransactionListener {
+
+ private static final String TAG = "SQLiteContentProvider";
+
+ private SQLiteOpenHelper mOpenHelper;
+ private volatile boolean mNotifyChange;
+ protected SQLiteDatabase mDb;
+
+ private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
+ private static final int SLEEP_AFTER_YIELD_DELAY = 4000;
+
+ /**
+ * Maximum number of operations allowed in a batch between yield points.
+ */
+ private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500;
+
+ @Override
+ public boolean onCreate() {
+ Context context = getContext();
+ mOpenHelper = getDatabaseHelper(context);
+ return true;
+ }
+
+ /**
+ * Returns a {@link SQLiteOpenHelper} that can open the database.
+ */
+ public abstract SQLiteOpenHelper getDatabaseHelper(Context context);
+
+ /**
+ * The equivalent of the {@link #insert} method, but invoked within a transaction.
+ */
+ public abstract Uri insertInTransaction(Uri uri, ContentValues values,
+ boolean callerIsSyncAdapter);
+
+ /**
+ * The equivalent of the {@link #update} method, but invoked within a transaction.
+ */
+ public abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter);
+
+ /**
+ * The equivalent of the {@link #delete} method, but invoked within a transaction.
+ */
+ public abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+ boolean callerIsSyncAdapter);
+
+ /**
+ * Called when the provider needs to notify the system of a change.
+ * @param callerIsSyncAdapter true if the caller that caused the change was a sync adapter.
+ */
+ public abstract void notifyChange(boolean callerIsSyncAdapter);
+
+ public boolean isCallerSyncAdapter(Uri uri) {
+ return false;
+ }
+
+ public SQLiteOpenHelper getDatabaseHelper() {
+ return mOpenHelper;
+ }
+
+ private boolean applyingBatch() {
+ return mApplyingBatch.get() != null && mApplyingBatch.get();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Uri result = null;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ result = insertInTransaction(uri, values, callerIsSyncAdapter);
+ if (result != null) {
+ mNotifyChange = true;
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ result = insertInTransaction(uri, values, callerIsSyncAdapter);
+ if (result != null) {
+ mNotifyChange = true;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ int numValues = values.length;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ for (int i = 0; i < numValues; i++) {
+ Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter);
+ if (result != null) {
+ mNotifyChange = true;
+ }
+ mDb.yieldIfContendedSafely();
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ return numValues;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ int count = 0;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ count = updateInTransaction(uri, values, selection, selectionArgs,
+ callerIsSyncAdapter);
+ if (count > 0) {
+ mNotifyChange = true;
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter);
+ if (count > 0) {
+ mNotifyChange = true;
+ }
+ }
+
+ return count;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ int count = 0;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+ if (count > 0) {
+ mNotifyChange = true;
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+ if (count > 0) {
+ mNotifyChange = true;
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ int ypCount = 0;
+ int opCount = 0;
+ boolean callerIsSyncAdapter = false;
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ mApplyingBatch.set(true);
+ final int numOperations = operations.size();
+ final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
+ throw new OperationApplicationException(
+ "Too many content provider operations between yield points. "
+ + "The maximum number of operations per yield point is "
+ + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
+ }
+ final ContentProviderOperation operation = operations.get(i);
+ if (!callerIsSyncAdapter && isCallerSyncAdapter(operation.getUri())) {
+ callerIsSyncAdapter = true;
+ }
+ if (i > 0 && operation.isYieldAllowed()) {
+ opCount = 0;
+ if (mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) {
+ ypCount++;
+ }
+ }
+ results[i] = operation.apply(this, results, i);
+ }
+ mDb.setTransactionSuccessful();
+ return results;
+ } finally {
+ mApplyingBatch.set(false);
+ mDb.endTransaction();
+ onEndTransaction(callerIsSyncAdapter);
+ }
+ }
+
+ @Override
+ public void onBegin() {
+ onBeginTransaction();
+ }
+
+ @Override
+ public void onCommit() {
+ beforeTransactionCommit();
+ }
+
+ @Override
+ public void onRollback() {
+ // not used
+ }
+
+ protected void onBeginTransaction() {
+ }
+
+ protected void beforeTransactionCommit() {
+ }
+
+ protected void onEndTransaction(boolean callerIsSyncAdapter) {
+ if (mNotifyChange) {
+ mNotifyChange = false;
+ notifyChange(callerIsSyncAdapter);
+ }
+ }
+}
diff --git a/src/com/android/browser/search/.DefaultSearchEngine.java.swp b/src/com/android/browser/search/.DefaultSearchEngine.java.swp
new file mode 100644
index 000000000..441153c4a
--- /dev/null
+++ b/src/com/android/browser/search/.DefaultSearchEngine.java.swp
Binary files differ
diff --git a/src/com/android/browser/widget/BookmarkListWidgetProvider.java b/src/com/android/browser/widget/BookmarkListWidgetProvider.java
new file mode 100644
index 000000000..04f7b07ee
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkListWidgetProvider.java
@@ -0,0 +1,110 @@
+/*
+ * 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.widget;
+
+import com.android.browser.BrowserActivity;
+import com.android.browser.R;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+/**
+ * Widget that shows a preview of the user's bookmarks.
+ */
+public class BookmarkListWidgetProvider extends AppWidgetProvider {
+ static final String ACTION_BOOKMARK_APPWIDGET_UPDATE =
+ "com.android.browser.BOOKMARK_APPWIDGET_UPDATE";
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Handle bookmark-specific updates ourselves because they might be
+ // coming in without extras, which AppWidgetProvider then blocks.
+ final String action = intent.getAction();
+ if (ACTION_BOOKMARK_APPWIDGET_UPDATE.equals(action)) {
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ performUpdate(context, appWidgetManager,
+ appWidgetManager.getAppWidgetIds(getComponentName(context)));
+ } else {
+ super.onReceive(context, intent);
+ }
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager mngr, int[] ids) {
+ performUpdate(context, mngr, ids);
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ // Start the backing service
+ context.startService(new Intent(context, BookmarkListWidgetService.class));
+ }
+
+ @Override
+ public void onDisabled(Context context) {
+ // Stop the backing service
+ context.stopService(new Intent(context, BookmarkListWidgetService.class));
+ }
+
+ @Override
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ super.onDeleted(context, appWidgetIds);
+ context.startService(new Intent(BookmarkListWidgetService.ACTION_REMOVE_FACTORIES,
+ null, context, BookmarkListWidgetService.class)
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds));
+ }
+
+ private void performUpdate(Context context,
+ AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ // Update the widgets
+ for (int appWidgetId : appWidgetIds) {
+ Intent updateIntent = new Intent(context, BookmarkListWidgetService.class);
+ updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)));
+ RemoteViews views = new RemoteViews(context.getPackageName(),
+ R.layout.bookmarklistwidget);
+ views.setRemoteAdapter(R.id.bookmarks_list, updateIntent);
+ appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.bookmarks_list);
+ Intent ic = new Intent(context, BookmarkListWidgetService.class);
+ views.setPendingIntentTemplate(R.id.bookmarks_list,
+ PendingIntent.getService(context, 0, ic,
+ PendingIntent.FLAG_UPDATE_CURRENT));
+ Intent launch = new Intent(context, BrowserActivity.class);
+ views.setOnClickPendingIntent(R.id.header, PendingIntent
+ .getActivity(context, 0, launch, PendingIntent.FLAG_CANCEL_CURRENT));
+ appWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+ }
+
+ /**
+ * Build {@link ComponentName} describing this specific
+ * {@link AppWidgetProvider}
+ */
+ static ComponentName getComponentName(Context context) {
+ return new ComponentName(context, BookmarkListWidgetProvider.class);
+ }
+}
diff --git a/src/com/android/browser/widget/BookmarkListWidgetService.java b/src/com/android/browser/widget/BookmarkListWidgetService.java
new file mode 100644
index 000000000..14b52b758
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkListWidgetService.java
@@ -0,0 +1,372 @@
+/*
+ * 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.widget;
+
+import com.android.browser.BrowserBookmarksPage;
+import com.android.browser.R;
+
+import android.appwidget.AppWidgetManager;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.preference.PreferenceManager;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Bookmarks;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+public class BookmarkListWidgetService extends RemoteViewsService {
+
+ static final String TAG = "BookmarkListWidgetService";
+ static final boolean USE_FOLDERS = true;
+
+ static final String ACTION_REMOVE_FACTORIES
+ = "com.android.browser.widget.REMOVE_FACTORIES";
+ static final String ACTION_CHANGE_FOLDER
+ = "com.android.browser.widget.CHANGE_FOLDER";
+
+ private static final String[] PROJECTION = new String[] {
+ BrowserContract.Bookmarks._ID,
+ BrowserContract.Bookmarks.TITLE,
+ BrowserContract.Bookmarks.URL,
+ BrowserContract.Bookmarks.FAVICON,
+ BrowserContract.Bookmarks.IS_FOLDER,
+ BrowserContract.Bookmarks.PARENT,
+ BrowserContract.Bookmarks.POSITION};
+
+ private Map<Integer, BookmarkFactory> mFactories;
+ private Handler mUiHandler;
+ private BookmarksObserver mBookmarksObserver;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mFactories = new HashMap<Integer, BookmarkFactory>();
+ mUiHandler = new Handler();
+ mBookmarksObserver = new BookmarksObserver(mUiHandler);
+ getContentResolver().registerContentObserver(
+ BrowserContract.AUTHORITY_URI, true, mBookmarksObserver);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ String action = intent.getAction();
+ if (Intent.ACTION_VIEW.equals(action)) {
+ Intent view = new Intent(intent);
+ view.setComponent(null);
+ startActivity(view);
+ } else if (ACTION_REMOVE_FACTORIES.equals(action)) {
+ int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
+ if (ids != null) {
+ for (int id : ids) {
+ mFactories.remove(id);
+ }
+ }
+ } else if (ACTION_CHANGE_FOLDER.equals(action)) {
+ int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ long folderId = intent.getLongExtra(Bookmarks._ID, -1);
+ BookmarkFactory fac = mFactories.get(widgetId);
+ if (fac != null && folderId >= 0) {
+ fac.changeFolder(folderId);
+ AppWidgetManager.getInstance(this).notifyAppWidgetViewDataChanged(widgetId, R.id.bookmarks_list);
+ }
+ }
+ return START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ getContentResolver().unregisterContentObserver(mBookmarksObserver);
+ }
+
+ private class BookmarksObserver extends ContentObserver {
+ public BookmarksObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+
+ // Update all the bookmark widgets
+ sendBroadcast(new Intent(
+ BookmarkListWidgetProvider.ACTION_BOOKMARK_APPWIDGET_UPDATE,
+ null, BookmarkListWidgetService.this,
+ BookmarkListWidgetProvider.class));
+ }
+ }
+
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ if (widgetId < 0) {
+ Log.w(TAG, "Missing EXTRA_APPWIDGET_ID!");
+ return null;
+ } else {
+ BookmarkFactory fac = new BookmarkFactory(this, widgetId);
+ mFactories.put(widgetId, fac);
+ return fac;
+ }
+ }
+
+ private static class Breadcrumb {
+ long mId;
+ String mTitle;
+ public Breadcrumb(long id, String title) {
+ mId = id;
+ mTitle = title;
+ }
+ }
+
+ static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory {
+ private List<RenderResult> mBookmarks;
+ private Context mContext;
+ private int mWidgetId;
+ private String mAccountType;
+ private String mAccountName;
+ private Stack<Breadcrumb> mBreadcrumbs;
+
+ public BookmarkFactory(Context context, int widgetId) {
+ mBreadcrumbs = new Stack<Breadcrumb>();
+ mContext = context;
+ mWidgetId = widgetId;
+ }
+
+ void changeFolder(long folderId) {
+ if (mBookmarks == null) return;
+
+ if (!mBreadcrumbs.empty() && mBreadcrumbs.peek().mId == folderId) {
+ mBreadcrumbs.pop();
+ return;
+ }
+
+ for (RenderResult res : mBookmarks) {
+ if (res.mId == folderId) {
+ mBreadcrumbs.push(new Breadcrumb(res.mId, res.mTitle));
+ break;
+ }
+ }
+ }
+
+ @Override
+ public int getCount() {
+ if (mBookmarks == null)
+ return 0;
+ return mBookmarks.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ return null;
+ }
+
+ @Override
+ public RemoteViews getViewAt(int position) {
+ if (position < 0 || position >= getCount()) {
+ return null;
+ }
+
+ RenderResult res = mBookmarks.get(position);
+ Breadcrumb folder = mBreadcrumbs.empty() ? null : mBreadcrumbs.peek();
+
+ RemoteViews views = new RemoteViews(
+ mContext.getPackageName(), R.layout.bookmarklistwidget_item);
+ Intent fillin;
+ if (res.mIsFolder) {
+ long nfi = res.mId;
+ fillin = new Intent(ACTION_CHANGE_FOLDER, null,
+ mContext, BookmarkListWidgetService.class)
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
+ .putExtra(Bookmarks._ID, nfi);
+ } else {
+ fillin = new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(res.mUrl))
+ .addCategory(Intent.CATEGORY_BROWSABLE);
+ }
+ views.setOnClickFillInIntent(R.id.list_item, fillin);
+ // Set the title of the bookmark. Use the url as a backup.
+ String displayTitle = res.mTitle;
+ if (TextUtils.isEmpty(displayTitle)) {
+ // The browser always requires a title for bookmarks, but jic...
+ displayTitle = res.mUrl;
+ }
+ views.setTextViewText(R.id.label, displayTitle);
+ views.setDrawableParameters(R.id.list_item, true, 0, -1, null, -1);
+ if (res.mIsFolder) {
+ if (folder != null && res.mId == folder.mId) {
+ views.setDrawableParameters(R.id.list_item, true, 255, -1, null, -1);
+ views.setImageViewResource(R.id.thumb, R.drawable.ic_back_normal);
+ } else {
+ views.setImageViewResource(R.id.thumb, R.drawable.ic_folder);
+ }
+ } else {
+ if (res.mBitmap != null) {
+ views.setImageViewBitmap(R.id.thumb, res.mBitmap);
+ } else {
+ views.setImageViewResource(R.id.thumb,
+ R.drawable.browser_thumbnail);
+ }
+ }
+ return views;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ @Override
+ public void onCreate() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ mAccountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
+ mAccountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
+ loadData();
+ }
+
+ @Override
+ public void onDestroy() {
+ recycleBitmaps();
+ }
+
+ @Override
+ public void onDataSetChanged() {
+ loadData();
+ }
+
+ void loadData() {
+ // Reset identity since this could be an IPC call
+ long token = Binder.clearCallingIdentity();
+ update();
+ Binder.restoreCallingIdentity(token);
+ }
+
+ void update() {
+ recycleBitmaps();
+ String where = null;
+ Breadcrumb folder = mBreadcrumbs.empty() ? null : mBreadcrumbs.peek();
+ Uri uri;
+ if (USE_FOLDERS) {
+ uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
+ if (folder != null) {
+ uri = ContentUris.withAppendedId(uri, folder.mId);
+ }
+ } else {
+ uri = BrowserContract.Bookmarks.CONTENT_URI;
+ where = Bookmarks.IS_FOLDER + " == 0";
+ }
+ if (!TextUtils.isEmpty(mAccountType) && !TextUtils.isEmpty(mAccountName)) {
+ uri = uri.buildUpon()
+ .appendQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE, mAccountType)
+ .appendQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME, mAccountName).build();
+ }
+ Cursor c = null;
+ try {
+ c = mContext.getContentResolver().query(uri, PROJECTION,
+ where, null, null);
+ if (c != null) {
+ mBookmarks = new ArrayList<RenderResult>(c.getCount() + 1);
+ if (folder != null) {
+ RenderResult res = new RenderResult(folder.mId, folder.mTitle, null);
+ res.mIsFolder = true;
+ mBookmarks.add(res);
+ }
+ while (c.moveToNext()) {
+ long id = c.getLong(0);
+ String title = c.getString(1);
+ String url = c.getString(2);
+ RenderResult res = new RenderResult(id, title, url);
+ byte[] blob = c.getBlob(3);
+ if (blob != null) {
+ // RemoteViews require a valid bitmap config
+ Options options = new Options();
+ options.inPreferredConfig = Config.ARGB_8888;
+ res.mBitmap = BitmapFactory.decodeByteArray(
+ blob, 0, blob.length, options);
+ }
+ res.mIsFolder = c.getInt(4) != 0;
+ mBookmarks.add(res);
+ }
+ }
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "update bookmark widget", e);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+
+ private void recycleBitmaps() {
+ // Do a bit of house cleaning for the system
+ if (mBookmarks != null) {
+ for (RenderResult res : mBookmarks) {
+ if (res.mBitmap != null) {
+ res.mBitmap.recycle();
+ res.mBitmap = null;
+ }
+ }
+ }
+ }
+ }
+
+ // Class containing the rendering information for a specific bookmark.
+ private static class RenderResult {
+ final String mTitle;
+ final String mUrl;
+ Bitmap mBitmap;
+ boolean mIsFolder;
+ long mId;
+
+ RenderResult(long id, String title, String url) {
+ mId = id;
+ mTitle = title;
+ mUrl = url;
+ }
+
+ }
+
+}
diff --git a/src/com/android/browser/widget/BookmarkWidgetProvider.java b/src/com/android/browser/widget/BookmarkWidgetProvider.java
deleted file mode 100644
index 62b48c068..000000000
--- a/src/com/android/browser/widget/BookmarkWidgetProvider.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.widget;
-
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-/**
- * Widget that shows a preview of the user's bookmarks.
- */
-public class BookmarkWidgetProvider extends AppWidgetProvider {
-
- static final String TAG = "BookmarkWidgetProvider";
-
- @Override
- public void onUpdate(Context context, AppWidgetManager mngr, int[] ids) {
- context.startService(new Intent(BookmarkWidgetService.UPDATE, null,
- context, BookmarkWidgetService.class));
- }
-
- @Override
- public void onEnabled(Context context) {
- context.startService(new Intent(context, BookmarkWidgetService.class));
- }
-
- @Override
- public void onDisabled(Context context) {
- context.stopService(new Intent(context, BookmarkWidgetService.class));
- }
-}
-
diff --git a/src/com/android/browser/widget/BookmarkWidgetService.java b/src/com/android/browser/widget/BookmarkWidgetService.java
deleted file mode 100644
index 1fd916377..000000000
--- a/src/com/android/browser/widget/BookmarkWidgetService.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * 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.widget;
-
-import android.app.PendingIntent;
-import android.app.Service;
-import android.appwidget.AppWidgetManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.provider.Browser;
-import android.provider.Browser.BookmarkColumns;
-import android.service.urlrenderer.UrlRenderer;
-import android.service.urlrenderer.UrlRendererService;
-import android.util.Log;
-import android.view.View;
-import android.widget.RemoteViews;
-
-import com.android.browser.R;
-
-import java.io.InputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class BookmarkWidgetService extends Service
- implements UrlRenderer.Callback {
-
- private static final String TAG = "BookmarkWidgetService";
-
- /** Force the bookmarks to be re-renderer. */
- public static final String UPDATE = "com.android.browser.widget.UPDATE";
-
- /** Change the widget to the next bookmark. */
- private static final String NEXT = "com.android.browser.widget.NEXT";
-
- /** Change the widget to the previous bookmark. */
- private static final String PREV = "com.android.browser.widget.PREV";
-
- /** Id of the current item displayed in the widget. */
- private static final String EXTRA_ID =
- "com.android.browser.widget.extra.ID";
-
- // XXX: Remove these magic numbers once the dimensions of the widget can be
- // queried.
- private static final int WIDTH = 306;
- private static final int HEIGHT = 386;
-
- // Limit the number of connection attempts.
- private static final int MAX_SERVICE_RETRY_COUNT = 5;
-
- // No id specified.
- private static final int NO_ID = -1;
-
- private static final int MSG_UPDATE = 0;
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE:
- if (mRenderer != null) {
- queryCursorAndRender();
- } else {
- if (++mServiceRetryCount <= MAX_SERVICE_RETRY_COUNT) {
- // Service is not connected, try again in a second.
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE, 1000);
- }
- }
- break;
- default:
- break;
- }
- }
- };
-
- private final ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className,
- IBinder service) {
- mRenderer = new UrlRenderer(service);
- }
-
- public void onServiceDisconnected(ComponentName className) {
- mRenderer = null;
- }
- };
-
- // Id -> information map storing db ids and their result.
- private final HashMap<Integer, RenderResult> mIdsToResults =
- new HashMap<Integer, RenderResult>();
-
- // List of ids in order
- private final ArrayList<Integer> mIdList = new ArrayList<Integer>();
-
- // Map of urls to ids for when a url is complete.
- private final HashMap<String, Integer> mUrlsToIds =
- new HashMap<String, Integer>();
-
- // The current id used by the widget during an update.
- private int mCurrentId = NO_ID;
- // Class that contacts the service on the phone to render bookmarks.
- private UrlRenderer mRenderer;
- // Number of service retries. Stop trying to connect after
- // MAX_SERVICE_RETRY_COUNT
- private int mServiceRetryCount;
-
- @Override
- public void onCreate() {
- bindService(new Intent(UrlRendererService.SERVICE_INTERFACE),
- mConnection, Context.BIND_AUTO_CREATE);
- }
-
- @Override
- public void onDestroy() {
- unbindService(mConnection);
- }
-
- @Override
- public android.os.IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- final String action = intent.getAction();
- if (UPDATE.equals(action)) {
- mHandler.sendEmptyMessage(MSG_UPDATE);
- } else if (PREV.equals(action) && mIdList.size() > 1) {
- int prev = getPreviousId(intent);
- if (prev == NO_ID) {
- Log.d(TAG, "Could not determine previous id");
- return START_NOT_STICKY;
- }
- RenderResult res = mIdsToResults.get(prev);
- if (res != null) {
- updateWidget(res);
- }
- } else if (NEXT.equals(action) && mIdList.size() > 1) {
- int next = getNextId(intent);
- if (next == NO_ID) {
- Log.d(TAG, "Could not determine next id");
- return START_NOT_STICKY;
- }
- RenderResult res = mIdsToResults.get(next);
- if (res != null) {
- updateWidget(res);
- }
- }
- return START_STICKY;
- }
-
- private int getPreviousId(Intent intent) {
- int listSize = mIdList.size();
- // If the list contains 1 or fewer entries, return NO_ID so that the
- // widget does not update.
- if (listSize <= 1) {
- return NO_ID;
- }
-
- int curr = intent.getIntExtra(EXTRA_ID, NO_ID);
- if (curr == NO_ID) {
- return NO_ID;
- }
-
- // Check if the current id is the beginning of the list so we can skip
- // iterating through.
- if (mIdList.get(0) == curr) {
- return mIdList.get(listSize - 1);
- }
-
- // Search for the current id and remember the previous id.
- int prev = NO_ID;
- for (int id : mIdList) {
- if (id == curr) {
- break;
- }
- prev = id;
- }
- return prev;
- }
-
- private int getNextId(Intent intent) {
- int listSize = mIdList.size();
- // If the list contains 1 or fewer entries, return NO_ID so that the
- // widget does not update.
- if (listSize <= 1) {
- return NO_ID;
- }
-
- int curr = intent.getIntExtra(EXTRA_ID, NO_ID);
- if (curr == NO_ID) {
- return NO_ID;
- }
-
- // Check if the current id is at the end of the list so we can skip
- // iterating through.
- if (mIdList.get(listSize - 1) == curr) {
- return mIdList.get(0);
- }
-
- // Iterate through the ids. i is set to the current index + 1.
- int i = 1;
- for (int id : mIdList) {
- if (id == curr) {
- break;
- }
- i++;
- }
- return mIdList.get(i);
- }
-
- private void updateWidget(RenderResult res) {
- RemoteViews views = new RemoteViews(getPackageName(),
- R.layout.bookmarkwidget);
-
- Intent prev = new Intent(PREV, null, this, BookmarkWidgetService.class);
- prev.putExtra(EXTRA_ID, res.mId);
- views.setOnClickPendingIntent(R.id.previous,
- PendingIntent.getService(this, 0, prev,
- PendingIntent.FLAG_CANCEL_CURRENT));
-
- Intent next = new Intent(NEXT, null, this, BookmarkWidgetService.class);
- next.putExtra(EXTRA_ID, res.mId);
- views.setOnClickPendingIntent(R.id.next,
- PendingIntent.getService(this, 0, next,
- PendingIntent.FLAG_CANCEL_CURRENT));
-
- // Set the title of the bookmark. Use the url as a backup.
- String displayTitle = res.mTitle;
- if (displayTitle == null) {
- displayTitle = res.mUrl;
- }
- views.setTextViewText(R.id.title, displayTitle);
-
- // Set the image or revert to the progress indicator.
- if (res.mBitmap != null) {
- views.setImageViewBitmap(R.id.image, res.mBitmap);
- views.setViewVisibility(R.id.image, View.VISIBLE);
- views.setViewVisibility(R.id.progress, View.GONE);
- } else {
- views.setViewVisibility(R.id.progress, View.VISIBLE);
- views.setViewVisibility(R.id.image, View.GONE);
- }
-
- // Update the current id.
- mCurrentId = res.mId;
-
- AppWidgetManager.getInstance(this).updateAppWidget(
- new ComponentName(this, BookmarkWidgetProvider.class),
- views);
- }
-
- // Default WHERE clause is all bookmarks.
- private static final String QUERY_WHERE =
- BookmarkColumns.BOOKMARK + " == 1";
- private static final String[] PROJECTION = new String[] {
- BookmarkColumns._ID, BookmarkColumns.TITLE, BookmarkColumns.URL };
-
- // Class containing the rendering information for a specific bookmark.
- private static class RenderResult {
- final int mId;
- final String mTitle;
- final String mUrl;
- Bitmap mBitmap;
-
- RenderResult(int id, String title, String url) {
- mId = id;
- mTitle = title;
- mUrl = url;
- }
- }
-
- private void queryCursorAndRender() {
- // Clear the ordered list of ids and the map of ids to bitmaps.
- mIdList.clear();
- mIdsToResults.clear();
-
- // Look up all the bookmarks
- Cursor c = getContentResolver().query(Browser.BOOKMARKS_URI, PROJECTION,
- QUERY_WHERE, null, null);
- if (c != null) {
- if (c.moveToFirst()) {
- ArrayList<String> urls = new ArrayList<String>(c.getCount());
- boolean sawCurrentId = false;
- do {
- int id = c.getInt(0);
- String title = c.getString(1);
- String url = c.getString(2);
-
- // Linear list of ids to obtain the previous and next.
- mIdList.add(id);
-
- // Map the url to its db id for lookup when complete.
- mUrlsToIds.put(url, id);
-
- // Is this the current id?
- if (mCurrentId == id) {
- sawCurrentId = true;
- }
-
- // Store the current information to at least display the
- // title.
- RenderResult res = new RenderResult(id, title, url);
- mIdsToResults.put(id, res);
-
- // Add the url to our list to render.
- urls.add(url);
- } while (c.moveToNext());
-
- // Request a rendering of the urls. XXX: Hard-coded dimensions
- // until the view's orientation and size can be determined. Or
- // in the future the image will be a picture that can be
- // scaled/zoomed arbitrarily.
- mRenderer.render(urls, WIDTH, HEIGHT, this);
-
- // Set the current id to the very first id if we did not see
- // the current id in the list (the bookmark could have been
- // deleted or this is the first update).
- if (!sawCurrentId) {
- mCurrentId = mIdList.get(0);
- }
- }
- c.close();
- }
- }
-
- // UrlRenderer.Callback implementation
- public void complete(String url, ParcelFileDescriptor result) {
- int id = mUrlsToIds.get(url);
- if (id == NO_ID) {
- Log.d(TAG, "No matching id found during completion of "
- + url);
- return;
- }
-
- RenderResult res = mIdsToResults.get(id);
- if (res == null) {
- Log.d(TAG, "No result found during completion of "
- + url);
- return;
- }
-
- // Set the result.
- if (result != null) {
- InputStream input =
- new ParcelFileDescriptor.AutoCloseInputStream(result);
- Bitmap orig = BitmapFactory.decodeStream(input, null, null);
- // XXX: Hard-coded scaled bitmap until I can query the image
- // dimensions.
- res.mBitmap = Bitmap.createScaledBitmap(orig, WIDTH, HEIGHT, true);
- try {
- input.close();
- } catch (IOException e) {
- // oh well...
- }
- }
-
- // If we are currently looking at the bookmark that just finished,
- // update the widget.
- if (mCurrentId == id) {
- updateWidget(res);
- }
- }
-}