eclair snapshot
diff --git a/src/com/android/browser/ActiveTabsPage.java b/src/com/android/browser/ActiveTabsPage.java
new file mode 100644
index 0000000..90c417a
--- /dev/null
+++ b/src/com/android/browser/ActiveTabsPage.java
@@ -0,0 +1,161 @@
+/*
+ * 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.content.Context;
+import android.graphics.Bitmap;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+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) {
+        super(context);
+        mBrowserActivity = context;
+        mControl = control;
+        mFactory = LayoutInflater.from(context);
+        mFactory.inflate(R.layout.active_tabs, this);
+        mListView = (ListView) findViewById(R.id.list);
+        mAdapter = new TabsListAdapter();
+        mListView.setAdapter(mAdapter);
+        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+                public void onItemClick(AdapterView<?> parent, View view,
+                        int position, long id) {
+                    if (mControl.getTabCount() < TabControl.MAX_TABS) {
+                        position--;
+                    }
+                    boolean needToAttach = false;
+                    if (position == -1) {
+                        // Create a new tab
+                        mBrowserActivity.openTabToHomePage();
+                    } 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);
+                    }
+                    mBrowserActivity.removeActiveTabPage(needToAttach);
+                }
+        });
+    }
+
+    /**
+     * Special class to hold the close drawable.  Its sole purpose is to allow
+     * the parent to be pressed without being pressed itself.  This way the line
+     * of a tab can be pressed, but the close button itself is not.
+     */
+    private static class CloseHolder extends ImageView {
+        public CloseHolder(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @Override
+        public void setPressed(boolean pressed) {
+            // If the parent is pressed, do not set to pressed.
+            if (pressed && ((View) getParent()).isPressed()) {
+                return;
+            }
+            super.setPressed(pressed);
+        }
+    }
+
+    private class TabsListAdapter extends BaseAdapter {
+        public int getCount() {
+            int count = mControl.getTabCount();
+            if (count < TabControl.MAX_TABS) {
+                count++;
+            }
+            return count;
+        }
+        public Object getItem(int position) {
+            return null;
+        }
+        public long getItemId(int position) {
+            return position;
+        }
+        public int getViewTypeCount() {
+            return 2;
+        }
+        public int getItemViewType(int position) {
+            if (mControl.getTabCount() < TabControl.MAX_TABS) {
+                position--;
+            }
+            // Do not recycle the "add new tab" item.
+            return position == -1 ? IGNORE_ITEM_VIEW_TYPE : 1;
+        }
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final int tabCount = mControl.getTabCount();
+            if (tabCount < TabControl.MAX_TABS) {
+                position--;
+            }
+
+            if (convertView == null) {
+                convertView = mFactory.inflate(position == -1 ?
+                        R.layout.tab_view_add_tab : R.layout.tab_view, null);
+            }
+
+            if (position != -1) {
+                TextView title =
+                        (TextView) convertView.findViewById(R.id.title);
+                TextView url = (TextView) convertView.findViewById(R.id.url);
+                ImageView favicon =
+                        (ImageView) convertView.findViewById(R.id.favicon);
+                View close = convertView.findViewById(R.id.close);
+                TabControl.Tab tab = mControl.getTab(position);
+                mControl.populatePickerData(tab);
+                title.setText(tab.getTitle());
+                url.setText(tab.getUrl());
+                Bitmap icon = tab.getFavicon();
+                if (icon != null) {
+                    favicon.setImageBitmap(icon);
+                } else {
+                    favicon.setImageResource(R.drawable.app_web_browser_sm);
+                }
+                final int closePosition = position;
+                close.setOnClickListener(new View.OnClickListener() {
+                        public void onClick(View v) {
+                            mBrowserActivity.closeTab(
+                                    mControl.getTab(closePosition));
+                            if (tabCount == 1) {
+                                mBrowserActivity.openTabToHomePage();
+                                mBrowserActivity.removeActiveTabPage(false);
+                            } else {
+                                mListView.setAdapter(mAdapter);
+                            }
+                        }
+                });
+            }
+            return convertView;
+        }
+    }
+}
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index cf3fe70..81123ba 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -18,22 +18,24 @@
 
 import android.app.Activity;
 import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.database.Cursor;
+import android.graphics.Bitmap;
 import android.net.ParseException;
 import android.net.WebAddress;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
 import android.provider.Browser;
 import android.view.View;
 import android.view.Window;
-import android.webkit.WebIconDatabase;
 import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.Toast;
 
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Date;
 
 public class AddBookmarkPage extends Activity {
@@ -46,18 +48,19 @@
     private View        mCancelButton;
     private boolean     mEditingExisting;
     private Bundle      mMap;
-    
-    private static final String[]   mProjection = 
-        { "_id", "url", "bookmark", "created", "title", "visits" };
-    private static final String     WHERE_CLAUSE = "url = ?";
-    private final String[]          SELECTION_ARGS = new String[1];
+    private String      mTouchIconUrl;
+    private Bitmap      mThumbnail;
+    private String      mOriginalUrl;
+
+    // Message IDs
+    private static final int SAVE_BOOKMARK = 100;
+
+    private Handler mHandler;
 
     private View.OnClickListener mSaveBookmark = new View.OnClickListener() {
         public void onClick(View v) {
             if (save()) {
                 finish();
-                Toast.makeText(AddBookmarkPage.this, R.string.bookmark_saved,
-                        Toast.LENGTH_LONG).show();
             }
         }
     };
@@ -73,7 +76,7 @@
         requestWindowFeature(Window.FEATURE_LEFT_ICON);
         setContentView(R.layout.browser_add_bookmark);
         setTitle(R.string.save_to_bookmarks);
-        getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_dialog_bookmark);
+        getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_list_bookmark);
         
         String title = null;
         String url = null;
@@ -86,7 +89,9 @@
                 setTitle(R.string.edit_bookmark);
             }
             title = mMap.getString("title");
-            url = mMap.getString("url");
+            url = mOriginalUrl = mMap.getString("url");
+            mTouchIconUrl = mMap.getString("touch_icon_url");
+            mThumbnail = (Bitmap) mMap.getParcelable("thumbnail");
         }
 
         mTitle = (EditText) findViewById(R.id.title);
@@ -94,7 +99,6 @@
         mAddress = (EditText) findViewById(R.id.address);
         mAddress.setText(url);
 
-
         View.OnClickListener accept = mSaveBookmark;
         mButton = (TextView) findViewById(R.id.OK);
         mButton.setOnClickListener(accept);
@@ -106,13 +110,59 @@
             mButton.requestFocus();
         }
     }
-    
+
+    private void createHandler() {
+        if (mHandler == null) {
+            mHandler = new Handler() {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case SAVE_BOOKMARK:
+                            // Unbundle bookmark data.
+                            Bundle bundle = msg.getData();
+                            String title = bundle.getString("title");
+                            String url = bundle.getString("url");
+                            boolean invalidateThumbnail = bundle.getBoolean("invalidateThumbnail");
+                            Bitmap thumbnail = invalidateThumbnail
+                                    ? null : (Bitmap) bundle.getParcelable("thumbnail");
+                            String touchIconUrl = bundle.getString("touchIconUrl");
+
+                            // Save to the bookmarks DB.
+                            if (updateBookmarksDB(title, url, thumbnail, touchIconUrl)) {
+                                Toast.makeText(AddBookmarkPage.this, R.string.bookmark_saved,
+                                        Toast.LENGTH_LONG).show();
+                            } else {
+                                Toast.makeText(AddBookmarkPage.this, R.string.bookmark_not_saved,
+                                        Toast.LENGTH_LONG).show();
+                            }
+                            break;
+                    }
+                }
+            };
+        }
+    }
+
+    private boolean updateBookmarksDB(String title, String url, Bitmap thumbnail, String touchIconUrl) {
+        try {
+            final ContentResolver cr = getContentResolver();
+            Bookmarks.addBookmark(null, cr, url, title, thumbnail, true);
+            if (touchIconUrl != null) {
+                final Cursor c =
+                        BrowserBookmarksAdapter.queryBookmarksForUrl(cr, null, url, true);
+                new DownloadTouchIcon(cr, c, url).execute(mTouchIconUrl);
+            }
+        } catch (IllegalStateException e) {
+            return false;
+        }
+        return true;
+    }
+
     /**
-     *  Save the data to the database. 
-     *  Also, change the view to dialog stating 
-     *  that the webpage has been saved.
+     * Parse the data entered in the dialog and post a message to update the bookmarks database.
      */
     boolean save() {
+        createHandler();
+
         String title = mTitle.getText().toString().trim();
         String unfilteredUrl = 
                 BrowserActivity.fixUrl(mAddress.getText().toString());
@@ -129,98 +179,47 @@
             return false;
         }
         String url = unfilteredUrl;
-        if (!(url.startsWith("about:") || url.startsWith("data:") || url
-                .startsWith("file:"))) {
-            WebAddress address;
-            try {
-                address = new WebAddress(unfilteredUrl);
-            } catch (ParseException e) {
-                mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
-                return false;
-            }
-            if (address.mHost.length() == 0) {
-                mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
-                return false;
-            }
-            url = address.toString();
-        }
         try {
-            if (mEditingExisting) {
-                mMap.putString("title", title);
-                mMap.putString("url", url);
-                setResult(RESULT_OK, (new Intent()).setAction(
-                        getIntent().toString()).putExtras(mMap));
-            } else {
-                // Want to append to the beginning of the list
-                long creationTime = new Date().getTime();
-                SELECTION_ARGS[0] = url;
-                ContentResolver cr = getContentResolver();
-                Cursor c = cr.query(Browser.BOOKMARKS_URI,
-                        mProjection,
-                        WHERE_CLAUSE,
-                        SELECTION_ARGS,
-                        null);
-                ContentValues map = new ContentValues();
-                if (c.moveToFirst() && c.getInt(c.getColumnIndexOrThrow(
-                        Browser.BookmarkColumns.BOOKMARK)) == 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, title);
-                    map.put(Browser.BookmarkColumns.BOOKMARK, 1);
-                    cr.update(Browser.BOOKMARKS_URI, map, 
-                            "_id = " + c.getInt(0), null);
-                } else {
-                    int count = c.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
-                        c.moveToPosition(i);
-                        if (c.getString(c.getColumnIndexOrThrow(
-                                Browser.BookmarkColumns.TITLE)).equals(title)) {
-                            // The old bookmark has the same name.
-                            // Update its creation time.
-                            map.put(Browser.BookmarkColumns.CREATED,
-                                    creationTime);
-                            cr.update(Browser.BOOKMARKS_URI, map, 
-                                    "_id = " + c.getInt(0), null);
-                            matchedTitle = true;
-                        }
-                    }
-                    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, title);
-                        map.put(Browser.BookmarkColumns.URL, url);
-                        map.put(Browser.BookmarkColumns.CREATED, creationTime);
-                        map.put(Browser.BookmarkColumns.BOOKMARK, 1);
-                        map.put(Browser.BookmarkColumns.DATE, 0);
-                        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 = c.getInt(c.getColumnIndexOrThrow(
-                                    Browser.BookmarkColumns.VISITS));
-                        }
-                        // 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);
-                    }
+            URI uriObj = new URI(url);
+            String scheme = uriObj.getScheme();
+            if (!("about".equals(scheme) || "data".equals(scheme)
+                    || "javascript".equals(scheme)
+                    || "file".equals(scheme) || "content".equals(scheme))) {
+                WebAddress address;
+                try {
+                    address = new WebAddress(unfilteredUrl);
+                } catch (ParseException e) {
+                    throw new URISyntaxException("", "");
                 }
-                WebIconDatabase.getInstance().retainIconForPageUrl(url);
-                c.deactivate();
-                setResult(RESULT_OK);
+                if (address.mHost.length() == 0) {
+                    throw new URISyntaxException("", "");
+                }
+                url = address.toString();
             }
-        } catch (IllegalStateException e) {
-            setTitle(r.getText(R.string.no_database));
+        } catch (URISyntaxException e) {
+            mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
             return false;
         }
+
+        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));
+        } else {
+            // Post a message to write to the DB.
+            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);
+            mHandler.sendMessage(msg);
+            setResult(RESULT_OK);
+        }
         return true;
     }
 }
diff --git a/src/com/android/browser/AddNewBookmark.java b/src/com/android/browser/AddNewBookmark.java
index a75d002..5308f6b 100644
--- a/src/com/android/browser/AddNewBookmark.java
+++ b/src/com/android/browser/AddNewBookmark.java
@@ -47,17 +47,7 @@
         mUrlText = (TextView) findViewById(R.id.url);
         mImageView = (ImageView) findViewById(R.id.favicon);
     }
-    
-    /**
-     *  Copy this BookmarkItem to item.
-     *  @param item BookmarkItem to receive the info from this BookmarkItem.
-     */
-    /* package */ void copyTo(AddNewBookmark item) {
-        item.mTextView.setText(mTextView.getText());
-        item.mUrlText.setText(mUrlText.getText());
-        item.mImageView.setImageDrawable(mImageView.getDrawable());
-    }
-    
+
     /**
      *  Set the new url for the bookmark item.
      *  @param url  The new url for the bookmark item.
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
new file mode 100644
index 0000000..c8aaee7
--- /dev/null
+++ b/src/com/android/browser/Bookmarks.java
@@ -0,0 +1,217 @@
+/*
+ * 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.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.provider.Browser;
+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.
+ */
+/* package */ class Bookmarks {
+    private static final String     WHERE_CLAUSE
+            = "url = ? OR url = ? OR url = ? OR url = ?";
+    private static final String     WHERE_CLAUSE_SECURE = "url = ? OR url = ?";
+
+    private static String[]         SELECTION_ARGS;
+
+    /**
+     *  Add a bookmark to the database.
+     *  @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.
+     */
+    /* package */ static void addBookmark(Context context,
+            ContentResolver cr, String url, String name,
+            Bitmap thumbnail, boolean retainIcon) {
+        // Want to append to the beginning of the list
+        long creationTime = new Date().getTime();
+        // First we check to see if the user has already visited this
+        // site.  They may have bookmarked it in a different way from
+        // how it's stored in the database, so allow different combos
+        // to map to the same url.
+        boolean secure = false;
+        String compareString = url;
+        if (compareString.startsWith("http://")) {
+            compareString = compareString.substring(7);
+        } else if (compareString.startsWith("https://")) {
+            compareString = compareString.substring(8);
+            secure = true;
+        }
+        if (compareString.startsWith("www.")) {
+            compareString = compareString.substring(4);
+        }
+        if (secure) {
+            SELECTION_ARGS = new String[2];
+            SELECTION_ARGS[0] = "https://" + compareString;
+            SELECTION_ARGS[1] = "https://www." + compareString;
+        } else {
+            SELECTION_ARGS = new String[4];
+            SELECTION_ARGS[0] = compareString;
+            SELECTION_ARGS[1] = "www." + compareString;
+            SELECTION_ARGS[2] = "http://" + compareString;
+            SELECTION_ARGS[3] = "http://" + SELECTION_ARGS[1];
+        }
+        Cursor cursor = cr.query(Browser.BOOKMARKS_URI,
+                Browser.HISTORY_PROJECTION,
+                secure ? WHERE_CLAUSE_SECURE : WHERE_CLAUSE,
+                SELECTION_ARGS,
+                null);
+        ContentValues map = new ContentValues();
+        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);
+            }
+        }
+        if (retainIcon) {
+            WebIconDatabase.getInstance().retainIconForPageUrl(url);
+        }
+        cursor.deactivate();
+        if (context != null) {
+            Toast.makeText(context, R.string.added_to_bookmarks,
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    /**
+     *  Remove a bookmark from the database.  If the url is a visited site, it
+     *  will remain in the database, but only as a history item, and not as a
+     *  bookmarked site.
+     *  @param context Context of the calling Activity.  This is used to make
+     *          Toast confirming that the bookmark has been removed.  If the
+     *          caller provides null, the Toast will not be shown.
+     *  @param cr The ContentResolver being used to remove the bookmark.
+     *  @param url URL of the website to be removed.
+     */
+    /* package */ static void removeFromBookmarks(Context context,
+            ContentResolver cr, String url, String title) {
+        Cursor cursor = cr.query(
+                Browser.BOOKMARKS_URI,
+                Browser.HISTORY_PROJECTION,
+                "url = ? AND title = ?",
+                new String[] { url, title },
+                null);
+        boolean first = cursor.moveToFirst();
+        // Should be in the database no matter what
+        if (!first) {
+            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!");
+            }
+        }
+        if (context != null) {
+            Toast.makeText(context, R.string.removed_from_bookmarks,
+                    Toast.LENGTH_LONG).show();
+        }
+        cursor.deactivate();
+    }
+
+    private static byte[] bitmapToBytes(Bitmap bm) {
+        if (bm == null) {
+            return null;
+        }
+
+        final ByteArrayOutputStream os = new ByteArrayOutputStream();
+        bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+        return os.toByteArray();
+    }
+}
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index b58af66..5937881 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -20,7 +20,6 @@
 import com.google.android.googlelogin.GoogleLoginServiceConstants;
 
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.ProgressDialog;
 import android.app.SearchManager;
@@ -28,6 +27,7 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -35,6 +35,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.DialogInterface.OnCancelListener;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.AssetManager;
@@ -44,19 +45,19 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.DrawFilter;
 import android.graphics.Paint;
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Picture;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.PaintDrawable;
 import android.hardware.SensorListener;
 import android.hardware.SensorManager;
 import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 import android.net.Uri;
 import android.net.WebAddress;
 import android.net.http.EventHandler;
@@ -75,14 +76,15 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.provider.Browser;
-import android.provider.Contacts;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Intents.Insert;
 import android.provider.Downloads;
 import android.provider.MediaStore;
-import android.provider.Contacts.Intents.Insert;
 import android.text.IClipboard;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.text.util.Regex;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.Gravity;
@@ -106,12 +108,17 @@
 import android.webkit.CookieManager;
 import android.webkit.CookieSyncManager;
 import android.webkit.DownloadListener;
+import android.webkit.GeolocationPermissions;
 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.WebChromeClient.CustomViewCallback;
 import android.webkit.WebHistoryItem;
 import android.webkit.WebIconDatabase;
+import android.webkit.WebStorage;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.widget.EditText;
@@ -121,6 +128,7 @@
 import android.widget.Toast;
 
 import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -143,8 +151,7 @@
 import java.util.zip.ZipFile;
 
 public class BrowserActivity extends Activity
-    implements KeyTracker.OnKeyTracker,
-        View.OnCreateContextMenuListener,
+    implements View.OnCreateContextMenuListener,
         DownloadListener {
 
     /* Define some aliases to make these debugging flags easier to refer to.
@@ -268,382 +275,31 @@
                     mGlsConnection, Context.BIND_AUTO_CREATE);
     }
 
-    /**
-     * This class is in charge of installing pre-packaged plugins
-     * from the Browser assets directory to the user's data partition.
-     * Plugins are loaded from the "plugins" directory in the assets;
-     * Anything that is in this directory will be copied over to the
-     * user data partition in app_plugins.
-     */
-    private class CopyPlugins implements Runnable {
-        final static String TAG = "PluginsInstaller";
-        final static String ZIP_FILTER = "assets/plugins/";
-        final static String APK_PATH = "/system/app/Browser.apk";
-        final static String PLUGIN_EXTENSION = ".so";
-        final static String TEMPORARY_EXTENSION = "_temp";
-        final static String BUILD_INFOS_FILE = "build.prop";
-        final static String SYSTEM_BUILD_INFOS_FILE = "/system/"
-                              + BUILD_INFOS_FILE;
-        final int BUFSIZE = 4096;
-        boolean mDoOverwrite = false;
-        String pluginsPath;
-        Context mContext;
-        File pluginsDir;
-        AssetManager manager;
-
-        public CopyPlugins (boolean overwrite, Context context) {
-            mDoOverwrite = overwrite;
-            mContext = context;
-        }
-
-        /**
-         * Returned a filtered list of ZipEntry.
-         * We list all the files contained in the zip and
-         * only returns the ones starting with the ZIP_FILTER
-         * path.
-         *
-         * @param zip the zip file used.
-         */
-        public Vector<ZipEntry> pluginsFilesFromZip(ZipFile zip) {
-            Vector<ZipEntry> list = new Vector<ZipEntry>();
-            Enumeration entries = zip.entries();
-            while (entries.hasMoreElements()) {
-                ZipEntry entry = (ZipEntry) entries.nextElement();
-                if (entry.getName().startsWith(ZIP_FILTER)) {
-                  list.add(entry);
-                }
-            }
-            return list;
-        }
-
-        /**
-         * Utility method to copy the content from an inputstream
-         * to a file output stream.
-         */
-        public void copyStreams(InputStream is, FileOutputStream fos) {
-            BufferedOutputStream os = null;
-            try {
-                byte data[] = new byte[BUFSIZE];
-                int count;
-                os = new BufferedOutputStream(fos, BUFSIZE);
-                while ((count = is.read(data, 0, BUFSIZE)) != -1) {
-                    os.write(data, 0, count);
-                }
-                os.flush();
-            } catch (IOException e) {
-                Log.e(TAG, "Exception while copying: " + e);
-            } finally {
-              try {
-                if (os != null) {
-                    os.close();
-                }
-              } catch (IOException e2) {
-                Log.e(TAG, "Exception while closing the stream: " + e2);
-              }
-            }
-        }
-
-        /**
-         * Returns a string containing the contents of a file
-         *
-         * @param file the target file
-         */
-        private String contentsOfFile(File file) {
-          String ret = null;
-          FileInputStream is = null;
-          try {
-            byte[] buffer = new byte[BUFSIZE];
-            int count;
-            is = new FileInputStream(file);
-            StringBuffer out = new StringBuffer();
-
-            while ((count = is.read(buffer, 0, BUFSIZE)) != -1) {
-              out.append(new String(buffer, 0, count));
-            }
-            ret = out.toString();
-          } catch (IOException e) {
-            Log.e(TAG, "Exception getting contents of file " + e);
-          } finally {
-            if (is != null) {
-              try {
-                is.close();
-              } catch (IOException e2) {
-                Log.e(TAG, "Exception while closing the file: " + e2);
-              }
-            }
-          }
-          return ret;
-        }
-
-        /**
-         * Utility method to initialize the user data plugins path.
-         */
-        public void initPluginsPath() {
-            BrowserSettings s = BrowserSettings.getInstance();
-            pluginsPath = s.getPluginsPath();
-            if (pluginsPath == null) {
-                s.loadFromDb(mContext);
-                pluginsPath = s.getPluginsPath();
-            }
-            if (LOGV_ENABLED) {
-                Log.v(TAG, "Plugin path: " + pluginsPath);
-            }
-        }
-
-        /**
-         * Utility method to delete a file or a directory
-         *
-         * @param file the File to delete
-         */
-        public void deleteFile(File file) {
-            File[] files = file.listFiles();
-            if ((files != null) && files.length > 0) {
-              for (int i=0; i< files.length; i++) {
-                deleteFile(files[i]);
-              }
-            }
-            if (!file.delete()) {
-              Log.e(TAG, file.getPath() + " could not get deleted");
-            }
-        }
-
-        /**
-         * Clean the content of the plugins directory.
-         * We delete the directory, then recreate it.
-         */
-        public void cleanPluginsDirectory() {
-          if (LOGV_ENABLED) {
-            Log.v(TAG, "delete plugins directory: " + pluginsPath);
-          }
-          File pluginsDirectory = new File(pluginsPath);
-          deleteFile(pluginsDirectory);
-          pluginsDirectory.mkdir();
-        }
-
-
-        /**
-         * Copy the SYSTEM_BUILD_INFOS_FILE file containing the
-         * informations about the system build to the
-         * BUILD_INFOS_FILE in the plugins directory.
-         */
-        public void copyBuildInfos() {
-          try {
-            if (LOGV_ENABLED) {
-              Log.v(TAG, "Copy build infos to the plugins directory");
-            }
-            File buildInfoFile = new File(SYSTEM_BUILD_INFOS_FILE);
-            File buildInfoPlugins = new File(pluginsPath, BUILD_INFOS_FILE);
-            copyStreams(new FileInputStream(buildInfoFile),
-                        new FileOutputStream(buildInfoPlugins));
-          } catch (IOException e) {
-            Log.e(TAG, "Exception while copying the build infos: " + e);
-          }
-        }
-
-        /**
-         * Returns true if the current system is newer than the
-         * system that installed the plugins.
-         * We determinate this by checking the build number of the system.
-         *
-         * At the end of the plugins copy operation, we copy the
-         * SYSTEM_BUILD_INFOS_FILE to the BUILD_INFOS_FILE.
-         * We then just have to load both and compare them -- if they
-         * are different the current system is newer.
-         *
-         * Loading and comparing the strings should be faster than
-         * creating a hash, the files being rather small. Extracting the
-         * version number would require some parsing which may be more
-         * brittle.
-         */
-        public boolean newSystemImage() {
-          try {
-            File buildInfoFile = new File(SYSTEM_BUILD_INFOS_FILE);
-            File buildInfoPlugins = new File(pluginsPath, BUILD_INFOS_FILE);
-            if (!buildInfoPlugins.exists()) {
-              if (LOGV_ENABLED) {
-                Log.v(TAG, "build.prop in plugins directory " + pluginsPath
-                  + " does not exist, therefore it's a new system image");
-              }
-              return true;
-            } else {
-              String buildInfo = contentsOfFile(buildInfoFile);
-              String buildInfoPlugin = contentsOfFile(buildInfoPlugins);
-              if (buildInfo == null || buildInfoPlugin == null
-                  || buildInfo.compareTo(buildInfoPlugin) != 0) {
-                if (LOGV_ENABLED) {
-                  Log.v(TAG, "build.prop are different, "
-                    + " therefore it's a new system image");
-                }
-                return true;
-              }
-            }
-          } catch (Exception e) {
-            Log.e(TAG, "Exc in newSystemImage(): " + e);
-          }
-          return false;
-        }
-
-        /**
-         * Check if the version of the plugins contained in the
-         * Browser assets is the same as the version of the plugins
-         * in the plugins directory.
-         * We simply iterate on every file in the assets/plugins
-         * and return false if a file listed in the assets does
-         * not exist in the plugins directory.
-         */
-        private boolean checkIsDifferentVersions() {
-          try {
-            ZipFile zip = new ZipFile(APK_PATH);
-            Vector<ZipEntry> files = pluginsFilesFromZip(zip);
-            int zipFilterLength = ZIP_FILTER.length();
-
-            Enumeration entries = files.elements();
-            while (entries.hasMoreElements()) {
-              ZipEntry entry = (ZipEntry) entries.nextElement();
-              String path = entry.getName().substring(zipFilterLength);
-              File outputFile = new File(pluginsPath, path);
-              if (!outputFile.exists()) {
-                if (LOGV_ENABLED) {
-                  Log.v(TAG, "checkIsDifferentVersions(): extracted file "
-                    + path + " does not exist, we have a different version");
-                }
-                return true;
-              }
-            }
-          } catch (IOException e) {
-            Log.e(TAG, "Exception in checkDifferentVersions(): " + e);
-          }
-          return false;
-        }
-
-        /**
-         * Copy every files from the assets/plugins directory
-         * to the app_plugins directory in the data partition.
-         * Once copied, we copy over the SYSTEM_BUILD_INFOS file
-         * in the plugins directory.
-         *
-         * NOTE: we directly access the content from the Browser
-         * package (it's a zip file) and do not use AssetManager
-         * as there is a limit of 1Mb (see Asset.h)
-         */
-        public void run() {
-            // Lower the priority
-            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-            try {
-                if (pluginsPath == null) {
-                    Log.e(TAG, "No plugins path found!");
-                    return;
-                }
-
-                ZipFile zip = new ZipFile(APK_PATH);
-                Vector<ZipEntry> files = pluginsFilesFromZip(zip);
-                Vector<File> plugins = new Vector<File>();
-                int zipFilterLength = ZIP_FILTER.length();
-
-                Enumeration entries = files.elements();
-                while (entries.hasMoreElements()) {
-                    ZipEntry entry = (ZipEntry) entries.nextElement();
-                    String path = entry.getName().substring(zipFilterLength);
-                    File outputFile = new File(pluginsPath, path);
-                    outputFile.getParentFile().mkdirs();
-
-                    if (outputFile.exists() && !mDoOverwrite) {
-                        if (LOGV_ENABLED) {
-                            Log.v(TAG, path + " already extracted.");
-                        }
-                    } else {
-                        if (path.endsWith(PLUGIN_EXTENSION)) {
-                            // We rename plugins to be sure a half-copied
-                            // plugin is not loaded by the browser.
-                            plugins.add(outputFile);
-                            outputFile = new File(pluginsPath,
-                                path + TEMPORARY_EXTENSION);
-                        }
-                        FileOutputStream fos = new FileOutputStream(outputFile);
-                        if (LOGV_ENABLED) {
-                            Log.v(TAG, "copy " + entry + " to "
-                                + pluginsPath + "/" + path);
-                        }
-                        copyStreams(zip.getInputStream(entry), fos);
-                    }
-                }
-
-                // We now rename the .so we copied, once all their resources
-                // are safely copied over to the user data partition.
-                Enumeration elems = plugins.elements();
-                while (elems.hasMoreElements()) {
-                    File renamedFile = (File) elems.nextElement();
-                    File sourceFile = new File(renamedFile.getPath()
-                        + TEMPORARY_EXTENSION);
-                    if (LOGV_ENABLED) {
-                        Log.v(TAG, "rename " + sourceFile.getPath()
-                            + " to " + renamedFile.getPath());
-                    }
-                    sourceFile.renameTo(renamedFile);
-                }
-
-                copyBuildInfos();
-
-                // Refresh the plugin list.
-                if (mTabControl.getCurrentWebView() != null) {
-                    mTabControl.getCurrentWebView().refreshPlugins(false);
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "IO Exception: " + e);
-            }
-        }
-    };
-
-    /**
-     * Copy the content of assets/plugins/ to the app_plugins directory
-     * in the data partition.
-     *
-     * This function is called every time the browser is started.
-     * We first check if the system image is newer than the one that
-     * copied the plugins (if there's plugins in the data partition).
-     * If this is the case, we then check if the versions are different.
-     * If they are different, we clean the plugins directory in the
-     * data partition, then start a thread to copy the plugins while
-     * the browser continue to load.
-     *
-     * @param overwrite if true overwrite the files even if they are
-     * already present (to let the user "reset" the plugins if needed).
-     */
-    private void copyPlugins(boolean overwrite) {
-        CopyPlugins copyPluginsFromAssets = new CopyPlugins(overwrite, this);
-        copyPluginsFromAssets.initPluginsPath();
-        if (copyPluginsFromAssets.newSystemImage())  {
-          if (copyPluginsFromAssets.checkIsDifferentVersions()) {
-            copyPluginsFromAssets.cleanPluginsDirectory();
-            Thread copyplugins = new Thread(copyPluginsFromAssets);
-            copyplugins.setName("CopyPlugins");
-            copyplugins.start();
-          }
-        }
-    }
-
-    private class ClearThumbnails extends AsyncTask<File, Void, Void> {
+    private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
         @Override
         public Void doInBackground(File... files) {
             if (files != null) {
                 for (File f : files) {
-                    f.delete();
+                    if (!f.delete()) {
+                      Log.e(LOGTAG, f.getPath() + " was not deleted");
+                    }
                 }
             }
             return null;
         }
     }
 
+    /**
+     * This layout holds everything you see below the status bar, including the
+     * error console, the custom view container, and the webviews.
+     */
+    private FrameLayout mBrowserFrameLayout;
+
     @Override public void onCreate(Bundle icicle) {
         if (LOGV_ENABLED) {
             Log.v(LOGTAG, this + " onStart");
         }
         super.onCreate(icicle);
-        this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
-        this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
-        this.requestWindowFeature(Window.FEATURE_PROGRESS);
-        this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-
         // test the browser in OpenGL
         // requestWindowFeature(Window.FEATURE_OPENGL);
 
@@ -651,6 +307,13 @@
 
         mResolver = getContentResolver();
 
+        // If this was a web search request, pass it on to the default web
+        // search provider and finish this activity.
+        if (handleWebSearchIntent(getIntent())) {
+            finish();
+            return;
+        }
+
         //
         // start MASF proxy service
         //
@@ -665,11 +328,19 @@
                 android.R.drawable.ic_secure);
         mMixLockIcon = Resources.getSystem().getDrawable(
                 android.R.drawable.ic_partial_secure);
-        mGenericFavicon = getResources().getDrawable(
-                R.drawable.app_web_browser_sm);
 
-        mContentView = (FrameLayout) getWindow().getDecorView().findViewById(
-                com.android.internal.R.id.content);
+        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);
 
         // Create the tab control and our initial tab
         mTabControl = new TabControl(this);
@@ -695,24 +366,67 @@
                 public void onReceive(Context context, Intent intent) {
                     if (intent.getAction().equals(
                             ConnectivityManager.CONNECTIVITY_ACTION)) {
-                        boolean down = intent.getBooleanExtra(
+                        boolean noConnectivity = intent.getBooleanExtra(
                                 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
-                        onNetworkToggle(!down);
+                        onNetworkToggle(!noConnectivity);
                     }
                 }
             };
 
-        // If this was a web search request, pass it on to the default web search provider.
-        if (handleWebSearchIntent(getIntent())) {
-            moveTaskToBack(true);
-            return;
-        }
+        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;
+                }
+                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.
@@ -726,8 +440,6 @@
                     intent.getData() != null,
                     intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
             mTabControl.setCurrentTab(t);
-            // This is one of the only places we call attachTabToContentView
-            // without animating from the tab picker.
             attachTabToContentView(t);
             WebView webView = t.getWebView();
             if (extra != null) {
@@ -744,7 +456,6 @@
                     && !mSettings.isLoginInitialized()) {
                 setupHomePage();
             }
-            copyPlugins(true);
 
             if (urlData.isEmpty()) {
                 if (mSettings.isLoginInitialized()) {
@@ -761,10 +472,15 @@
             }
         } else {
             // TabControl.restoreState() will create a new tab even if
-            // restoring the state fails. Attach it to the view here since we
-            // are not animating from the tab picker.
+            // restoring the state fails.
             attachTabToContentView(mTabControl.getCurrentTab());
         }
+
+        // Read JavaScript flags if it exists.
+        String jsFlags = mSettings.getJsFlags();
+        if (jsFlags.trim().length() != 0) {
+            mTabControl.getCurrentWebView().setJsFlags(jsFlags);
+        }
     }
 
     @Override
@@ -812,7 +528,7 @@
             if (Intent.ACTION_VIEW.equals(action)
                     && !getPackageName().equals(appId)
                     && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
-                final TabControl.Tab appTab = mTabControl.getTabFromId(appId);
+                TabControl.Tab appTab = mTabControl.getTabFromId(appId);
                 if (appTab != null) {
                     Log.i(LOGTAG, "Reusing tab for " + appId);
                     // Dismiss the subwindow if applicable.
@@ -825,45 +541,47 @@
                     // page, it can be reused.
                     boolean needsLoad =
                             mTabControl.recreateWebView(appTab, urlData.mUrl);
-                    
+
                     if (current != appTab) {
-                        showTab(appTab, needsLoad ? urlData : EMPTY_URL_DATA);
+                        switchToTab(mTabControl.getTabIndex(appTab));
+                        if (needsLoad) {
+                            urlData.loadIn(appTab.getWebView());
+                        }
                     } else {
-                        if (mTabOverview != null && mAnimationCount == 0) {
-                            sendAnimateFromOverview(appTab, false,
-                                    needsLoad ? urlData : EMPTY_URL_DATA, TAB_OVERVIEW_DELAY,
-                                    null);
-                        } else {
-                            // If the tab was the current tab, we have to attach
-                            // it to the view system again.
-                            attachTabToContentView(appTab);
-                            if (needsLoad) {
-                                urlData.loadIn(appTab.getWebView());
-                            }
+                        // If the tab was the current tab, we have to attach
+                        // it to the view system again.
+                        attachTabToContentView(appTab);
+                        if (needsLoad) {
+                            urlData.loadIn(appTab.getWebView());
                         }
                     }
                     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);
+                    }
                 }
-                // 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, null, true, appId);
             } else {
                 if ("about:debug".equals(urlData.mUrl)) {
                     mSettings.toggleDebugSettings();
                     return;
                 }
-                // If the Window overview is up and we are not in the midst of
-                // an animation, animate away from the Window overview.
-                if (mTabOverview != null && mAnimationCount == 0) {
-                    sendAnimateFromOverview(current, false, urlData,
-                            TAB_OVERVIEW_DELAY, null);
-                } else {
-                    // Get rid of the subwindow if it exists
-                    dismissSubWindow(current);
-                    urlData.loadIn(current.getWebView());
-                }
+                // Get rid of the subwindow if it exists
+                dismissSubWindow(current);
+                urlData.loadIn(current.getWebView());
             }
         }
     }
@@ -894,13 +612,15 @@
         String url = null;
         final String action = intent.getAction();
         if (Intent.ACTION_VIEW.equals(action)) {
-            url = intent.getData().toString();
+            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));
+        return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA),
+                intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
     }
 
     /**
@@ -908,7 +628,7 @@
      * 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) {
+    private boolean handleWebSearchRequest(String inUrl, Bundle appData, String extraData) {
         if (inUrl == null) return false;
 
         // In general, we shouldn't modify URL from Intent.
@@ -932,6 +652,9 @@
         if (appData != null) {
             intent.putExtra(SearchManager.APP_DATA, appData);
         }
+        if (extraData != null) {
+            intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
+        }
         intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
         startActivity(intent);
 
@@ -992,6 +715,24 @@
     }
 
     /* 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:") ||
@@ -1107,8 +848,9 @@
             return;
         }
 
+        mTabControl.resumeCurrentTab();
         mActivityInPause = false;
-        resumeWebView();
+        resumeWebViewTimers();
 
         if (mWakeLock.isHeld()) {
             mHandler.removeMessages(RELEASE_WAKELOCK);
@@ -1140,6 +882,206 @@
     }
 
     /**
+     * Since the actual title bar is embedded in the WebView, and removing it
+     * would change its appearance, create a temporary title bar to go at
+     * the top of the screen while the menu is open.
+     */
+    private TitleBar mFakeTitleBar;
+
+    /**
+     * Holder for the fake title bar.  It will have a foreground shadow, as well
+     * as a white background, so the fake title bar looks like the real one.
+     */
+    private ViewGroup mFakeTitleBarHolder;
+
+    /**
+     * Layout parameters for the fake title bar within mFakeTitleBarHolder
+     */
+    private FrameLayout.LayoutParams mFakeTitleBarParams
+            = new FrameLayout.LayoutParams(
+            ViewGroup.LayoutParams.FILL_PARENT,
+            ViewGroup.LayoutParams.WRAP_CONTENT);
+    /**
+     * 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;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Special class used exclusively for the shadow drawn underneath the fake
+     * title bar.  The shadow does not need to be drawn if the WebView
+     * underneath is scrolled to the top, because it will draw directly on top
+     * of the embedded shadow.
+     */
+    private static class Shadow extends View {
+        private WebView mWebView;
+
+        public Shadow(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public void setWebView(WebView view) {
+            mWebView = view;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            // In general onDraw is the method to override, but we care about
+            // whether or not the background gets drawn, which happens in draw()
+            if (mWebView == null || mWebView.getScrollY() > getHeight()) {
+                super.draw(canvas);
+            }
+            // Need to invalidate so that if the scroll position changes, we
+            // still draw as appropriate.
+            invalidate();
+        }
+    }
+
+    private void showFakeTitleBar() {
+        final View decor = getWindow().peekDecorView();
+        if (mFakeTitleBar == null && mActiveTabsPage == null
+                && !mActivityInPause && decor != null
+                && decor.getWindowToken() != null) {
+            Rect visRect = new Rect();
+            if (!mBrowserFrameLayout.getGlobalVisibleRect(visRect)) {
+                if (LOGD_ENABLED) {
+                    Log.d(LOGTAG, "showFakeTitleBar visRect failed");
+                }
+                return;
+            }
+            final WebView webView = getTopWindow();
+            mFakeTitleBar = new TitleBar(this);
+            mFakeTitleBar.setTitleAndUrl(null, webView.getUrl());
+            mFakeTitleBar.setProgress(webView.getProgress());
+            mFakeTitleBar.setFavicon(webView.getFavicon());
+            updateLockIconToLatest();
+
+            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.FILL_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                    PixelFormat.TRANSLUCENT);
+            params.gravity = Gravity.TOP;
+            WebView mainView = mTabControl.getCurrentWebView();
+            boolean atTop = mainView != null && mainView.getScrollY() == 0;
+            params.windowAnimations = atTop ? 0 : R.style.TitleBar;
+            // XXX : Without providing an offset, the fake title bar will be
+            // placed underneath the status bar.  Use the global visible rect
+            // of mBrowserFrameLayout to determine the bottom of the status bar
+            params.y = visRect.top;
+            // Add a holder for the title bar.  It also holds a shadow to show
+            // below the title bar.
+            if (mFakeTitleBarHolder == null) {
+                mFakeTitleBarHolder = (ViewGroup) LayoutInflater.from(this)
+                    .inflate(R.layout.title_bar_bg, null);
+            }
+            Shadow shadow = (Shadow) mFakeTitleBarHolder.findViewById(
+                    R.id.shadow);
+            shadow.setWebView(mainView);
+            mFakeTitleBarHolder.addView(mFakeTitleBar, 0, mFakeTitleBarParams);
+            manager.addView(mFakeTitleBarHolder, 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 == null) return;
+        WindowManager.LayoutParams params = (WindowManager.LayoutParams)
+                mFakeTitleBarHolder.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(mFakeTitleBarHolder, params);
+        mFakeTitleBarHolder.removeView(mFakeTitleBar);
+        manager.removeView(mFakeTitleBarHolder);
+        mFakeTitleBar = null;
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
      *  onSaveInstanceState(Bundle map)
      *  onSaveInstanceState is called right before onStop(). The map contains
      *  the saved state.
@@ -1166,8 +1108,9 @@
             return;
         }
 
+        mTabControl.pauseCurrentTab();
         mActivityInPause = true;
-        if (mTabControl.getCurrentIndex() >= 0 && !pauseWebView()) {
+        if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
             mWakeLock.acquire();
             mHandler.sendMessageDelayed(mHandler
                     .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
@@ -1179,6 +1122,14 @@
         }
         mCredsDlg = null;
 
+        // 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
@@ -1195,6 +1146,9 @@
             Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
         }
         super.onDestroy();
+
+        if (mTabControl == null) return;
+
         // Remove the current tab and sub window
         TabControl.Tab t = mTabControl.getCurrentTab();
         if (t != null) {
@@ -1218,10 +1172,13 @@
         //        "com.android.masfproxyservice",
         //        "com.android.masfproxyservice.MasfProxyService"));
         //stopService(proxyServiceIntent);
+
+        unregisterReceiver(mPackageInstallationReceiver);
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
+        mConfigChanged = true;
         super.onConfigurationChanged(newConfig);
 
         if (mPageInfoDialog != null) {
@@ -1266,7 +1223,7 @@
         mTabControl.freeMemory();
     }
 
-    private boolean resumeWebView() {
+    private boolean resumeWebViewTimers() {
         if ((!mActivityInPause && !mPageStarted) ||
                 (mActivityInPause && mPageStarted)) {
             CookieSyncManager.getInstance().startSync();
@@ -1280,7 +1237,7 @@
         }
     }
 
-    private boolean pauseWebView() {
+    private boolean pauseWebViewTimers() {
         if (mActivityInPause && !mPageStarted) {
             CookieSyncManager.getInstance().stopSync();
             WebView w = mTabControl.getCurrentWebView();
@@ -1293,6 +1250,7 @@
         }
     }
 
+    // FIXME: Do we want to call this when loading google for the first time?
     /*
      * This function is called when we are launching for the first time. We
      * are waiting for the login credentials before loading Google home
@@ -1403,15 +1361,20 @@
         // options selector, so set mCanChord to true so we can access them.
         mCanChord = true;
         int id = item.getItemId();
-        final WebView webView = getTopWindow();
-        if (null == webView) {
-            return false;
-        }
-        final HashMap hrefMap = new HashMap();
-        hrefMap.put("webview", webView);
-        final Message msg = mHandler.obtainMessage(
-                FOCUS_NODE_HREF, id, 0, hrefMap);
         switch (id) {
+            // For the context menu from the title bar
+            case R.id.title_bar_share_page_url:
+            case R.id.title_bar_copy_page_url:
+                WebView mainView = mTabControl.getCurrentWebView();
+                if (null == mainView) {
+                    return false;
+                }
+                if (id == R.id.title_bar_share_page_url) {
+                    Browser.sendString(this, mainView.getUrl());
+                } else {
+                    copy(mainView.getUrl());
+                }
+                break;
             // -- Browser context menu
             case R.id.open_context_menu_id:
             case R.id.open_newtab_context_menu_id:
@@ -1419,6 +1382,14 @@
             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) {
+                    return false;
+                }
+                final HashMap hrefMap = new HashMap();
+                hrefMap.put("webview", webView);
+                final Message msg = mHandler.obtainMessage(
+                        FOCUS_NODE_HREF, id, 0, hrefMap);
                 webView.requestFocusNodeHref(msg);
                 break;
 
@@ -1441,7 +1412,8 @@
      */
     @Override
     public boolean onSearchRequested() {
-        String url = getTopWindow().getUrl();
+        if (mOptionsMenuOpen) closeOptionsMenu();
+        String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
         startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
                 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
         return true;
@@ -1456,6 +1428,82 @@
         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) {
+        TabControl.Tab tab = mTabControl.getTab(index);
+        TabControl.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 */ TabControl.Tab openTabToHomePage() {
+        return openTabAndShow(mSettings.getHomePage(), false, null);
+    }
+
+    /* package */ void closeCurrentWindow() {
+        final TabControl.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.
+            TabControl.Tab newTab = openTabToHomePage();
+            closeTab(current);
+            return;
+        }
+        final TabControl.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();
+    }
+
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (!mCanChord) {
@@ -1463,7 +1511,7 @@
             // menu key.
             return false;
         }
-        if (null == mTabOverview && null == getTopWindow()) {
+        if (null == getTopWindow()) {
             return false;
         }
         if (mMenuIsDown) {
@@ -1475,23 +1523,36 @@
         }
         switch (item.getItemId()) {
             // -- Main menu
-            case R.id.goto_menu_id: {
-                String url = getTopWindow().getUrl();
-                startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
-                        createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_GOTO), false);
-                }
+            case R.id.new_tab_menu_id:
+                openTabToHomePage();
+                break;
+
+            case R.id.goto_menu_id:
+                onSearchRequested();
                 break;
 
             case R.id.bookmarks_menu_id:
                 bookmarksOrHistoryPicker(false);
                 break;
 
-            case R.id.windows_menu_id:
-                if (mTabControl.getTabCount() == 1) {
-                    openTabAndShow(mSettings.getHomePage(), null, false, null);
-                } else {
-                    tabPicker(true, mTabControl.getCurrentIndex(), false);
-                }
+            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:
@@ -1516,21 +1577,7 @@
                     dismissSubWindow(mTabControl.getCurrentTab());
                     break;
                 }
-                final int currentIndex = mTabControl.getCurrentIndex();
-                final TabControl.Tab parent =
-                        mTabControl.getCurrentTab().getParentTab();
-                int indexToShow = -1;
-                if (parent != null) {
-                    indexToShow = mTabControl.getTabIndex(parent);
-                } else {
-                    // Get the last tab in the list. If it is the current tab,
-                    // subtract 1 more.
-                    indexToShow = mTabControl.getTabCount() - 1;
-                    if (currentIndex == indexToShow) {
-                        indexToShow--;
-                    }
-                }
-                switchTabs(currentIndex, indexToShow, true);
+                closeCurrentWindow();
                 break;
 
             case R.id.homepage_menu_id:
@@ -1568,7 +1615,8 @@
                 break;
 
             case R.id.share_page_menu_id:
-                Browser.sendString(this, getTopWindow().getUrl());
+                Browser.sendString(this, getTopWindow().getUrl(),
+                        getText(R.string.choosertitle_sharevia).toString());
                 break;
 
             case R.id.dump_nav_menu_id:
@@ -1587,62 +1635,6 @@
                 viewDownloads(null);
                 break;
 
-            // -- Tab menu
-            case R.id.view_tab_menu_id:
-                if (mTabListener != null && mTabOverview != null) {
-                    int pos = mTabOverview.getContextMenuPosition(item);
-                    mTabOverview.setCurrentIndex(pos);
-                    mTabListener.onClick(pos);
-                }
-                break;
-
-            case R.id.remove_tab_menu_id:
-                if (mTabListener != null && mTabOverview != null) {
-                    int pos = mTabOverview.getContextMenuPosition(item);
-                    mTabListener.remove(pos);
-                }
-                break;
-
-            case R.id.new_tab_menu_id:
-                // No need to check for mTabOverview here since we are not
-                // dependent on it for a position.
-                if (mTabListener != null) {
-                    // If the overview happens to be non-null, make the "New
-                    // Tab" cell visible.
-                    if (mTabOverview != null) {
-                        mTabOverview.setCurrentIndex(ImageGrid.NEW_TAB);
-                    }
-                    mTabListener.onClick(ImageGrid.NEW_TAB);
-                }
-                break;
-
-            case R.id.bookmark_tab_menu_id:
-                if (mTabListener != null && mTabOverview != null) {
-                    int pos = mTabOverview.getContextMenuPosition(item);
-                    TabControl.Tab t = mTabControl.getTab(pos);
-                    // Since we called populatePickerData for all of the
-                    // tabs, getTitle and getUrl will return appropriate
-                    // values.
-                    Browser.saveBookmark(BrowserActivity.this, t.getTitle(),
-                            t.getUrl());
-                }
-                break;
-
-            case R.id.history_tab_menu_id:
-                bookmarksOrHistoryPicker(true);
-                break;
-
-            case R.id.bookmarks_tab_menu_id:
-                bookmarksOrHistoryPicker(false);
-                break;
-
-            case R.id.properties_tab_menu_id:
-                if (mTabListener != null && mTabOverview != null) {
-                    int pos = mTabOverview.getContextMenuPosition(item);
-                    showPageInfo(mTabControl.getTab(pos), false);
-                }
-                break;
-
             case R.id.window_one_menu_id:
             case R.id.window_two_menu_id:
             case R.id.window_three_menu_id:
@@ -1658,7 +1650,7 @@
                             TabControl.Tab desiredTab = mTabControl.getTab(id);
                             if (desiredTab != null &&
                                     desiredTab != mTabControl.getCurrentTab()) {
-                                switchTabs(mTabControl.getCurrentIndex(), id, false);
+                                switchToTab(id);
                             }
                             break;
                         }
@@ -1690,26 +1682,11 @@
         // whether the matching shortcut key will function.
         super.onPrepareOptionsMenu(menu);
         switch (mMenuState) {
-            case R.id.TAB_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);
-                    menu.setGroupVisible(R.id.TAB_MENU, true);
-                    menu.setGroupEnabled(R.id.TAB_MENU, true);
-                }
-                boolean newT = mTabControl.getTabCount() < TabControl.MAX_TABS;
-                final MenuItem tab = menu.findItem(R.id.new_tab_menu_id);
-                tab.setVisible(newT);
-                tab.setEnabled(newT);
-                break;
             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);
-                    menu.setGroupVisible(R.id.TAB_MENU, false);
-                    menu.setGroupEnabled(R.id.TAB_MENU, false);
                 }
                 break;
             default:
@@ -1717,8 +1694,6 @@
                     menu.setGroupVisible(R.id.MAIN_MENU, true);
                     menu.setGroupEnabled(R.id.MAIN_MENU, true);
                     menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
-                    menu.setGroupVisible(R.id.TAB_MENU, false);
-                    menu.setGroupEnabled(R.id.TAB_MENU, false);
                 }
                 final WebView w = getTopWindow();
                 boolean canGoBack = false;
@@ -1738,6 +1713,9 @@
                 menu.findItem(R.id.forward_menu_id)
                         .setEnabled(canGoForward);
 
+                menu.findItem(R.id.new_tab_menu_id).setEnabled(
+                        mTabControl.getTabCount() < TabControl.MAX_TABS);
+
                 // decide whether to show the share link option
                 PackageManager pm = getPackageManager();
                 Intent send = new Intent(Intent.ACTION_SEND);
@@ -1745,12 +1723,6 @@
                 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
                 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
 
-                // If there is only 1 window, the text will be "New window"
-                final MenuItem windows = menu.findItem(R.id.windows_menu_id);
-                windows.setTitleCondensed(mTabControl.getTabCount() > 1 ?
-                        getString(R.string.view_tabs_condensed) :
-                        getString(R.string.tab_picker_new_tab));
-
                 boolean isNavDump = mSettings.isNavDump();
                 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
                 nav.setVisible(isNavDump);
@@ -1811,7 +1783,7 @@
                                 .parse(WebView.SCHEME_TEL + extra)));
                 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
                 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
-                addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
+                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(
@@ -1873,176 +1845,122 @@
     }
 
     // Attach the given tab to the content view.
+    // this should only be called for the current tab.
     private void attachTabToContentView(TabControl.Tab t) {
-        final WebView main = t.getWebView();
-        // Attach the main WebView.
-        mContentView.addView(main, COVER_SCREEN_PARAMS);
-        // Attach the sub window if necessary
-        attachSubWindow(t);
+        // Attach the container that contains the main WebView and any other UI
+        // associated with the tab.
+        t.attachTabToContentView(mContentView);
+
+        if (mShouldShowErrorConsole) {
+            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+            if (errorConsole.numberOfErrors() == 0) {
+                errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+            } else {
+                errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+            }
+
+            mErrorConsoleContainer.addView(errorConsole,
+                    new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                                                  ViewGroup.LayoutParams.WRAP_CONTENT));
+        }
+
+        setLockIconType(t.getLockIconType());
+        setPrevLockType(t.getPrevLockIconType());
+
+        // this is to match the code in removeTabFromContentView()
+        if (!mPageStarted && t.getTopWindow().getProgress() < 100) {
+            mPageStarted = true;
+        }
+
+        WebView view = t.getWebView();
+        view.setEmbeddedTitleBar(mTitleBar);
         // Request focus on the top window.
         t.getTopWindow().requestFocus();
     }
 
     // Attach a sub window to the main WebView of the given tab.
     private void attachSubWindow(TabControl.Tab t) {
-        // If a sub window exists, attach it to the content view.
-        final WebView subView = t.getSubWebView();
-        if (subView != null) {
-            final View container = t.getSubWebViewContainer();
-            mContentView.addView(container, COVER_SCREEN_PARAMS);
-            subView.requestFocus();
-        }
+        t.attachSubWindow(mContentView);
+        getTopWindow().requestFocus();
     }
 
     // Remove the given tab from the content view.
     private void removeTabFromContentView(TabControl.Tab t) {
-        // Remove the main WebView.
-        mContentView.removeView(t.getWebView());
-        // Remove the sub window if it exists.
-        if (t.getSubWebView() != null) {
-            mContentView.removeView(t.getSubWebViewContainer());
+        // Remove the container that contains the main WebView.
+        t.removeTabFromContentView(mContentView);
+
+        if (mTabControl.getCurrentErrorConsole(false) != null) {
+            mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
+        }
+
+        WebView view = t.getWebView();
+        if (view != null) {
+            view.setEmbeddedTitleBar(null);
+        }
+
+        // unlike attachTabToContentView(), removeTabFromContentView() can be
+        // called for the non-current tab. Need to add the check.
+        if (t == mTabControl.getCurrentTab()) {
+            t.setLockIconType(getLockIconType());
+            t.setPrevLockIconType(getPrevLockType());
+
+            // this is not a perfect solution. But currently there is one
+            // WebViewClient for all the WebView. if user switches from an
+            // in-load window to an already loaded window, mPageStarted will not
+            // be set to false. If user leaves the Browser, pauseWebViewTimers()
+            // won't do anything and leaves the timer running even Browser is in
+            // the background.
+            if (mPageStarted) {
+                mPageStarted = false;
+            }
         }
     }
 
     // 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(TabControl.Tab t) {
-        final WebView mainView = t.getWebView();
-        if (t.getSubWebView() != null) {
-            // Remove the container view and request focus on the main WebView.
-            mContentView.removeView(t.getSubWebViewContainer());
-            mainView.requestFocus();
-            // Tell the TabControl to dismiss the subwindow. This will destroy
-            // the WebView.
-            mTabControl.dismissSubWindow(t);
-        }
+        t.removeSubWindow(mContentView);
+        // Tell the TabControl to dismiss the subwindow. This will destroy
+        // the WebView.
+        mTabControl.dismissSubWindow(t);
+        getTopWindow().requestFocus();
     }
 
-    // Send the ANIMTE_FROM_OVERVIEW message after changing the current tab.
-    private void sendAnimateFromOverview(final TabControl.Tab tab,
-            final boolean newTab, final UrlData urlData, final int delay,
-            final Message msg) {
-        // Set the current tab.
-        mTabControl.setCurrentTab(tab);
-        // Attach the WebView so it will layout.
-        attachTabToContentView(tab);
-        // Set the view to invisibile for now.
-        tab.getWebView().setVisibility(View.INVISIBLE);
-        // If there is a sub window, make it invisible too.
-        if (tab.getSubWebView() != null) {
-            tab.getSubWebViewContainer().setVisibility(View.INVISIBLE);
-        }
-        // Create our fake animating view.
-        final AnimatingView view = new AnimatingView(this, tab);
-        // Attach it to the view system and make in invisible so it will
-        // layout but not flash white on the screen.
-        mContentView.addView(view, COVER_SCREEN_PARAMS);
-        view.setVisibility(View.INVISIBLE);
-        // Send the animate message.
-        final HashMap map = new HashMap();
-        map.put("view", view);
-        // Load the url after the AnimatingView has captured the picture. This
-        // prevents any bad layout or bad scale from being used during
-        // animation.
-        if (!urlData.isEmpty()) {
-            dismissSubWindow(tab);
-            urlData.loadIn(tab.getWebView());
-        }
-        map.put("msg", msg);
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                ANIMATE_FROM_OVERVIEW, newTab ? 1 : 0, 0, map), delay);
-        // Increment the count to indicate that we are in an animation.
-        mAnimationCount++;
-        // Remove the listener so we don't get any more tab changes.
-        mTabOverview.setListener(null);
-        mTabListener = null;
-        // Make the menu empty until the animation completes.
-        mMenuState = EMPTY_MENU;
-
-    }
-
-    // 500ms animation with 800ms delay
-    private static final int TAB_ANIMATION_DURATION = 200;
-    private static final int TAB_OVERVIEW_DELAY     = 500;
-
-    // Called by TabControl when a tab is requesting focus
-    /* package */ void showTab(TabControl.Tab t) {
-        showTab(t, EMPTY_URL_DATA);
-    }
-
-    private void showTab(TabControl.Tab t, UrlData urlData) {
-        // Disallow focus change during a tab animation.
-        if (mAnimationCount > 0) {
-            return;
-        }
-        int delay = 0;
-        if (mTabOverview == null) {
-            // Add a delay so the tab overview can be shown before the second
-            // animation begins.
-            delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
-            tabPicker(false, mTabControl.getTabIndex(t), false);
-        }
-        sendAnimateFromOverview(t, false, urlData, delay, null);
-    }
-
-    // A wrapper function of {@link #openTabAndShow(UrlData, Message, boolean, String)}
+    // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
     // that accepts url as string.
-    private TabControl.Tab openTabAndShow(String url, final Message msg,
-            boolean closeOnExit, String appId) {
-        return openTabAndShow(new UrlData(url), msg, closeOnExit, appId);
+    private TabControl.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. If the tab overview is not
-    // showing, it will animate to the tab overview, create a new tab and
-    // animate away from it. After the animation completes, it will dispatch
-    // the given Message. If the tab overview is already showing (i.e. this
-    // method is called from TabListener.onClick(), the method will animate
-    // away from the tab overview.
-    private TabControl.Tab openTabAndShow(UrlData urlData, final Message msg,
+    // url isn't null, it will load the given url.
+    /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
             boolean closeOnExit, String appId) {
         final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
         final TabControl.Tab currentTab = mTabControl.getCurrentTab();
         if (newTab) {
-            int delay = 0;
-            // If the tab overview is up and there are animations, just load
-            // the url.
-            if (mTabOverview != null && mAnimationCount > 0) {
-                if (!urlData.isEmpty()) {
-                    // We should not have a msg here since onCreateWindow
-                    // checks the animation count and every other caller passes
-                    // null.
-                    assert msg == null;
-                    // just dismiss the subwindow and load the given url.
-                    dismissSubWindow(currentTab);
-                    urlData.loadIn(currentTab.getWebView());
-                }
-            } else {
-                // show mTabOverview if it is not there.
-                if (mTabOverview == null) {
-                    // We have to delay the animation from the tab picker by the
-                    // length of the tab animation. Add a delay so the tab
-                    // overview can be shown before the second animation begins.
-                    delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
-                    tabPicker(false, ImageGrid.NEW_TAB, false);
-                }
-                // Animate from the Tab overview after any animations have
-                // finished.
-                final TabControl.Tab tab = mTabControl.createNewTab(
-                        closeOnExit, appId, urlData.mUrl);
-                sendAnimateFromOverview(tab, true, urlData, delay, msg);
-                return tab;
+            final TabControl.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);
             }
-        } else if (!urlData.isEmpty()) {
-            // We should not have a msg here.
-            assert msg == null;
-            if (mTabOverview != null && mAnimationCount == 0) {
-                sendAnimateFromOverview(currentTab, false, urlData,
-                        TAB_OVERVIEW_DELAY, null);
-            } else {
-                // Get rid of the subwindow if it exists
-                dismissSubWindow(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()) {
+                urlData.loadIn(webview);
+            }
+            return tab;
+        } else {
+            // Get rid of the subwindow if it exists
+            dismissSubWindow(currentTab);
+            if (!urlData.isEmpty()) {
                 // Load the given url.
                 urlData.loadIn(currentTab.getWebView());
             }
@@ -2050,234 +1968,16 @@
         return currentTab;
     }
 
-    private Animation createTabAnimation(final AnimatingView view,
-            final View cell, boolean scaleDown) {
-        final AnimationSet set = new AnimationSet(true);
-        final float scaleX = (float) cell.getWidth() / view.getWidth();
-        final float scaleY = (float) cell.getHeight() / view.getHeight();
-        if (scaleDown) {
-            set.addAnimation(new ScaleAnimation(1.0f, scaleX, 1.0f, scaleY));
-            set.addAnimation(new TranslateAnimation(0, cell.getLeft(), 0,
-                    cell.getTop()));
-        } else {
-            set.addAnimation(new ScaleAnimation(scaleX, 1.0f, scaleY, 1.0f));
-            set.addAnimation(new TranslateAnimation(cell.getLeft(), 0,
-                    cell.getTop(), 0));
-        }
-        set.setDuration(TAB_ANIMATION_DURATION);
-        set.setInterpolator(new DecelerateInterpolator());
-        return set;
-    }
-
-    // Animate to the tab overview. currentIndex tells us which position to
-    // animate to and newIndex is the position that should be selected after
-    // the animation completes.
-    // If remove is true, after the animation stops, a confirmation dialog will
-    // be displayed to the user.
-    private void animateToTabOverview(final int newIndex, final boolean remove,
-            final AnimatingView view) {
-        // Find the view in the ImageGrid allowing for the "New Tab" cell.
-        int position = mTabControl.getTabIndex(view.mTab);
-        if (!((ImageAdapter) mTabOverview.getAdapter()).maxedOut()) {
-            position++;
-        }
-
-        // Offset the tab position with the first visible position to get a
-        // number between 0 and 3.
-        position -= mTabOverview.getFirstVisiblePosition();
-
-        // Grab the view that we are going to animate to.
-        final View v = mTabOverview.getChildAt(position);
-
-        final Animation.AnimationListener l =
-                new Animation.AnimationListener() {
-                    public void onAnimationStart(Animation a) {
-                        if (mTabOverview != null) {
-                            mTabOverview.requestFocus();
-                            // Clear the listener so we don't trigger a tab
-                            // selection.
-                            mTabOverview.setListener(null);
-                        }
-                    }
-                    public void onAnimationRepeat(Animation a) {}
-                    public void onAnimationEnd(Animation a) {
-                        // We are no longer animating so decrement the count.
-                        mAnimationCount--;
-                        // Make the view GONE so that it will not draw between
-                        // now and when the Runnable is handled.
-                        view.setVisibility(View.GONE);
-                        // Post a runnable since we can't modify the view
-                        // hierarchy during this callback.
-                        mHandler.post(new Runnable() {
-                            public void run() {
-                                // Remove the AnimatingView.
-                                mContentView.removeView(view);
-                                if (mTabOverview != null) {
-                                    // Make newIndex visible.
-                                    mTabOverview.setCurrentIndex(newIndex);
-                                    // Restore the listener.
-                                    mTabOverview.setListener(mTabListener);
-                                    // Change the menu to TAB_MENU if the
-                                    // ImageGrid is interactive.
-                                    if (mTabOverview.isLive()) {
-                                        mMenuState = R.id.TAB_MENU;
-                                        mTabOverview.requestFocus();
-                                    }
-                                }
-                                // If a remove was requested, remove the tab.
-                                if (remove) {
-                                    // During a remove, the current tab has
-                                    // already changed. Remember the current one
-                                    // here.
-                                    final TabControl.Tab currentTab =
-                                            mTabControl.getCurrentTab();
-                                    // Remove the tab at newIndex from
-                                    // TabControl and the tab overview.
-                                    final TabControl.Tab tab =
-                                            mTabControl.getTab(newIndex);
-                                    mTabControl.removeTab(tab);
-                                    // Restore the current tab.
-                                    if (currentTab != tab) {
-                                        mTabControl.setCurrentTab(currentTab);
-                                    }
-                                    if (mTabOverview != null) {
-                                        mTabOverview.remove(newIndex);
-                                        // Make the current tab visible.
-                                        mTabOverview.setCurrentIndex(
-                                                mTabControl.getCurrentIndex());
-                                    }
-                                }
-                            }
-                        });
-                    }
-                };
-
-        // Do an animation if there is a view to animate to.
-        if (v != null) {
-            // Create our animation
-            final Animation anim = createTabAnimation(view, v, true);
-            anim.setAnimationListener(l);
-            // Start animating
-            view.startAnimation(anim);
-        } else {
-            // If something goes wrong and we didn't find a view to animate to,
-            // just do everything here.
-            l.onAnimationStart(null);
-            l.onAnimationEnd(null);
-        }
-    }
-
-    // Animate from the tab picker. The index supplied is the index to animate
-    // from.
-    private void animateFromTabOverview(final AnimatingView view,
-            final boolean newTab, final Message msg) {
-        // firstVisible is the first visible tab on the screen.  This helps
-        // to know which corner of the screen the selected tab is.
-        int firstVisible = mTabOverview.getFirstVisiblePosition();
-        // tabPosition is the 0-based index of of the tab being opened
-        int tabPosition = mTabControl.getTabIndex(view.mTab);
-        if (!((ImageAdapter) mTabOverview.getAdapter()).maxedOut()) {
-            // Add one to make room for the "New Tab" cell.
-            tabPosition++;
-        }
-        // If this is a new tab, animate from the "New Tab" cell.
-        if (newTab) {
-            tabPosition = 0;
-        }
-        // Location corresponds to the four corners of the screen.
-        // A new tab or 0 is upper left, 0 for an old tab is upper
-        // right, 1 is lower left, and 2 is lower right
-        int location = tabPosition - firstVisible;
-
-        // Find the view at this location.
-        final View v = mTabOverview.getChildAt(location);
-
-        // Wait until the animation completes to replace the AnimatingView.
-        final Animation.AnimationListener l =
-                new Animation.AnimationListener() {
-                    public void onAnimationStart(Animation a) {}
-                    public void onAnimationRepeat(Animation a) {}
-                    public void onAnimationEnd(Animation a) {
-                        mHandler.post(new Runnable() {
-                            public void run() {
-                                mContentView.removeView(view);
-                                // Dismiss the tab overview. If the cell at the
-                                // given location is null, set the fade
-                                // parameter to true.
-                                dismissTabOverview(v == null);
-                                TabControl.Tab t =
-                                        mTabControl.getCurrentTab();
-                                mMenuState = R.id.MAIN_MENU;
-                                // Resume regular updates.
-                                t.getWebView().resumeTimers();
-                                // Dispatch the message after the animation
-                                // completes.
-                                if (msg != null) {
-                                    msg.sendToTarget();
-                                }
-                                // The animation is done and the tab overview is
-                                // gone so allow key events and other animations
-                                // to begin.
-                                mAnimationCount--;
-                                // Reset all the title bar info.
-                                resetTitle();
-                            }
-                        });
-                    }
-                };
-
-        if (v != null) {
-            final Animation anim = createTabAnimation(view, v, false);
-            // Set the listener and start animating
-            anim.setAnimationListener(l);
-            view.startAnimation(anim);
-            // Make the view VISIBLE during the animation.
-            view.setVisibility(View.VISIBLE);
-        } else {
-            // Go ahead and do all the cleanup.
-            l.onAnimationEnd(null);
-        }
-    }
-
-    // Dismiss the tab overview applying a fade if needed.
-    private void dismissTabOverview(final boolean fade) {
-        if (fade) {
-            AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f);
-            anim.setDuration(500);
-            anim.startNow();
-            mTabOverview.startAnimation(anim);
-        }
-        // Just in case there was a problem with animating away from the tab
-        // overview
-        WebView current = mTabControl.getCurrentWebView();
-        if (current != null) {
-            current.setVisibility(View.VISIBLE);
-        } else {
-            Log.e(LOGTAG, "No current WebView in dismissTabOverview");
-        }
-        // Make the sub window container visible.
-        if (mTabControl.getCurrentSubWindow() != null) {
-            mTabControl.getCurrentTab().getSubWebViewContainer()
-                    .setVisibility(View.VISIBLE);
-        }
-        mContentView.removeView(mTabOverview);
-        // Clear all the data for tab picker so next time it will be
-        // recreated.
-        mTabControl.wipeAllPickerData();
-        mTabOverview.clear();
-        mTabOverview = null;
-        mTabListener = null;
-    }
-
     private TabControl.Tab openTab(String url) {
         if (mSettings.openInBackground()) {
             TabControl.Tab t = mTabControl.createNewTab();
             if (t != null) {
-                t.getWebView().loadUrl(url);
+                WebView view = t.getWebView();
+                view.loadUrl(url);
             }
             return t;
         } else {
-            return openTabAndShow(url, null, false, null);
+            return openTabAndShow(url, false, null);
         }
     }
 
@@ -2319,15 +2019,6 @@
     }
 
     /**
-     * Resets the browser title-view to whatever it must be (for example, if we
-     * load a page from history).
-     */
-    private void resetTitle() {
-        resetLockIcon();
-        resetTitleIconAndProgress();
-    }
-
-    /**
      * 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
@@ -2374,43 +2065,13 @@
         mUrl = url;
         mTitle = title;
 
-        // While the tab overview is animating or being shown, block changes
-        // to the title.
-        if (mAnimationCount == 0 && mTabOverview == null) {
-            setTitle(buildUrlTitle(url, title));
+        mTitleBar.setTitleAndUrl(title, url);
+        if (mFakeTitleBar != null) {
+            mFakeTitleBar.setTitleAndUrl(title, url);
         }
     }
 
     /**
-     * Builds and returns the page title, which is some
-     * combination of the page URL and title.
-     * @param url The URL of the site being loaded.
-     * @param title The title of the site being loaded.
-     * @return The page title.
-     */
-    private String buildUrlTitle(String url, String title) {
-        String urlTitle = "";
-
-        if (url != null) {
-            String titleUrl = buildTitleUrl(url);
-
-            if (title != null && 0 < title.length()) {
-                if (titleUrl != null && 0 < titleUrl.length()) {
-                    urlTitle = titleUrl + ": " + title;
-                } else {
-                    urlTitle = title;
-                }
-            } else {
-                if (titleUrl != null) {
-                    urlTitle = titleUrl;
-                }
-            }
-        }
-
-        return urlTitle;
-    }
-
-    /**
      * @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,
@@ -2418,7 +2079,7 @@
      * or an empty string if, for example, the URL in question is a
      * file:// URL with no hostname.
      */
-    private static String buildTitleUrl(String url) {
+    /* package */ static String buildTitleUrl(String url) {
         String titleUrl = null;
 
         if (url != null) {
@@ -2449,23 +2110,10 @@
 
     // Set the favicon in the title bar.
     private void setFavicon(Bitmap icon) {
-        // While the tab overview is animating or being shown, block changes to
-        // the favicon.
-        if (mAnimationCount > 0 || mTabOverview != null) {
-            return;
+        mTitleBar.setFavicon(icon);
+        if (mFakeTitleBar != null) {
+            mFakeTitleBar.setFavicon(icon);
         }
-        Drawable[] array = new Drawable[2];
-        PaintDrawable p = new PaintDrawable(Color.WHITE);
-        p.setCornerRadius(3f);
-        array[0] = p;
-        if (icon == null) {
-            array[1] = mGenericFavicon;
-        } else {
-            array[1] = new BitmapDrawable(icon);
-        }
-        LayerDrawable d = new LayerDrawable(array);
-        d.setLayerInset(1, 2, 2, 2, 2);
-        getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
     }
 
     /**
@@ -2490,29 +2138,22 @@
                   " revert lock icon to " + mLockIconType);
         }
 
-        updateLockIconImage(mLockIconType);
+        updateLockIconToLatest();
     }
 
-    private void switchTabs(int indexFrom, int indexToShow, boolean remove) {
-        int delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
-        // Animate to the tab picker, remove the current tab, then
-        // animate away from the tab picker to the parent WebView.
-        tabPicker(false, indexFrom, remove);
-        // Change to the parent tab
-        final TabControl.Tab tab = mTabControl.getTab(indexToShow);
-        if (tab != null) {
-            sendAnimateFromOverview(tab, false, EMPTY_URL_DATA, delay, null);
-        } else {
-            // Increment this here so that no other animations can happen in
-            // between the end of the tab picker transition and the beginning
-            // of openTabAndShow. This has a matching decrement in the handler
-            // of OPEN_TAB_AND_SHOW.
-            mAnimationCount++;
-            // Send a message to open a new tab.
-            mHandler.sendMessageDelayed(
-                    mHandler.obtainMessage(OPEN_TAB_AND_SHOW,
-                        mSettings.getHomePage()), delay);
+    /**
+     * Close the tab, remove its associated title bar, and adjust mTabControl's
+     * current tab to a valid value.
+     */
+    /* package */ void closeTab(TabControl.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();
     }
 
     private void goBackOnePageOrQuit() {
@@ -2526,6 +2167,7 @@
              * moveTaskToBack().
              */
             moveTaskToBack(true);
+            return;
         }
         WebView w = current.getWebView();
         if (w.canGoBack()) {
@@ -2535,25 +2177,31 @@
             // another window. If so, we switch back to that window.
             TabControl.Tab parent = current.getParentTab();
             if (parent != null) {
-                switchTabs(mTabControl.getCurrentIndex(),
-                        mTabControl.getTabIndex(parent), true);
+                switchToTab(mTabControl.getTabIndex(parent));
+                // Now we close the other tab
+                closeTab(current);
             } else {
                 if (current.closeOnExit()) {
+                    // force mPageStarted to be false as we are going to either
+                    // finish the activity or remove the tab. This will ensure
+                    // pauseWebView() taking action.
+                    mPageStarted = false;
                     if (mTabControl.getTabCount() == 1) {
                         finish();
                         return;
                     }
-                    // call pauseWebView() 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 pauseWebView() will
-                    // do nothing if mActivityInPause is false.
+                    // 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.");
+                        Log.e(LOGTAG, "BrowserActivity is already paused "
+                                + "while handing goBackOnePageOrQuit.");
                     }
                     mActivityInPause = true;
-                    pauseWebView();
+                    pauseWebViewTimers();
                     mActivityInPause = savedState;
                     removeTabFromContentView(current);
                     mTabControl.removeTab(current);
@@ -2570,84 +2218,76 @@
         }
     }
 
-    public KeyTracker.State onKeyTracker(int keyCode,
-                                         KeyEvent event,
-                                         KeyTracker.Stage stage,
-                                         int duration) {
-        // if onKeyTracker() is called after activity onStop()
-        // because of accumulated key events,
-        // we should ignore it as browser is not active any more.
-        WebView topWindow = getTopWindow();
-        if (topWindow == null)
-            return KeyTracker.State.NOT_TRACKING;
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent 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;
 
-        if (keyCode == KeyEvent.KEYCODE_BACK) {
-            // During animations, block the back key so that other animations
-            // are not triggered and so that we don't end up destroying all the
-            // WebViews before finishing the animation.
-            if (mAnimationCount > 0) {
-                return KeyTracker.State.DONE_TRACKING;
-            }
-            if (stage == KeyTracker.Stage.LONG_REPEAT) {
-                bookmarksOrHistoryPicker(true);
-                return KeyTracker.State.DONE_TRACKING;
-            } else if (stage == KeyTracker.Stage.UP) {
-                // FIXME: Currently, we do not have a notion of the
-                // history picker for the subwindow, but maybe we
-                // should?
-                WebView subwindow = mTabControl.getCurrentSubWindow();
-                if (subwindow != null) {
-                    if (subwindow.canGoBack()) {
-                        subwindow.goBack();
-                    } else {
-                        dismissSubWindow(mTabControl.getCurrentTab());
-                    }
+        switch(keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                mMenuIsDown = true;
+                break;
+            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 {
-                    goBackOnePageOrQuit();
+                    getTopWindow().pageDown(false);
                 }
-                return KeyTracker.State.DONE_TRACKING;
-            }
-            return KeyTracker.State.KEEP_TRACKING;
+                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 KeyTracker.State.NOT_TRACKING;
+        return super.onKeyDown(keyCode, event);
     }
 
-    @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_MENU) {
-            mMenuIsDown = true;
-        } else if (mMenuIsDown) {
-            // 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.
-            return true;
-        }
-        boolean handled =  mKeyTracker.doKeyDown(keyCode, event);
-        if (!handled) {
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_SPACE:
-                    if (event.isShiftPressed()) {
-                        getTopWindow().pageUp(false);
+    @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
+                        mWebChromeClient.onHideCustomView();
+                    } else if (mActiveTabsPage != null) {
+                        // if tab page is showing, hide it
+                        removeActiveTabPage(true);
                     } else {
-                        getTopWindow().pageDown(false);
+                        WebView subwindow = mTabControl.getCurrentSubWindow();
+                        if (subwindow != null) {
+                            if (subwindow.canGoBack()) {
+                                subwindow.goBack();
+                            } else {
+                                dismissSubWindow(mTabControl.getCurrentTab());
+                            }
+                        } else {
+                            goBackOnePageOrQuit();
+                        }
                     }
-                    handled = true;
-                    break;
-
-                default:
-                    break;
-            }
+                    return true;
+                }
+                break;
         }
-        return handled || super.onKeyDown(keyCode, event);
+        return super.onKeyUp(keyCode, event);
     }
 
-    @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_MENU) {
-            mMenuIsDown = false;
-        }
-        return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
-    }
-
-    private void stopLoading() {
+    /* package */ void stopLoading() {
+        mDidStopLoad = true;
         resetTitleAndRevertLockIcon();
         WebView w = getTopWindow();
         w.stopLoading();
@@ -2678,37 +2318,17 @@
     // Message Ids
     private static final int FOCUS_NODE_HREF         = 102;
     private static final int CANCEL_CREDS_REQUEST    = 103;
-    private static final int ANIMATE_FROM_OVERVIEW   = 104;
-    private static final int ANIMATE_TO_OVERVIEW     = 105;
-    private static final int OPEN_TAB_AND_SHOW       = 106;
-    private static final int CHECK_MEMORY            = 107;
-    private static final int RELEASE_WAKELOCK        = 108;
+    private static final int RELEASE_WAKELOCK        = 107;
+
+    private 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 ANIMATE_FROM_OVERVIEW:
-                    final HashMap map = (HashMap) msg.obj;
-                    animateFromTabOverview((AnimatingView) map.get("view"),
-                            msg.arg1 == 1, (Message) map.get("msg"));
-                    break;
-
-                case ANIMATE_TO_OVERVIEW:
-                    animateToTabOverview(msg.arg1, msg.arg2 == 1,
-                            (AnimatingView) msg.obj);
-                    break;
-
-                case OPEN_TAB_AND_SHOW:
-                    // Decrement mAnimationCount before openTabAndShow because
-                    // the method relies on the value being 0 to start the next
-                    // animation.
-                    mAnimationCount--;
-                    openTabAndShow((String) msg.obj, null, false, null);
-                    break;
-
                 case FOCUS_NODE_HREF:
+                {
                     String url = (String) msg.getData().get("url");
                     if (url == null || url.length() == 0) {
                         break;
@@ -2739,7 +2359,8 @@
                             startActivity(intent);
                             break;
                         case R.id.share_link_context_menu_id:
-                            Browser.sendString(BrowserActivity.this, url);
+                            Browser.sendString(BrowserActivity.this, url,
+                                    getText(R.string.choosertitle_sharevia).toString());
                             break;
                         case R.id.copy_link_context_menu_id:
                             copy(url);
@@ -2750,6 +2371,7 @@
                             break;
                     }
                     break;
+                }
 
                 case LOAD_URL:
                     loadURL(getTopWindow(), (String) msg.obj);
@@ -2763,23 +2385,112 @@
                     resumeAfterCredentials();
                     break;
 
-                case CHECK_MEMORY:
-                    // reschedule to check memory condition
-                    mHandler.removeMessages(CHECK_MEMORY);
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage
-                            (CHECK_MEMORY), CHECK_MEMORY_INTERVAL);
-                    checkMemory();
-                    break;
-
                 case RELEASE_WAKELOCK:
                     if (mWakeLock.isHeld()) {
                         mWakeLock.release();
                     }
                     break;
+
+                case UPDATE_BOOKMARK_THUMBNAIL:
+                    WebView view = (WebView) msg.obj;
+                    if (view != null) {
+                        updateScreenshot(view);
+                    }
+                    break;
             }
         }
     };
 
+    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.
+
+        ContentResolver cr = getContentResolver();
+        final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
+                cr, view.getOriginalUrl(), view.getUrl(), true);
+        if (c != null) {
+            boolean succeed = c.moveToFirst();
+            ContentValues values = null;
+            while (succeed) {
+                if (values == null) {
+                    final ByteArrayOutputStream os
+                            = new ByteArrayOutputStream();
+                    Bitmap bm = createScreenshot(view);
+                    if (bm == null) {
+                        c.close();
+                        return;
+                    }
+                    bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+                    values = new ContentValues();
+                    values.put(Browser.BookmarkColumns.THUMBNAIL,
+                            os.toByteArray());
+                }
+                cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
+                        c.getInt(0)), values, null, null);
+                succeed = c.moveToNext();
+            }
+            c.close();
+        }
+    }
+
+    /**
+     * 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.ARGB_4444);
+        Canvas canvas = new Canvas(bm);
+        // May need to tweak these values to determine what is the
+        // best scale factor
+        int thumbnailWidth = thumbnail.getWidth();
+        if (thumbnailWidth > 0) {
+            float scaleFactor = (float) getDesiredThumbnailWidth(this) /
+                    (float)thumbnailWidth;
+            canvas.scale(scaleFactor, scaleFactor);
+        }
+        thumbnail.draw(canvas);
+        return bm;
+    }
+
     // -------------------------------------------------------------------------
     // WebViewClient implementation.
     //-------------------------------------------------------------------------
@@ -2794,10 +2505,18 @@
         return mWebViewClient;
     }
 
+    private void updateIcon(WebView view, Bitmap icon) {
+        if (icon != null) {
+            BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
+                    view.getOriginalUrl(), view.getUrl(), icon);
+        }
+        setFavicon(icon);
+    }
+
     private void updateIcon(String url, Bitmap icon) {
         if (icon != null) {
             BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
-                    url, icon);
+                    null, url, icon);
         }
         setFavicon(icon);
     }
@@ -2807,35 +2526,45 @@
         public void onPageStarted(WebView view, String url, Bitmap favicon) {
             resetLockIcon(url);
             setUrlTitle(url, null);
+
+            // 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(UPDATE_BOOKMARK_THUMBNAIL);
+
+            // 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
+            // create a new one when the touch icon url is known.
+            if (mTouchIconLoader != null) {
+                mTouchIconLoader.mActivity = null;
+                mTouchIconLoader = null;
+            }
+
+            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
+            if (errorConsole != null) {
+                errorConsole.clearErrorMessages();
+                if (mShouldShowErrorConsole) {
+                    errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+                }
+            }
+
             // Call updateIcon instead of setFavicon so the bookmark
             // database can be updated.
             updateIcon(url, favicon);
 
-            if (mSettings.isTracing() == true) {
-                // FIXME: we should save the trace file somewhere other than data.
-                // I can't use "/tmp" as it competes for system memory.
-                File file = getDir("browserTrace", 0);
-                String baseDir = file.getPath();
-                if (!baseDir.endsWith(File.separator)) baseDir += File.separator;
+            if (mSettings.isTracing()) {
                 String host;
                 try {
                     WebAddress uri = new WebAddress(url);
                     host = uri.mHost;
                 } catch (android.net.ParseException ex) {
-                    host = "unknown_host";
+                    host = "browser";
                 }
                 host = host.replace('.', '_');
-                baseDir = baseDir + host;
-                file = new File(baseDir+".data");
-                if (file.exists() == true) {
-                    file.delete();
-                }
-                file = new File(baseDir+".key");
-                if (file.exists() == true) {
-                    file.delete();
-                }
+                host += ".trace";
                 mInTrace = true;
-                Debug.startMethodTracing(baseDir, 8 * 1024 * 1024);
+                Debug.startMethodTracing(host, 20 * 1024 * 1024);
             }
 
             // Performance probe
@@ -2855,31 +2584,24 @@
 
             if (!mPageStarted) {
                 mPageStarted = true;
-                // if onResume() has been called, resumeWebView() does nothing.
-                resumeWebView();
+                // if onResume() has been called, resumeWebViewTimers() does
+                // nothing.
+                resumeWebViewTimers();
             }
 
             // reset sync timer to avoid sync starts during loading a page
             CookieSyncManager.getInstance().resetSync();
 
             mInLoad = true;
+            mDidStopLoad = false;
+            showFakeTitleBar();
             updateInLoadMenuItems();
             if (!mIsNetworkUp) {
-                if ( mAlertDialog == null) {
-                    mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
-                        .setTitle(R.string.loadSuspendedTitle)
-                        .setMessage(R.string.loadSuspended)
-                        .setPositiveButton(R.string.ok, null)
-                        .show();
-                }
+                createAndShowNetworkDialog();
                 if (view != null) {
                     view.setNetworkAvailable(false);
                 }
             }
-
-            // schedule to check memory condition
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(CHECK_MEMORY),
-                    CHECK_MEMORY_INTERVAL);
         }
 
         @Override
@@ -2888,8 +2610,15 @@
             // load.
             resetTitleAndIcon(view);
 
+            if (!mDidStopLoad) {
+                // Only update the bookmark screenshot if the user did not
+                // cancel the load early.
+                Message updateScreenshot = Message.obtain(mHandler, UPDATE_BOOKMARK_THUMBNAIL, view);
+                mHandler.sendMessageDelayed(updateScreenshot, 500);
+            }
+
             // Update the lock icon image only once we are done loading
-            updateLockIconImage(mLockIconType);
+            updateLockIconToLatest();
 
             // Performance probe
             if (false) {
@@ -2977,18 +2706,15 @@
 
             if (mPageStarted) {
                 mPageStarted = false;
-                // pauseWebView() will do nothing and return false if onPause()
-                // is not called yet.
-                if (pauseWebView()) {
+                // pauseWebViewTimers() will do nothing and return false if
+                // onPause() is not called yet.
+                if (pauseWebViewTimers()) {
                     if (mWakeLock.isHeld()) {
                         mHandler.removeMessages(RELEASE_WAKELOCK);
                         mWakeLock.release();
                     }
                 }
             }
-
-            mHandler.removeMessages(CHECK_MEMORY);
-            checkMemory();
         }
 
         // return true if want to hijack the url to let another app to handle it
@@ -3025,9 +2751,9 @@
             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);
@@ -3167,7 +2893,7 @@
                 }
             }
             ErrorDialog errDialog = new ErrorDialog(
-                    err == EventHandler.FILE_NOT_FOUND_ERROR ?
+                    err == WebViewClient.ERROR_FILE_NOT_FOUND ?
                     R.string.browserFrameFileErrorLabel :
                     R.string.browserFrameNetworkErrorLabel,
                     desc, err);
@@ -3196,11 +2922,11 @@
         @Override
         public void onReceivedError(WebView view, int errorCode,
                 String description, String failingUrl) {
-            if (errorCode != EventHandler.ERROR_LOOKUP &&
-                    errorCode != EventHandler.ERROR_CONNECT &&
-                    errorCode != EventHandler.ERROR_BAD_URL &&
-                    errorCode != EventHandler.ERROR_UNSUPPORTED_SCHEME &&
-                    errorCode != EventHandler.FILE_ERROR) {
+            if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
+                    errorCode != WebViewClient.ERROR_CONNECT &&
+                    errorCode != WebViewClient.ERROR_BAD_URL &&
+                    errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
+                    errorCode != WebViewClient.ERROR_FILE) {
                 queueError(errorCode, description);
             }
             Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
@@ -3247,6 +2973,19 @@
             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);
+                }
+            }
             Browser.updateVisitedHistory(mResolver, url, true);
             WebIconDatabase.getInstance().retainIconForPageUrl(url);
         }
@@ -3412,28 +3151,21 @@
                 msg.sendToTarget();
             } else {
                 final TabControl.Tab parent = mTabControl.getCurrentTab();
-                // openTabAndShow will dispatch the message after creating the
-                // new WebView. This will prevent another request from coming
-                // in during the animation.
-                final TabControl.Tab newTab =
-                        openTabAndShow(EMPTY_URL_DATA, msg, false, null);
+                final TabControl.Tab newTab
+                        = openTabAndShow(EMPTY_URL_DATA, false, null);
                 if (newTab != parent) {
                     parent.addChildTab(newTab);
                 }
                 WebView.WebViewTransport transport =
                         (WebView.WebViewTransport) msg.obj;
                 transport.setWebView(mTabControl.getCurrentWebView());
+                msg.sendToTarget();
             }
         }
 
         @Override
         public boolean onCreateWindow(WebView view, final boolean dialog,
                 final boolean userGesture, final Message resultMsg) {
-            // Ignore these requests during tab animations or if the tab
-            // overview is showing.
-            if (mAnimationCount > 0 || mTabOverview != null) {
-                return false;
-            }
             // Short-circuit if we can't create any more tabs or sub windows.
             if (dialog && mTabControl.getCurrentSubWindow() != null) {
                 new AlertDialog.Builder(BrowserActivity.this)
@@ -3455,9 +3187,6 @@
 
             // Short-circuit if this was a user gesture.
             if (userGesture) {
-                // createWindow will call openTabAndShow for new Windows and
-                // that will call tabPicker which will increment
-                // mAnimationCount.
                 createWindow(dialog, resultMsg);
                 return true;
             }
@@ -3467,12 +3196,7 @@
                     new AlertDialog.OnClickListener() {
                         public void onClick(DialogInterface d,
                                 int which) {
-                            // Same comment as above for setting
-                            // mAnimationCount.
                             createWindow(dialog, resultMsg);
-                            // Since we incremented mAnimationCount while the
-                            // dialog was up, we have to decrement it here.
-                            mAnimationCount--;
                         }
                     };
 
@@ -3481,9 +3205,6 @@
                     new AlertDialog.OnClickListener() {
                         public void onClick(DialogInterface d, int which) {
                             resultMsg.sendToTarget();
-                            // We are not going to trigger an animation so
-                            // unblock keys and animation requests.
-                            mAnimationCount--;
                         }
                     };
 
@@ -3500,51 +3221,57 @@
 
             // Show the confirmation dialog.
             d.show();
-            // We want to increment mAnimationCount here to prevent a
-            // potential race condition. If the user allows a pop-up from a
-            // site and that pop-up then triggers another pop-up, it is
-            // possible to get the BACK key between here and when the dialog
-            // appears.
-            mAnimationCount++;
             return true;
         }
 
         @Override
         public void onCloseWindow(WebView window) {
-            final int currentIndex = mTabControl.getCurrentIndex();
-            final TabControl.Tab parent =
-                    mTabControl.getCurrentTab().getParentTab();
+            final TabControl.Tab current = mTabControl.getCurrentTab();
+            final TabControl.Tab parent = current.getParentTab();
             if (parent != null) {
                 // JavaScript can only close popup window.
-                switchTabs(currentIndex, mTabControl.getTabIndex(parent), true);
+                switchToTab(mTabControl.getTabIndex(parent));
+                // Now we need to close the window
+                closeTab(current);
             }
         }
 
         @Override
         public void onProgressChanged(WebView view, int newProgress) {
-            // Block progress updates to the title bar while the tab overview
-            // is animating or being displayed.
-            if (mAnimationCount == 0 && mTabOverview == null) {
-                getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
-                        newProgress * 100);
+            mTitleBar.setProgress(newProgress);
+            if (mFakeTitleBar != null) {
+                mFakeTitleBar.setProgress(newProgress);
             }
 
             if (newProgress == 100) {
-                // onProgressChanged() is called for sub-frame too while
-                // onPageFinished() is only called for the main frame. sync
-                // cookie and cache promptly here.
+                // 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)
+
+                // sync cookies and cache promptly here.
                 CookieSyncManager.getInstance().sync();
                 if (mInLoad) {
                     mInLoad = false;
                     updateInLoadMenuItems();
+                    // If the options menu is open, leave the title bar
+                    if (!mOptionsMenuOpen || !mIconView) {
+                        hideFakeTitleBar();
+                    }
                 }
-            } else {
+            } 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.
-                if (!mInLoad) {
-                    mInLoad = true;
-                    updateInLoadMenuItems();
+                mInLoad = true;
+                updateInLoadMenuItems();
+                if (!mOptionsMenuOpen || mIconView) {
+                    // This page has begun to load, so show the title bar
+                    showFakeTitleBar();
                 }
             }
         }
@@ -3560,6 +3287,8 @@
                 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
                 return;
             }
+            // See if we can find the current url in our history database and
+            // add the new title to it.
             if (url.startsWith("http://www.")) {
                 url = url.substring(11);
             } else if (url.startsWith("http://")) {
@@ -3574,9 +3303,6 @@
                 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
                     Browser.HISTORY_PROJECTION, where, selArgs, null);
                 if (c.moveToFirst()) {
-                    if (LOGV_ENABLED) {
-                        Log.v(LOGTAG, "updating cursor");
-                    }
                     // Current implementation of database only has one entry per
                     // url.
                     ContentValues map = new ContentValues();
@@ -3594,8 +3320,200 @@
 
         @Override
         public void onReceivedIcon(WebView view, Bitmap icon) {
-            updateIcon(view.getUrl(), icon);
+            updateIcon(view, icon);
         }
+
+        @Override
+        public void onReceivedTouchIconUrl(WebView view, String url,
+                boolean precomposed) {
+            final ContentResolver cr = getContentResolver();
+            final Cursor c =
+                    BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
+                            view.getOriginalUrl(), view.getUrl(), true);
+            if (c != null) {
+                if (c.getCount() > 0) {
+                    // Let precomposed icons take precedence over non-composed
+                    // icons.
+                    if (precomposed && mTouchIconLoader != null) {
+                        mTouchIconLoader.cancel(false);
+                        mTouchIconLoader = null;
+                    }
+                    // Have only one async task at a time.
+                    if (mTouchIconLoader == null) {
+                        mTouchIconLoader = new DownloadTouchIcon(
+                                BrowserActivity.this, cr, c, view);
+                        mTouchIconLoader.execute(url);
+                    }
+                } else {
+                    c.close();
+                }
+            }
+        }
+
+        @Override
+        public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
+            if (mCustomView != null)
+                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.
+            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;
+            // Reset the old menu state.
+            mMenuState = mOldMenuState;
+            mOldMenuState = EMPTY_MENU;
+            mCustomViewContainer.setVisibility(View.GONE);
+            mCustomViewCallback.onCustomViewHidden();
+            // Show the content view.
+            mContentView.setVisibility(View.VISIBLE);
+        }
+
+        /**
+         * The origin has exceeded its database quota.
+         * @param url the URL that exceeded the quota
+         * @param databaseIdentifier the identifier of the database on
+         *     which the transaction that caused the quota overflow was run
+         * @param currentQuota the current quota for the origin.
+         * @param estimatedSize the estimated size of the database.
+         * @param totalUsedQuota is the sum of all origins' quota.
+         * @param quotaUpdater The callback to run when a decision to allow or
+         *     deny quota has been made. Don't forget to call this!
+         */
+        @Override
+        public void onExceededDatabaseQuota(String url,
+            String databaseIdentifier, long currentQuota, long estimatedSize,
+            long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
+            mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
+                    url, databaseIdentifier, currentQuota, estimatedSize,
+                    totalUsedQuota, quotaUpdater);
+        }
+
+        /**
+         * The Application Cache has exceeded its max size.
+         * @param spaceNeeded is the amount of disk space that would be needed
+         * in order for the last appcache operation to succeed.
+         * @param totalUsedQuota is the sum of all origins' quota.
+         * @param quotaUpdater A callback to inform the WebCore thread that a new
+         * app cache size is available. This callback must always be executed at
+         * some point to ensure that the sleeping WebCore thread is woken up.
+         */
+        @Override
+        public void onReachedMaxAppCacheSize(long spaceNeeded,
+                long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
+            mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
+                    spaceNeeded, totalUsedQuota, quotaUpdater);
+        }
+
+        /**
+         * Instructs the browser to show a prompt to ask the user to set the
+         * Geolocation permission state for the specified origin.
+         * @param origin The origin for which Geolocation permissions are
+         *     requested.
+         * @param callback The callback to call once the user has set the
+         *     Geolocation permission state.
+         */
+        @Override
+        public void onGeolocationPermissionsShowPrompt(String origin,
+                GeolocationPermissions.Callback callback) {
+            mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
+                    origin, callback);
+        }
+
+        /**
+         * Instructs the browser to hide the Geolocation permissions prompt.
+         */
+        @Override
+        public void onGeolocationPermissionsHidePrompt() {
+            mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
+        }
+
+        /* Adds a JavaScript error message to the system log.
+         * @param message The error message to report.
+         * @param lineNumber The line number of the error.
+         * @param sourceID The name of the source file that caused the error.
+         */
+        @Override
+        public void addMessageToConsole(String message, int lineNumber, String sourceID) {
+            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+            errorConsole.addErrorMessage(message, sourceID, lineNumber);
+                if (mShouldShowErrorConsole &&
+                        errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
+                    errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+                }
+            Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
+        }
+
+        /**
+         * Ask the browser for an icon to represent a <video> element.
+         * This icon will be used if the Web page did not specify a poster attribute.
+         *
+         * @return Bitmap The icon or null if no such icon is available.
+         * @hide pending API Council approval
+         */
+        @Override
+        public Bitmap getDefaultVideoPoster() {
+            if (mDefaultVideoPoster == null) {
+                mDefaultVideoPoster = BitmapFactory.decodeResource(
+                        getResources(), R.drawable.default_video_poster);
+            }
+            return mDefaultVideoPoster;
+        }
+
+        /**
+         * Ask the host application for a custom progress view to show while
+         * a <video> is loading.
+         *
+         * @return View The progress view.
+         * @hide pending API Council approval
+         */
+        @Override
+        public View getVideoLoadingProgressView() {
+            if (mVideoProgressView == null) {
+                LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this);
+                mVideoProgressView = inflater.inflate(R.layout.video_loading_progress, null);
+            }
+            return mVideoProgressView;
+        }
+
+        /**
+         * Deliver a list of already-visited URLs
+         * @hide pending API Council approval
+         */
+        @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(getContentResolver());
+                }
+
+                public void onPostExecute(String[] result) {
+                    callback.onReceiveValue(result);
+
+                };
+            };
+            task.execute();
+        };
     };
 
     /**
@@ -3612,23 +3530,36 @@
         // 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)) {
+                || !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);
-            if (getPackageManager().resolveActivity(intent,
-                        PackageManager.MATCH_DEFAULT_ONLY) != null) {
-                // someone 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);
+            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
                     }
-                    // Best behavior is to fall back to a download in this case
                 }
             }
         }
@@ -3711,19 +3642,19 @@
         String cookies = CookieManager.getInstance().getCookie(url);
 
         ContentValues values = new ContentValues();
-        values.put(Downloads.URI, uri.toString());
-        values.put(Downloads.COOKIE_DATA, cookies);
-        values.put(Downloads.USER_AGENT, userAgent);
-        values.put(Downloads.NOTIFICATION_PACKAGE,
+        values.put(Downloads.COLUMN_URI, uri.toString());
+        values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
+        values.put(Downloads.COLUMN_USER_AGENT, userAgent);
+        values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
                 getPackageName());
-        values.put(Downloads.NOTIFICATION_CLASS,
+        values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
                 BrowserDownloadPage.class.getCanonicalName());
-        values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
-        values.put(Downloads.MIMETYPE, mimetype);
-        values.put(Downloads.FILENAME_HINT, filename);
-        values.put(Downloads.DESCRIPTION, uri.getHost());
+        values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+        values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
+        values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
+        values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
         if (contentLength > 0) {
-            values.put(Downloads.TOTAL_BYTES, contentLength);
+            values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
         }
         if (mimetype == null) {
             // We must have long pressed on a link or image to download it. We
@@ -3757,23 +3688,27 @@
         updateLockIconImage(LOCK_ICON_UNSECURE);
     }
 
+    /* package */ void setLockIconType(int type) {
+        mLockIconType = type;
+    }
+
+    /* package */ int getLockIconType() {
+        return mLockIconType;
+    }
+
+    /* package */ void setPrevLockType(int type) {
+        mPrevLockType = type;
+    }
+
+    /* package */ int getPrevLockType() {
+        return mPrevLockType;
+    }
+
     /**
-     * Resets the lock icon.  This method is called when the icon needs to be
-     * reset but we do not know whether we are loading a secure or not secure
-     * page.
+     * Update the lock icon to correspond to our latest state.
      */
-    private void resetLockIcon() {
-        // Save the lock-icon state (we revert to it if the load gets cancelled)
-        saveLockIcon();
-
-        mLockIconType = LOCK_ICON_UNSECURE;
-
-        if (LOGV_ENABLED) {
-          Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
-                " reset lock icon to " + mLockIconType);
-        }
-
-        updateLockIconImage(LOCK_ICON_UNSECURE);
+    /* package */ void updateLockIconToLatest() {
+        updateLockIconImage(mLockIconType);
     }
 
     /**
@@ -3786,10 +3721,9 @@
         } else if (lockIconType == LOCK_ICON_MIXED) {
             d = mMixLockIcon;
         }
-        // If the tab overview is animating or being shown, do not update the
-        // lock icon.
-        if (mAnimationCount == 0 && mTabOverview == null) {
-            getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
+        mTitleBar.setLock(d);
+        if (mFakeTitleBar != null) {
+            mFakeTitleBar.setLock(d);
         }
     }
 
@@ -4259,13 +4193,9 @@
             }
         } else {
             mIsNetworkUp = false;
-            if (mInLoad && mAlertDialog == null) {
-                mAlertDialog = new AlertDialog.Builder(this)
-                        .setTitle(R.string.loadSuspendedTitle)
-                        .setMessage(R.string.loadSuspended)
-                        .setPositiveButton(R.string.ok, null)
-                        .show();
-            }
+            if (mInLoad) {
+                createAndShowNetworkDialog();
+           }
         }
         WebView w = mTabControl.getCurrentWebView();
         if (w != null) {
@@ -4273,6 +4203,18 @@
         }
     }
 
+    // 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
     protected void onActivityResult(int requestCode, int resultCode,
                                     Intent intent) {
@@ -4286,17 +4228,9 @@
                     } else {
                         final TabControl.Tab currentTab =
                                 mTabControl.getCurrentTab();
-                        // If the Window overview is up and we are not in the
-                        // middle of an animation, animate away from it to the
-                        // current tab.
-                        if (mTabOverview != null && mAnimationCount == 0) {
-                            sendAnimateFromOverview(currentTab, false, new UrlData(data),
-                                    TAB_OVERVIEW_DELAY, null);
-                        } else {
-                            dismissSubWindow(currentTab);
-                            if (data != null && data.length() != 0) {
-                                getTopWindow().loadUrl(data);
-                            }
+                        dismissSubWindow(currentTab);
+                        if (data != null && data.length() != 0) {
+                            getTopWindow().loadUrl(data);
                         }
                     }
                 }
@@ -4321,176 +4255,11 @@
     }
 
     /**
-     * Handle results from Tab Switcher mTabOverview tool
+     * Open the Go page.
+     * @param startWithHistory If true, open starting on the history tab.
+     *                         Otherwise, start with the bookmarks tab.
      */
-    private class TabListener implements ImageGrid.Listener {
-        public void remove(int position) {
-            // Note: Remove is not enabled if we have only one tab.
-            if (DEBUG && mTabControl.getTabCount() == 1) {
-                throw new AssertionError();
-            }
-
-            // Remember the current tab.
-            TabControl.Tab current = mTabControl.getCurrentTab();
-            final TabControl.Tab remove = mTabControl.getTab(position);
-            mTabControl.removeTab(remove);
-            // If we removed the current tab, use the tab at position - 1 if
-            // possible.
-            if (current == remove) {
-                // If the user removes the last tab, act like the New Tab item
-                // was clicked on.
-                if (mTabControl.getTabCount() == 0) {
-                    current = mTabControl.createNewTab();
-                    sendAnimateFromOverview(current, true,
-                            new UrlData(mSettings.getHomePage()), TAB_OVERVIEW_DELAY, null);
-                } else {
-                    final int index = position > 0 ? (position - 1) : 0;
-                    current = mTabControl.getTab(index);
-                }
-            }
-
-            // The tab overview could have been dismissed before this method is
-            // called.
-            if (mTabOverview != null) {
-                // Remove the tab and change the index.
-                mTabOverview.remove(position);
-                mTabOverview.setCurrentIndex(mTabControl.getTabIndex(current));
-            }
-
-            // Only the current tab ensures its WebView is non-null. This
-            // implies that we are reloading the freed tab.
-            mTabControl.setCurrentTab(current);
-        }
-        public void onClick(int index) {
-            // Change the tab if necessary.
-            // Index equals ImageGrid.CANCEL when pressing back from the tab
-            // overview.
-            if (index == ImageGrid.CANCEL) {
-                index = mTabControl.getCurrentIndex();
-                // The current index is -1 if the current tab was removed.
-                if (index == -1) {
-                    // Take the last tab as a fallback.
-                    index = mTabControl.getTabCount() - 1;
-                }
-            }
-
-            // NEW_TAB means that the "New Tab" cell was clicked on.
-            if (index == ImageGrid.NEW_TAB) {
-                openTabAndShow(mSettings.getHomePage(), null, false, null);
-            } else {
-                sendAnimateFromOverview(mTabControl.getTab(index),
-                        false, EMPTY_URL_DATA, 0, null);
-            }
-        }
-    }
-
-    // A fake View that draws the WebView's picture with a fast zoom filter.
-    // The View is used in case the tab is freed during the animation because
-    // of low memory.
-    private static class AnimatingView extends View {
-        private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
-                Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG;
-        private static final DrawFilter sZoomFilter =
-                new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
-        private final Picture mPicture;
-        private final float   mScale;
-        private final int     mScrollX;
-        private final int     mScrollY;
-        final TabControl.Tab  mTab;
-
-        AnimatingView(Context ctxt, TabControl.Tab t) {
-            super(ctxt);
-            mTab = t;
-            // Use the top window in the animation since the tab overview will
-            // display the top window in each cell.
-            final WebView w = t.getTopWindow();
-            mPicture = w.capturePicture();
-            mScale = w.getScale() / w.getWidth();
-            mScrollX = w.getScrollX();
-            mScrollY = w.getScrollY();
-        }
-
-        @Override
-        protected void onDraw(Canvas canvas) {
-            canvas.save();
-            canvas.drawColor(Color.WHITE);
-            if (mPicture != null) {
-                canvas.setDrawFilter(sZoomFilter);
-                float scale = getWidth() * mScale;
-                canvas.scale(scale, scale);
-                canvas.translate(-mScrollX, -mScrollY);
-                canvas.drawPicture(mPicture);
-            }
-            canvas.restore();
-        }
-    }
-
-    /**
-     *  Open the tab picker. This function will always use the current tab in
-     *  its animation.
-     *  @param stay boolean stating whether the tab picker is to remain open
-     *          (in which case it needs a listener and its menu) or not.
-     *  @param index The index of the tab to show as the selection in the tab
-     *               overview.
-     *  @param remove If true, the tab at index will be removed after the
-     *                animation completes.
-     */
-    private void tabPicker(final boolean stay, final int index,
-            final boolean remove) {
-        if (mTabOverview != null) {
-            return;
-        }
-
-        int size = mTabControl.getTabCount();
-
-        TabListener l = null;
-        if (stay) {
-            l = mTabListener = new TabListener();
-        }
-        mTabOverview = new ImageGrid(this, stay, l);
-
-        for (int i = 0; i < size; i++) {
-            final TabControl.Tab t = mTabControl.getTab(i);
-            mTabControl.populatePickerData(t);
-            mTabOverview.add(t);
-        }
-
-        // Tell the tab overview to show the current tab, the tab overview will
-        // handle the "New Tab" case.
-        int currentIndex = mTabControl.getCurrentIndex();
-        mTabOverview.setCurrentIndex(currentIndex);
-
-        // Attach the tab overview.
-        mContentView.addView(mTabOverview, COVER_SCREEN_PARAMS);
-
-        // Create a fake AnimatingView to animate the WebView's picture.
-        final TabControl.Tab current = mTabControl.getCurrentTab();
-        final AnimatingView v = new AnimatingView(this, current);
-        mContentView.addView(v, COVER_SCREEN_PARAMS);
-        removeTabFromContentView(current);
-        // Pause timers to get the animation smoother.
-        current.getWebView().pauseTimers();
-
-        // Send a message so the tab picker has a chance to layout and get
-        // positions for all the cells.
-        mHandler.sendMessage(mHandler.obtainMessage(ANIMATE_TO_OVERVIEW,
-                index, remove ? 1 : 0, v));
-        // Setting this will indicate that we are animating to the overview. We
-        // set it here to prevent another request to animate from coming in
-        // between now and when ANIMATE_TO_OVERVIEW is handled.
-        mAnimationCount++;
-        // Always change the title bar to the window overview title while
-        // animating.
-        getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, null);
-        getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null);
-        getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
-                Window.PROGRESS_VISIBILITY_OFF);
-        setTitle(R.string.tab_picker_title);
-        // Make the menu empty until the animation completes.
-        mMenuState = EMPTY_MENU;
-    }
-
-    private void bookmarksOrHistoryPicker(boolean startWithHistory) {
+    /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
         WebView current = mTabControl.getCurrentWebView();
         if (current == null) {
             return;
@@ -4499,6 +4268,8 @@
                 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) {
@@ -4514,8 +4285,11 @@
         }
         intent.putExtra("title", title);
         intent.putExtra("url", url);
-        intent.putExtra("maxTabsOpen",
-                mTabControl.getTabCount() >= TabControl.MAX_TABS);
+        intent.putExtra("thumbnail", thumbnail);
+        // Disable opening in a new window if we have maxed out the windows
+        intent.putExtra("disable_new_window", mTabControl.getTabCount()
+                >= TabControl.MAX_TABS);
+        intent.putExtra("touch_icon_url", current.getTouchIconUrl());
         if (startWithHistory) {
             intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
                     CombinedBookmarkHistoryActivity.HISTORY_TAB);
@@ -4534,21 +4308,6 @@
         }
     }
 
-    private void checkMemory() {
-        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
-        ((ActivityManager) getSystemService(ACTIVITY_SERVICE))
-                .getMemoryInfo(mi);
-        // FIXME: mi.lowMemory is too aggressive, use (mi.availMem <
-        // mi.threshold) for now
-        //        if (mi.lowMemory) {
-        if (mi.availMem < mi.threshold) {
-            Log.w(LOGTAG, "Browser is freeing memory now because: available="
-                            + (mi.availMem / 1024) + "K threshold="
-                            + (mi.threshold / 1024) + "K");
-            mTabControl.freeMemory();
-        }
-    }
-
     private String smartUrlFilter(Uri inUri) {
         if (inUri != null) {
             return smartUrlFilter(inUri.toString());
@@ -4566,7 +4325,7 @@
       return 0;
     }
 
-    static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
+    protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
             "(?i)" + // switch on case insensitive matching
             "(" +    // begin group for schema
             "(?:http|https|file):\\/\\/" +
@@ -4631,9 +4390,37 @@
         return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
     }
 
-    private final static int LOCK_ICON_UNSECURE = 0;
-    private final static int LOCK_ICON_SECURE   = 1;
-    private final static int LOCK_ICON_MIXED    = 2;
+    /* package */ void setShouldShowErrorConsole(boolean flag) {
+        if (flag == mShouldShowErrorConsole) {
+            // Nothing to do.
+            return;
+        }
+
+        mShouldShowErrorConsole = flag;
+
+        ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(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.FILL_PARENT,
+                                                  ViewGroup.LayoutParams.WRAP_CONTENT));
+        } else {
+            mErrorConsoleContainer.removeView(errorConsole);
+        }
+
+    }
+
+    final static int LOCK_ICON_UNSECURE = 0;
+    final static int LOCK_ICON_SECURE   = 1;
+    final static int LOCK_ICON_MIXED    = 2;
 
     private int mLockIconType = LOCK_ICON_UNSECURE;
     private int mPrevLockType = LOCK_ICON_UNSECURE;
@@ -4642,12 +4429,15 @@
     private TabControl      mTabControl;
     private ContentResolver mResolver;
     private FrameLayout     mContentView;
-    private ImageGrid       mTabOverview;
+    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;
 
@@ -4658,17 +4448,13 @@
 
     private boolean mInLoad;
     private boolean mIsNetworkUp;
+    private boolean mDidStopLoad;
 
     private boolean mPageStarted;
     private boolean mActivityInPause = true;
 
     private boolean mMenuIsDown;
 
-    private final KeyTracker mKeyTracker = new KeyTracker(this);
-
-    // As trackball doesn't send repeat down, we have to track it ourselves
-    private boolean mTrackTrackball;
-
     private static boolean mInTrace;
 
     // Performance probe
@@ -4694,7 +4480,6 @@
 
     private Drawable    mMixLockIcon;
     private Drawable    mSecLockIcon;
-    private Drawable    mGenericFavicon;
 
     /* hold a ref so we can auto-cancel if necessary */
     private AlertDialog mAlertDialog;
@@ -4738,6 +4523,11 @@
                                             new FrameLayout.LayoutParams(
                                             ViewGroup.LayoutParams.FILL_PARENT,
                                             ViewGroup.LayoutParams.FILL_PARENT);
+    /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
+                                            new FrameLayout.LayoutParams(
+                                            ViewGroup.LayoutParams.FILL_PARENT,
+                                            ViewGroup.LayoutParams.FILL_PARENT,
+                                            Gravity.CENTER);
     // Google search
     final static String QuickSearch_G = "http://www.google.com/m?q=%s";
     // Wikipedia search
@@ -4762,8 +4552,6 @@
 
     private final static String LOGTAG = "browser";
 
-    private TabListener mTabListener;
-
     private String mLastEnteredUrl;
 
     private PowerManager.WakeLock mWakeLock;
@@ -4771,11 +4559,10 @@
 
     private Toast mStopToast;
 
-    // Used during animations to prevent other animations from being triggered.
-    // A count is used since the animation to and from the Window overview can
-    // overlap. A count of 0 means no animation where a count of > 0 means
-    // there are animations in progress.
-    private int mAnimationCount;
+    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.
@@ -4788,13 +4575,20 @@
     private IntentFilter mNetworkStateChangedFilter;
     private BroadcastReceiver mNetworkStateIntentReceiver;
 
-    // activity requestCode
-    final static int COMBO_PAGE             = 1;
-    final static int DOWNLOAD_PAGE          = 2;
-    final static int PREFERENCES_PAGE       = 3;
+    private BroadcastReceiver mPackageInstallationReceiver;
 
-    // the frenquency of checking whether system memory is low
-    final static int CHECK_MEMORY_INTERVAL = 30000;     // 30 seconds
+    // AsyncTask for downloading touch icons
+    /* package */ DownloadTouchIcon mTouchIconLoader;
+
+    // activity requestCode
+    final static int COMBO_PAGE                 = 1;
+    final static int DOWNLOAD_PAGE              = 2;
+    final static int PREFERENCES_PAGE           = 3;
+
+    // the default <video> poster
+    private Bitmap mDefaultVideoPoster;
+    // the video progress view
+    private View mVideoProgressView;
 
     /**
      * A UrlData class to abstract how the content will be set to WebView.
@@ -4841,7 +4635,7 @@
         String mEncoding;
         @Override
         boolean isEmpty() {
-            return mInlined == null || mInlined.length() == 0 || super.isEmpty(); 
+            return mInlined == null || mInlined.length() == 0 || super.isEmpty();
         }
 
         @Override
@@ -4850,5 +4644,5 @@
         }
     }
 
-    private static final UrlData EMPTY_URL_DATA = new UrlData(null);
+    /* 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
new file mode 100644
index 0000000..387555d
--- /dev/null
+++ b/src/com/android/browser/BrowserBackupAgent.java
@@ -0,0 +1,298 @@
+/*
+ * 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 java.io.IOException;
+
+import android.app.BackupAgent;
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.database.Cursor;
+import android.os.ParcelFileDescriptor;
+import android.provider.Browser;
+import android.provider.Browser.BookmarkColumns;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.zip.CRC32;
+
+/**
+ * Settings backup agent for the Android browser.  Currently the only thing
+ * stored is the set of bookmarks.  It's okay if I/O exceptions are thrown
+ * out of the agent; the calling code handles it and the backup operation
+ * simply fails.
+ *
+ * @hide
+ */
+public class BrowserBackupAgent extends BackupAgent {
+    static final String TAG = "BrowserBackupAgent";
+    static final boolean DEBUG = false;
+
+    static final String BOOKMARK_KEY = "_bookmarks_";
+    /** this version num MUST be incremented if the flattened-file schema ever changes */
+    static final int BACKUP_AGENT_VERSION = 0;
+
+    /**
+     * In order to determine whether the bookmark set has changed since the
+     * last time we did a backup, we store the following bits of info in the
+     * state file after a backup:
+     *
+     * 1. the size of the flattened bookmark file
+     * 2. the CRC32 of that file
+     * 3. the agent version number [relevant following an OTA]
+     *
+     * After we flatten the bookmarks file here in onBackup, we compare its
+     * metrics with the values from the saved state.  If they match, it means
+     * the bookmarks didn't really change and we don't need to send the data.
+     * (If they don't match, of course, then they've changed and we do indeed
+     * send the new flattened file to be backed up.)
+     */
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState) throws IOException {
+        long savedFileSize = -1;
+        long savedCrc = -1;
+        int savedVersion = -1;
+
+        // Extract the previous bookmark file size & CRC from the saved state
+        DataInputStream in = new DataInputStream(
+                new FileInputStream(oldState.getFileDescriptor()));
+        try {
+            savedFileSize = in.readLong();
+            savedCrc = in.readLong();
+            savedVersion = in.readInt();
+        } catch (EOFException e) {
+            // It means we had no previous state; that's fine
+        }
+
+        // Build a flattened representation of the bookmarks table
+        File tmpfile = File.createTempFile("bkp", null, getCacheDir());
+        try {
+            FileOutputStream outfstream = new FileOutputStream(tmpfile);
+            long newCrc = buildBookmarkFile(outfstream);
+            outfstream.close();
+
+            // Any changes since the last backup?
+            if ((savedVersion != BACKUP_AGENT_VERSION)
+                    || (newCrc != savedCrc)
+                    || (tmpfile.length() != savedFileSize)) {
+                // Different checksum or different size, so we need to back it up
+                copyFileToBackup(BOOKMARK_KEY, tmpfile, data);
+            }
+
+            // Record our backup state and we're done
+            writeBackupState(tmpfile.length(), newCrc, newState);
+        } finally {
+            // Make sure to tidy up when we're done
+            tmpfile.delete();
+        }
+    }
+
+    /**
+     * Restore from backup -- reads in the flattened bookmark file as supplied from
+     * the backup service, parses that out, and rebuilds the bookmarks table in the
+     * browser database from it.
+     */
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode,
+            ParcelFileDescriptor newState) throws IOException {
+        long crc = -1;
+        File tmpfile = File.createTempFile("rst", null, getFilesDir());
+        try {
+            while (data.readNextHeader()) {
+                if (BOOKMARK_KEY.equals(data.getKey())) {
+                    // Read the flattened bookmark data into a temp file
+                    crc = copyBackupToFile(data, tmpfile, data.getDataSize());
+
+                    FileInputStream infstream = new FileInputStream(tmpfile);
+                    DataInputStream in = new DataInputStream(infstream);
+
+                    try {
+                        int count = in.readInt();
+                        ArrayList<Bookmark> bookmarks = new ArrayList<Bookmark>(count);
+
+                        // Read all the bookmarks, then process later -- if we can't read
+                        // all the data successfully, we don't touch the bookmarks table
+                        for (int i = 0; i < count; i++) {
+                            Bookmark mark = new Bookmark();
+                            mark.url = in.readUTF();
+                            mark.visits = in.readInt();
+                            mark.date = in.readLong();
+                            mark.created = in.readLong();
+                            mark.title = in.readUTF();
+                            bookmarks.add(mark);
+                        }
+
+                        // Okay, we have all the bookmarks -- now see if we need to add
+                        // them to the browser's database
+                        int N = bookmarks.size();
+                        int nUnique = 0;
+                        if (DEBUG) Log.v(TAG, "Restoring " + N + " bookmarks");
+                        String[] urlCol = new String[] { BookmarkColumns.URL };
+                        for (int i = 0; i < N; i++) {
+                            Bookmark mark = bookmarks.get(i);
+
+                            // Does this URL exist in the bookmark table?
+                            Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
+                                    urlCol,  BookmarkColumns.URL + " == '" + mark.url + "' AND " +
+                                    BookmarkColumns.BOOKMARK + " == 1 ", null, null);
+                            // if not, insert it
+                            if (cursor.getCount() <= 0) {
+                                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);
+                                nUnique++;
+                            } else {
+                                if (DEBUG) Log.v(TAG, "Skipping extant url: " + mark.url);
+                            }
+                            cursor.close();
+                        }
+                        Log.i(TAG, "Restored " + nUnique + " of " + N + " bookmarks");
+                    } catch (IOException ioe) {
+                        Log.w(TAG, "Bad backup data; not restoring");
+                        crc = -1;
+                    }
+                }
+
+                // Last, write the state we just restored from so we can discern
+                // changes whenever we get invoked for backup in the future
+                writeBackupState(tmpfile.length(), crc, newState);
+            }
+        } finally {
+            // Whatever happens, delete the temp file
+            tmpfile.delete();
+        }
+    }
+
+    class Bookmark {
+        public String url;
+        public int visits;
+        public long date;
+        public long created;
+        public String title;
+    }
+    /*
+     * Utility functions
+     */
+
+    // Flatten the bookmarks table into the given file, calculating its CRC in the process
+    private long buildBookmarkFile(FileOutputStream outfstream) throws IOException {
+        CRC32 crc = new CRC32();
+        ByteArrayOutputStream bufstream = new ByteArrayOutputStream(512);
+        DataOutputStream bout = new DataOutputStream(bufstream);
+
+        Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
+                new String[] { BookmarkColumns.URL, BookmarkColumns.VISITS,
+                BookmarkColumns.DATE, BookmarkColumns.CREATED,
+                BookmarkColumns.TITLE },
+                BookmarkColumns.BOOKMARK + " == 1 ", null, null);
+
+        // The first thing in the file is the row count...
+        int count = cursor.getCount();
+        if (DEBUG) Log.v(TAG, "Backing up " + count + " bookmarks");
+        bout.writeInt(count);
+        byte[] record = bufstream.toByteArray();
+        crc.update(record);
+        outfstream.write(record);
+
+        // ... followed by the data for each row
+        for (int i = 0; i < count; i++) {
+            cursor.moveToNext();
+
+            String url = cursor.getString(0);
+            int visits = cursor.getInt(1);
+            long date = cursor.getLong(2);
+            long created = cursor.getLong(3);
+            String title = cursor.getString(4);
+
+            // construct the flattened record in a byte array
+            bufstream.reset();
+            bout.writeUTF(url);
+            bout.writeInt(visits);
+            bout.writeLong(date);
+            bout.writeLong(created);
+            bout.writeUTF(title);
+
+            // Update the CRC and write the record to the temp file
+            record = bufstream.toByteArray();
+            crc.update(record);
+            outfstream.write(record);
+
+            if (DEBUG) Log.v(TAG, "   wrote url " + url);
+        }
+
+        cursor.close();
+        return crc.getValue();
+    }
+
+    // Write the file to backup as a single record under the given key
+    private void copyFileToBackup(String key, File file, BackupDataOutput data)
+            throws IOException {
+        final int CHUNK = 8192;
+        byte[] buf = new byte[CHUNK];
+
+        int toCopy = (int) file.length();
+        data.writeEntityHeader(key, toCopy);
+
+        FileInputStream in = new FileInputStream(file);
+        int nRead;
+        while (toCopy > 0) {
+            nRead = in.read(buf, 0, CHUNK);
+            data.writeEntityData(buf, nRead);
+            toCopy -= nRead;
+        }
+        in.close();
+    }
+
+    // Read the given file from backup to a file, calculating a CRC32 along the way
+    private long copyBackupToFile(BackupDataInput data, File file, int toRead)
+            throws IOException {
+        final int CHUNK = 8192;
+        byte[] buf = new byte[CHUNK];
+        CRC32 crc = new CRC32();
+        FileOutputStream out = new FileOutputStream(file);
+
+        while (toRead > 0) {
+            int numRead = data.readEntityData(buf, 0, CHUNK);
+            crc.update(buf, 0, numRead);
+            out.write(buf, 0, numRead);
+            toRead -= numRead;
+        }
+
+        out.close();
+        return crc.getValue();
+    }
+
+    // Write the given metrics to the new state file
+    private void writeBackupState(long fileSize, long crc, ParcelFileDescriptor stateFile)
+            throws IOException {
+        DataOutputStream out = new DataOutputStream(
+                new FileOutputStream(stateFile.getFileDescriptor()));
+        out.writeLong(fileSize);
+        out.writeLong(crc);
+        out.writeInt(BACKUP_AGENT_VERSION);
+    }
+}
diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java
index 27782e0..dd56d2f 100644
--- a/src/com/android/browser/BrowserBookmarksAdapter.java
+++ b/src/com/android/browser/BrowserBookmarksAdapter.java
@@ -30,38 +30,37 @@
 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.WebIconDatabase.IconListener;
+import android.webkit.WebView;
 import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
 
 import java.io.ByteArrayOutputStream;
 
 class BrowserBookmarksAdapter extends BaseAdapter {
 
-    private final String            LOGTAG = "Bookmarks";
-
     private String                  mCurrentPage;
+    private String                  mCurrentTitle;
+    private Bitmap                  mCurrentThumbnail;
     private Cursor                  mCursor;
     private int                     mCount;
-    private String                  mLastWhereClause;
-    private String[]                mLastSelectionArgs;
-    private String                  mLastOrderBy;
     private BrowserBookmarksPage    mBookmarksPage;
     private ContentResolver         mContentResolver;
-    private ChangeObserver          mChangeObserver;
-    private DataSetObserver         mDataSetObserver;
     private boolean                 mDataValid;
-
-    // When true, this adapter is used to pick a bookmark to create a shortcut
-    private boolean mCreateShortcut;
-    private int mExtraOffset;
+    private BookmarkViewMode        mViewMode;
+    private boolean                 mMostVisited;
+    private boolean                 mNeedsOffset;
+    private int                     mExtraOffset;
 
     // Implementation of WebIconDatabase.IconListener
     private class IconReceiver implements IconListener {
         public void onReceivedIcon(String url, Bitmap icon) {
-            updateBookmarkFavicon(mContentResolver, url, icon);
+            updateBookmarkFavicon(mContentResolver, null, url, icon);
         }
     }
 
@@ -70,36 +69,44 @@
 
     /**
      *  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) {
-        this(b, curPage, false);
-    }
-
-    /**
-     *  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,
-            boolean createShortcut) {
-        mDataValid = false;
-        mCreateShortcut = createShortcut;
-        mExtraOffset = createShortcut ? 0 : 1;
+            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;
+        mCurrentPage = b.getResources().getString(R.string.current_page)
+                + curPage;
+        mCurrentTitle = curTitle;
+        mCurrentThumbnail = curThumbnail;
         mContentResolver = b.getContentResolver();
-        mLastOrderBy = Browser.BookmarkColumns.CREATED + " DESC";
-        mChangeObserver = new ChangeObserver();
-        mDataSetObserver = new MyDataSetObserver();
+        mViewMode = BookmarkViewMode.LIST;
+
+        String whereClause;
         // FIXME: Should have a default sort order that the user selects.
-        search(null);
+        String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
+        if (mostVisited) {
+            whereClause = Browser.BookmarkColumns.VISITS + " != 0";
+        } else {
+            whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0";
+        }
+        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;
+
         // FIXME: This requires another query of the database after the
-        // initial search(null). Can we optimize this?
+        // managedQuery. Can we optimize this?
         Browser.requestAllIcons(mContentResolver,
                 Browser.BookmarkColumns.FAVICON + " is NULL AND " +
                 Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver);
@@ -163,6 +170,10 @@
                 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) {
@@ -181,18 +192,8 @@
         }
         mCursor.moveToPosition(position- mExtraOffset);
         String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
-        WebIconDatabase.getInstance().releaseIconForPageUrl(url);
-        Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, mCursor
-                .getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
-        int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
-        if (0 == numVisits) {
-            mContentResolver.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);
-            mContentResolver.update(uri, values, null, null);
-        }
+        String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
+        Bookmarks.removeFromBookmarks(null, mContentResolver, url, title);
         refreshList();
     }
     
@@ -253,84 +254,25 @@
      *  Refresh list to recognize a change in the database.
      */
     public void refreshList() {
-        // FIXME: consider using requery().
-        // Need to do more work to get it to function though.
-        searchInternal(mLastWhereClause, mLastSelectionArgs, mLastOrderBy);
+        mCursor.requery();
+        mCount = mCursor.getCount() + mExtraOffset;
+        notifyDataSetChanged();
     }
 
     /**
-     *  Search the database for bookmarks that match the input string.
-     *  @param like String to use to search the database.  Strings with spaces 
-     *              are treated as having multiple search terms using the
-     *              OR operator.  Search both the title and url.
-     */
-    public void search(String like) {
-        String whereClause = Browser.BookmarkColumns.BOOKMARK + " == 1";
-        String[] selectionArgs = null;
-        if (like != null) {
-            String[] likes = like.split(" ");
-            int count = 0;
-            boolean firstTerm = true;
-            StringBuilder andClause = new StringBuilder(256);
-            for (int j = 0; j < likes.length; j++) {
-                if (likes[j].length() > 0) {
-                    if (firstTerm) {
-                        firstTerm = false;
-                    } else {
-                        andClause.append(" OR ");
-                    }
-                    andClause.append(Browser.BookmarkColumns.TITLE
-                            + " LIKE ? OR " + Browser.BookmarkColumns.URL
-                            + " LIKE ? ");
-                    count += 2;
-                }
-            }
-            if (count > 0) {
-                selectionArgs = new String[count];
-                count = 0;
-                for (int j = 0; j < likes.length; j++) {
-                    if (likes[j].length() > 0) {
-                        like = "%" + likes[j] + "%";
-                        selectionArgs[count++] = like;
-                        selectionArgs[count++] = like;
-                    }
-                }
-                whereClause += " AND (" + andClause + ")";
-            }
-        }
-        searchInternal(whereClause, selectionArgs, mLastOrderBy);
-    }
-
-    /**
-     * Update the bookmark's favicon.
+     * 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 url The url of the bookmark to update.
+     * @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(ContentResolver cr,
-            String url, Bitmap favicon) {
-        if (url == null || favicon == null) {
+            String originalUrl, String url, Bitmap favicon) {
+        final Cursor c = queryBookmarksForUrl(cr, originalUrl, url, true);
+        if (c == null) {
             return;
         }
-        // Strip the query.
-        int query = url.indexOf('?');
-        String noQuery = url;
-        if (query != -1) {
-            noQuery = url.substring(0, query);
-        }
-        url = noQuery + '?';
-        // 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[] { noQuery, url };
-        final String where = "(" + Browser.BookmarkColumns.URL + " == ? OR "
-                + Browser.BookmarkColumns.URL + " GLOB ? || '*') AND "
-                + Browser.BookmarkColumns.BOOKMARK + " == 1";
-        final String[] projection = new String[] { Browser.BookmarkColumns._ID };
-        final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection, where,
-                selArgs, null);
         boolean succeed = c.moveToFirst();
         ContentValues values = null;
         while (succeed) {
@@ -347,44 +289,55 @@
         c.close();
     }
 
-    /**
-     *  This sorts alphabetically, with non-capitalized titles before 
-     *  capitalized.
-     */
-    public void sortAlphabetical() {
-        searchInternal(mLastWhereClause, mLastSelectionArgs,
-                Browser.BookmarkColumns.TITLE + " COLLATE UNICODE ASC");
-    }
-
-    /**
-     *  Internal function used in search, sort, and refreshList.
-     */
-    private void searchInternal(String whereClause, String[] selectionArgs,
-            String orderBy) {
-        if (mCursor != null) {
-            mCursor.unregisterContentObserver(mChangeObserver);
-            mCursor.unregisterDataSetObserver(mDataSetObserver);
-            mBookmarksPage.stopManagingCursor(mCursor);
-            mCursor.deactivate();
+    /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
+            String originalUrl, String url, boolean onlyBookmarks) {
+        if (cr == null || url == null) {
+            return null;
         }
 
-        mLastWhereClause = whereClause;
-        mLastSelectionArgs = selectionArgs;
-        mLastOrderBy = orderBy;
-        mCursor = mContentResolver.query(
-            Browser.BOOKMARKS_URI,
-            Browser.HISTORY_PROJECTION,
-            whereClause,
-            selectionArgs, 
-            orderBy);
-        mCursor.registerContentObserver(mChangeObserver);
-        mCursor.registerDataSetObserver(mDataSetObserver);
-        mBookmarksPage.startManagingCursor(mCursor);
+        // If originalUrl is null, just set it to url.
+        if (originalUrl == null) {
+            originalUrl = url;
+        }
 
-        mDataValid = true;
-        notifyDataSetChanged();
+        // 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 + '?';
 
-        mCount = mCursor.getCount() + mExtraOffset;
+        // 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 + " GLOB ? || '*' OR "
+                + BookmarkColumns.URL + " GLOB ? || '*'";
+        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;
     }
 
     /**
@@ -425,6 +378,26 @@
         return position;
     }
 
+    /* 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);
+        } 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.
@@ -440,7 +413,45 @@
             throw new AssertionError(
                     "BrowserBookmarksAdapter tried to get a view out of range");
         }
-        if (position == 0 && !mCreateShortcut) {
+        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);
+            }
+            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 = getBitmap(Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX, position);
+            if (thumbnail == null) {
+                thumb.setImageResource(R.drawable.browser_thumbnail);
+            } else {
+                thumb.setImageBitmap(thumbnail);
+            }
+
+            return convertView;
+
+        }
+        if (position == 0 && mNeedsOffset) {
             AddNewBookmark b;
             if (convertView instanceof AddNewBookmark) {
                 b = (AddNewBookmark) convertView;
@@ -450,10 +461,20 @@
             b.setUrl(mCurrentPage);
             return b;
         }
-        if (convertView == null || convertView instanceof AddNewBookmark) {
-            convertView = new BookmarkItem(mBookmarksPage);
+        if (mMostVisited) {
+            if (convertView == null || !(convertView instanceof HistoryItem)) {
+                convertView = new HistoryItem(mBookmarksPage);
+            }
+        } else {
+            if (convertView == null || !(convertView instanceof BookmarkItem)) {
+                convertView = new BookmarkItem(mBookmarksPage);
+            }
         }
-        bind((BookmarkItem)convertView, position);
+        bind((BookmarkItem) convertView, position);
+        if (mMostVisited) {
+            ((HistoryItem) convertView).setIsBookmark(
+                    getIsBookmark(position));
+        }
         return convertView;
     }
 
@@ -475,11 +496,19 @@
      * 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(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+        byte[] data = mCursor.getBlob(cursorIndex);
         if (data == null) {
             return null;
         }
@@ -487,6 +516,17 @@
     }
 
     /**
+     * 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) {
@@ -514,7 +554,8 @@
         if (data != null) {
             b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
         } else {
-            b.setFavicon(null);
+            b.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet()
+                    .getFavicon(url));
         }
     }
 
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index dd34c14..6ab011b 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -20,11 +20,16 @@
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
 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.RectF;
 import android.net.Uri;
 import android.os.Bundle;
@@ -36,35 +41,47 @@
 import android.util.Log;
 import android.view.ContextMenu;
 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.ViewGroup.LayoutParams;
+import android.view.ViewStub;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.widget.AdapterView;
+import android.widget.GridView;
 import android.widget.ListView;
+import android.widget.Toast;
 
+/*package*/ enum BookmarkViewMode { NONE, GRID, LIST }
 /**
  *  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 View                    mVerticalList;
     private BrowserBookmarksAdapter mBookmarksAdapter;
     private static final int        BOOKMARKS_SAVE = 1;
-    private boolean                 mMaxTabsOpen;
+    private boolean                 mDisableNewWindow;
     private BookmarkItem            mContextHeader;
     private AddNewBookmark          mAddHeader;
     private boolean                 mCanceled = false;
     private boolean                 mCreateShortcut;
+    private boolean                 mMostVisited;
+    private View                    mEmptyView;
     // 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";
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
@@ -91,23 +108,58 @@
             editBookmark(i.position);
             break;
         case R.id.shortcut_context_menu_id:
-            final Intent send = createShortcutIntent(getUrl(i.position),
-                    getBookmarkTitle(i.position), getFavicon(i.position));
+            final Intent send = createShortcutIntent(i.position);
             send.setAction(INSTALL_SHORTCUT);
             sendBroadcast(send);
             break;
         case R.id.delete_context_menu_id:
-            displayRemoveBookmarkDialog(i.position);
+            if (mMostVisited) {
+                Browser.deleteFromHistory(getContentResolver(),
+                        getUrl(i.position));
+                refreshList();
+            } else {
+                displayRemoveBookmarkDialog(i.position);
+            }
             break;
         case R.id.new_window_context_menu_id:
             openInNewWindow(i.position);
             break;
-        case R.id.send_context_menu_id:
-            Browser.sendString(BrowserBookmarksPage.this, getUrl(i.position));
+        case R.id.share_link_context_menu_id:
+            Browser.sendString(BrowserBookmarksPage.this, getUrl(i.position),
+                    getText(R.string.choosertitle_sharevia).toString());
             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();
+            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();
+            }
+            // 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);
+            }
+            break;
         default:
             return super.onContextItemSelected(item);
         }
@@ -121,9 +173,13 @@
                     (AdapterView.AdapterContextMenuInfo) menuInfo;
 
             MenuInflater inflater = getMenuInflater();
-            inflater.inflate(R.menu.bookmarkscontext, menu);
+            if (mMostVisited) {
+                inflater.inflate(R.menu.historycontext, menu);
+            } else {
+                inflater.inflate(R.menu.bookmarkscontext, menu);
+            }
 
-            if (0 == i.position) {
+            if (0 == i.position && !mMostVisited) {
                 menu.setGroupVisible(R.id.CONTEXT_MENU, false);
                 if (mAddHeader == null) {
                     mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this);
@@ -131,25 +187,40 @@
                     ((ViewGroup) mAddHeader.getParent()).
                             removeView(mAddHeader);
                 }
-                ((AddNewBookmark) i.targetView).copyTo(mAddHeader);
+                mAddHeader.setUrl(getIntent().getStringExtra("url"));
                 menu.setHeaderView(mAddHeader);
                 return;
             }
-            menu.setGroupVisible(R.id.ADD_MENU, false);
-            BookmarkItem b = (BookmarkItem) i.targetView;
+            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);
+            }
+            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);
             }
-            b.copyTo(mContextHeader);
-            menu.setHeaderView(mContextHeader);
-
-            if (mMaxTabsOpen) {
-                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);
             }
+            menu.setHeaderView(mContextHeader);
         }
 
     /**
@@ -159,28 +230,124 @@
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        setContentView(R.layout.browser_bookmarks_page);
-        setTitle(R.string.browser_bookmarks_page_bookmarks_text);
-
         if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
             mCreateShortcut = true;
         }
+        mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window",
+                false);
+        mMostVisited = getIntent().getBooleanExtra("mostVisited", false);
 
-        mBookmarksAdapter = new BrowserBookmarksAdapter(this, 
-                getIntent().getStringExtra("url"), mCreateShortcut);
-        mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
+        if (mCreateShortcut) {
+            setTitle(R.string.browser_bookmarks_page_bookmarks_text);
+        }
+        mBookmarksAdapter = new BrowserBookmarksAdapter(this,
+                        getIntent().getStringExtra("url"),
+                        getIntent().getStringExtra("title"),
+                        (Bitmap) getIntent().getParcelableExtra("thumbnail"),
+                        mCreateShortcut,
+                        mMostVisited);
 
-        ListView listView = (ListView) findViewById(R.id.list);
-        listView.setAdapter(mBookmarksAdapter);
-        listView.setDrawSelectorOnTop(false);
-        listView.setVerticalScrollBarEnabled(true);
-        listView.setOnItemClickListener(mListener);
+        setContentView(R.layout.empty_history);
+        mEmptyView = findViewById(R.id.empty_view);
+        mEmptyView.setVisibility(View.GONE);
 
-        if (!mCreateShortcut) {
-            listView.setOnCreateContextMenuListener(this);
+        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);
+    }
+
+    /**
+     *  Set the ContentView to be either the grid of thumbnails or the vertical
+     *  list.
+     */
+    private void switchViewMode(BookmarkViewMode gridMode) {
+        if (mViewMode == gridMode) {
+            return;
+        }
+
+        mViewMode = gridMode;
+
+        // 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());
+        } else {
+            ed.putInt(PREF_BOOKMARK_VIEW_MODE, mViewMode.ordinal());
+        }
+        ed.commit();
+
+        mBookmarksAdapter.switchViewMode(gridMode);
+        if (mViewMode == BookmarkViewMode.GRID) {
+            if (mGridPage == null) {
+                mGridPage = new GridView(this);
+                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);
+                }
+            }
+        } else {
+            if (null == mVerticalList) {
+                ListView listView = new ListView(this);
+                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);
+                }
+            }
         }
     }
 
+    private static final ViewGroup.LayoutParams FULL_SCREEN_PARAMS
+            = new ViewGroup.LayoutParams(
+            ViewGroup.LayoutParams.FILL_PARENT,
+            ViewGroup.LayoutParams.FILL_PARENT);
+
     private static final int SAVE_CURRENT_PAGE = 1000;
     private final Handler mHandler = new Handler() {
         @Override
@@ -196,27 +363,29 @@
             // 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("browser", "item clicked when dismising");
+                android.util.Log.e(LOGTAG, "item clicked when dismissing");
                 return;
             }
             if (!mCreateShortcut) {
-                if (0 == position) {
+                if (0 == position && !mMostVisited) {
                     // XXX: Work-around for a framework issue.
                     mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
                 } else {
                     loadUrl(position);
                 }
             } else {
-                final Intent intent = createShortcutIntent(getUrl(position),
-                        getBookmarkTitle(position), getFavicon(position));
+                final Intent intent = createShortcutIntent(position);
                 setResultToParent(RESULT_OK, intent);
                 finish();
             }
         }
     };
 
-    private Intent createShortcutIntent(String url, String title,
-            Bitmap favicon) {
+    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));
@@ -226,41 +395,67 @@
                 Long.toString(uniqueId));
         i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
         i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
-        if (favicon == null) {
-            i.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
-                    Intent.ShortcutIconResource.fromContext(
-                            BrowserBookmarksPage.this,
-                            R.drawable.ic_launcher_shortcut_browser_bookmark));
-        } else {
-            Bitmap icon = BitmapFactory.decodeResource(getResources(),
-                    R.drawable.ic_launcher_shortcut_browser_bookmark);
-
-            // Make a copy of the regular icon so we can modify the pixels.
-            Bitmap copy = icon.copy(Bitmap.Config.ARGB_8888, true);
+        // Use the apple-touch-icon if available
+        if (touchIcon != null) {
+            // Make a copy so we can modify the pixels.
+            Bitmap copy = touchIcon.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);
+            // 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, touchIcon.getWidth(),
+                    touchIcon.getHeight());
+            rect.inset(1, 1);
+            path.addRoundRect(rect, 8f, 8f, Path.Direction.CW);
 
-            // Create a rectangle that is slightly wider than the favicon
-            final float iconSize = 16; // 16x16 favicon
-            final float padding = 2;   // white padding around icon
-            final float rectSize = iconSize + 2 * padding;
-            final float y = icon.getHeight() - rectSize;
-            RectF r = new RectF(0, y, rectSize, y + rectSize);
+            // Construct a paint that clears the outside of the rectangle and
+            // draw.
+            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+            canvas.drawPath(path, paint);
 
-            // 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);
+        } 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));
+            } else {
+                Bitmap icon = BitmapFactory.decodeResource(getResources(),
+                        R.drawable.ic_launcher_shortcut_browser_bookmark);
+
+                // 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);
+
+                // Create a rectangle that is slightly wider than the favicon
+                final float iconSize = 16; // 16x16 favicon
+                final float padding = 2;   // white padding around icon
+                final float rectSize = iconSize + 2 * padding;
+                final float y = icon.getHeight() - rectSize;
+                RectF r = new RectF(0, y, 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);
+            }
         }
         // Do not allow duplicate items
         i.putExtra("duplicate", false);
@@ -283,7 +478,7 @@
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         boolean result = super.onCreateOptionsMenu(menu);
-        if (!mCreateShortcut) {
+        if (!mCreateShortcut && !mMostVisited) {
             MenuInflater inflater = getMenuInflater();
             inflater.inflate(R.menu.bookmarks, menu);
             return true;
@@ -292,14 +487,45 @@
     }
 
     @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        boolean result = super.onPrepareOptionsMenu(menu);
+        if (mCreateShortcut || mMostVisited
+                || 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;
+        } else {
+            titleResId = R.string.switch_to_thumbnails;
+            iconResId = R.drawable.ic_menu_thumbnail;
+        }
+        switchItem.setTitle(titleResId);
+        switchItem.setIcon(iconResId);
+        return true;
+    }
+
+    @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
-            case R.id.new_context_menu_id:
-                saveCurrentPage();
-                break;
-                
-            default:
-                return super.onOptionsItemSelected(item);
+        case R.id.new_context_menu_id:
+            saveCurrentPage();
+            break;
+
+        case R.id.switch_mode_menu_id:
+            if (mViewMode == BookmarkViewMode.GRID) {
+                switchViewMode(BookmarkViewMode.LIST);
+            } else {
+                switchViewMode(BookmarkViewMode.GRID);
+            }
+            break;
+
+        default:
+            return super.onOptionsItemSelected(item);
         }
         return true;
     }
@@ -370,7 +596,7 @@
     /**
      *  Refresh the shown list after the database has changed.
      */
-    public void refreshList() {
+    private void refreshList() {
         mBookmarksAdapter.refreshList();
     }
     
@@ -395,6 +621,10 @@
         return mBookmarksAdapter.getFavicon(position);
     }
 
+    private Bitmap getTouchIcon(int position) {
+        return mBookmarksAdapter.getTouchIcon(position);
+    }
+
     private void copy(CharSequence text) {
         try {
             IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
@@ -416,13 +646,12 @@
     public void deleteBookmark(int position) {
         mBookmarksAdapter.deleteRow(position);
     }
-    
-    public boolean dispatchKeyEvent(KeyEvent event) {    
-        if (event.getKeyCode() ==  KeyEvent.KEYCODE_BACK && event.isDown()) {
-            setResultToParent(RESULT_CANCELED, null);
-            mCanceled = true;
-        }
-        return super.dispatchKeyEvent(event);
+
+    @Override
+    public void onBackPressed() {
+        setResultToParent(RESULT_CANCELED, null);
+        mCanceled = true;
+        super.onBackPressed();
     }
 
     // This Activity is generally a sub-Activity of CombinedHistoryActivity. In
diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java
index 38b83fe..16cb982 100644
--- a/src/com/android/browser/BrowserDownloadAdapter.java
+++ b/src/com/android/browser/BrowserDownloadAdapter.java
@@ -60,14 +60,14 @@
     public BrowserDownloadAdapter(Context context, int layout, Cursor c) {
         super(context, layout, c);
         mFilenameColumnId = c.getColumnIndexOrThrow(Downloads._DATA);
-        mTitleColumnId = c.getColumnIndexOrThrow(Downloads.TITLE);
-        mDescColumnId = c.getColumnIndexOrThrow(Downloads.DESCRIPTION);
-        mStatusColumnId = c.getColumnIndexOrThrow(Downloads.STATUS);
-        mTotalBytesColumnId = c.getColumnIndexOrThrow(Downloads.TOTAL_BYTES);
+        mTitleColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_TITLE);
+        mDescColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_DESCRIPTION);
+        mStatusColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
+        mTotalBytesColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES);
         mCurrentBytesColumnId = 
-            c.getColumnIndexOrThrow(Downloads.CURRENT_BYTES);
-        mMimetypeColumnId = c.getColumnIndexOrThrow(Downloads.MIMETYPE);
-        mDateColumnId = c.getColumnIndexOrThrow(Downloads.LAST_MODIFICATION);
+            c.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES);
+        mMimetypeColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE);
+        mDateColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION);
     }
 
     @Override
@@ -106,7 +106,7 @@
                 // We have a filename, so we can build a title from that
                 title = new File(fullFilename).getName();
                 ContentValues values = new ContentValues();
-                values.put(Downloads.TITLE, title);
+                values.put(Downloads.COLUMN_TITLE, title);
                 // assume "_id" is the first column for the cursor 
                 context.getContentResolver().update(
                         ContentUris.withAppendedId(Downloads.CONTENT_URI,
diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java
index 4397337..22e0e65 100644
--- a/src/com/android/browser/BrowserDownloadPage.java
+++ b/src/com/android/browser/BrowserDownloadPage.java
@@ -66,34 +66,30 @@
         setTitle(getText(R.string.download_title));
 
         mListView = (ListView) findViewById(R.id.list);
-        LayoutInflater factory = LayoutInflater.from(this);
-        View v = factory.inflate(R.layout.no_downloads, null);
-        addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
-                LayoutParams.FILL_PARENT));
-        mListView.setEmptyView(v);
+        mListView.setEmptyView(findViewById(R.id.empty));
         
         mDownloadCursor = managedQuery(Downloads.CONTENT_URI, 
-                new String [] {"_id", Downloads.TITLE, Downloads.STATUS,
-                Downloads.TOTAL_BYTES, Downloads.CURRENT_BYTES, 
-                Downloads._DATA, Downloads.DESCRIPTION, 
-                Downloads.MIMETYPE, Downloads.LAST_MODIFICATION,
-                Downloads.VISIBILITY}, 
+                new String [] {"_id", Downloads.COLUMN_TITLE, Downloads.COLUMN_STATUS,
+                Downloads.COLUMN_TOTAL_BYTES, Downloads.COLUMN_CURRENT_BYTES, 
+                Downloads._DATA, Downloads.COLUMN_DESCRIPTION, 
+                Downloads.COLUMN_MIME_TYPE, Downloads.COLUMN_LAST_MODIFICATION,
+                Downloads.COLUMN_VISIBILITY}, 
                 null, null);
         
         // only attach everything to the listbox if we can access
         // the download database. Otherwise, just show it empty
         if (mDownloadCursor != null) {
             mStatusColumnId = 
-                    mDownloadCursor.getColumnIndexOrThrow(Downloads.STATUS);
+                    mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
             mIdColumnId =
                     mDownloadCursor.getColumnIndexOrThrow(Downloads._ID);
             mTitleColumnId = 
-                    mDownloadCursor.getColumnIndexOrThrow(Downloads.TITLE);
+                    mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_TITLE);
             
             // Create a list "controller" for the data
             mDownloadAdapter = new BrowserDownloadAdapter(this, 
                     R.layout.browser_download_item, mDownloadCursor);
-            
+
             mListView.setAdapter(mDownloadAdapter);
             mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
             mListView.setOnCreateContextMenuListener(this);
@@ -403,7 +399,7 @@
                 mDownloadCursor.getColumnIndexOrThrow(Downloads._DATA);
         String filename = mDownloadCursor.getString(filenameColumnId);
         int mimetypeColumnId =
-                mDownloadCursor.getColumnIndexOrThrow(Downloads.MIMETYPE);
+                mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE);
         String mimetype = mDownloadCursor.getString(mimetypeColumnId);
         Uri path = Uri.parse(filename);
         // If there is no scheme, then it must be a file
@@ -453,13 +449,13 @@
     private void hideCompletedDownload() {
         int status = mDownloadCursor.getInt(mStatusColumnId);
 
-        int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.VISIBILITY);
+        int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY);
         int visibility = mDownloadCursor.getInt(visibilityColumn);
 
         if (Downloads.isStatusCompleted(status) &&
                 visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
             ContentValues values = new ContentValues();
-            values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE);
+            values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE);
             getContentResolver().update(
                     ContentUris.withAppendedId(Downloads.CONTENT_URI,
                     mDownloadCursor.getLong(mIdColumnId)), values, null, null);
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java
index 4a0f8c5..bdd109b 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -25,6 +25,7 @@
 import android.database.Cursor;
 import android.database.DataSetObserver;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.ServiceManager;
@@ -41,6 +42,7 @@
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.ViewStub;
 import android.webkit.DateSorter;
 import android.webkit.WebIconDatabase.IconListener;
 import android.widget.AdapterView;
@@ -48,6 +50,7 @@
 import android.widget.ExpandableListView;
 import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import java.util.List;
 import java.util.Vector;
@@ -59,7 +62,8 @@
 public class BrowserHistoryPage extends ExpandableListActivity {
     private HistoryAdapter          mAdapter;
     private DateSorter              mDateSorter;
-    private boolean                 mMaxTabsOpen;
+    private boolean                 mDisableNewWindow;
+    private HistoryItem             mContextHeader;
 
     private final static String LOGTAG = "browser";
 
@@ -110,8 +114,7 @@
         setListAdapter(mAdapter);
         final ExpandableListView list = getExpandableListView();
         list.setOnCreateContextMenuListener(this);
-        LayoutInflater factory = LayoutInflater.from(this);
-        View v = factory.inflate(R.layout.empty_history, null);
+        View v = new ViewStub(this, R.layout.empty_history);
         addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
                 LayoutParams.FILL_PARENT));
         list.setEmptyView(v);
@@ -127,8 +130,9 @@
                 }
             });
         }
-        mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
-        CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
+        mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window",
+                false);
+        CombinedBookmarkHistoryActivity.getIconListenerSet()
                 .addListener(mIconReceiver);
         
         // initialize the result to canceled, so that if the user just presses
@@ -139,7 +143,7 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
+        CombinedBookmarkHistoryActivity.getIconListenerSet()
                 .removeListener(mIconReceiver);
     }
 
@@ -172,7 +176,7 @@
         }  
         return super.onOptionsItemSelected(item);
     }
-    
+
     @Override
     public void onCreateContextMenu(ContextMenu menu, View v,
             ContextMenuInfo menuInfo) {
@@ -187,12 +191,26 @@
         MenuInflater inflater = getMenuInflater();
         inflater.inflate(R.menu.historycontext, menu);
 
-        // Setup the header
-        menu.setHeaderTitle(((HistoryItem)i.targetView).getUrl());
+        HistoryItem historyItem = (HistoryItem) i.targetView;
 
-        // Only show open in new tab if we have not maxed out available tabs
-        menu.findItem(R.id.new_window_context_menu_id).setVisible(!mMaxTabsOpen);
-        
+        // Setup the header
+        if (mContextHeader == null) {
+            mContextHeader = new HistoryItem(this);
+        } else if (mContextHeader.getParent() != null) {
+            ((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader);
+        }
+        historyItem.copyTo(mContextHeader);
+        menu.setHeaderView(mContextHeader);
+
+        // Only show open in new tab if it was not explicitly disabled
+        if (mDisableNewWindow) {
+            menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
+        }
+        // For a bookmark, provide the option to remove it from bookmarks
+        if (historyItem.isBookmark()) {
+            MenuItem item = menu.findItem(R.id.save_to_bookmarks_menu_id);
+            item.setTitle(R.string.remove_from_bookmarks);
+        }
         // decide whether to show the share link option
         PackageManager pm = getPackageManager();
         Intent send = new Intent(Intent.ACTION_SEND);
@@ -207,8 +225,9 @@
     public boolean onContextItemSelected(MenuItem item) {
         ExpandableListContextMenuInfo i = 
             (ExpandableListContextMenuInfo) item.getMenuInfo();
-        String url = ((HistoryItem)i.targetView).getUrl();
-        String title = ((HistoryItem)i.targetView).getName();
+        HistoryItem historyItem = (HistoryItem) i.targetView;
+        String url = historyItem.getUrl();
+        String title = historyItem.getName();
         switch (item.getItemId()) {
             case R.id.open_context_menu_id:
                 loadUrl(url, false);
@@ -217,18 +236,29 @@
                 loadUrl(url, true);
                 return true;
             case R.id.save_to_bookmarks_menu_id:
-                Browser.saveBookmark(this, title, url);
+                if (historyItem.isBookmark()) {
+                    Bookmarks.removeFromBookmarks(this, getContentResolver(),
+                            url, title);
+                } else {
+                    Browser.saveBookmark(this, title, url);
+                }
                 return true;
             case R.id.share_link_context_menu_id:
-                Browser.sendString(this, url);
+                Browser.sendString(this, url,
+                        getText(R.string.choosertitle_sharevia).toString());
                 return true;
-            case R.id.copy_context_menu_id:
+            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();
                 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();
+                return true;
             default:
                 break;
         }
@@ -274,18 +304,25 @@
         
         // Array for each of our bins.  Each entry represents how many items are
         // in that bin.
-        int mItemMap[];
+        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.
-        int mNumberOfBins;
-        Vector<DataSetObserver> mObservers;
-        Cursor mCursor;
+        private int mNumberOfBins;
+        private Vector<DataSetObserver> mObservers;
+        private Cursor mCursor;
         
         HistoryAdapter() {
             mObservers = new Vector<DataSetObserver>();
             
-            String whereClause = Browser.BookmarkColumns.VISITS + " > 0 ";
-            String orderBy = Browser.BookmarkColumns.DATE + " DESC";
+            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";
            
             mCursor = managedQuery(
                     Browser.BOOKMARKS_URI,
@@ -297,6 +334,9 @@
         }
         
         void refreshData() {
+            if (mCursor.isClosed()) {
+                return;
+            }
             mCursor.requery();
             buildMap();
             for (DataSetObserver o : mObservers) {
@@ -386,8 +426,14 @@
             item.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
             String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
             item.setUrl(url);
-            item.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet(
-                    getContentResolver()).getFavicon(url));
+            byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+            if (data != null) {
+                item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
+                        data.length));
+            } else {
+                item.setFavicon(CombinedBookmarkHistoryActivity
+                        .getIconListenerSet().getFavicon(url));
+            }
             item.setIsBookmark(1 ==
                     mCursor.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 d4708c3..be96db3 100644
--- a/src/com/android/browser/BrowserHomepagePreference.java
+++ b/src/com/android/browser/BrowserHomepagePreference.java
@@ -18,47 +18,46 @@
 
 import android.app.AlertDialog;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.preference.EditTextPreference;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.text.util.Regex;
 import android.util.AttributeSet;
 
-public class BrowserHomepagePreference extends EditTextPreference implements
-        TextWatcher {
+public class BrowserHomepagePreference extends EditTextPreference {
 
     public BrowserHomepagePreference(Context context, AttributeSet attrs,
             int defStyle) {
         super(context, attrs, defStyle);
-        getEditText().addTextChangedListener(this);
     }
 
     public BrowserHomepagePreference(Context context, AttributeSet attrs) {
         super(context, attrs);
-        getEditText().addTextChangedListener(this);
     }
 
     public BrowserHomepagePreference(Context context) {
         super(context);
-        getEditText().addTextChangedListener(this);
     }
 
-    public void afterTextChanged(Editable s) {
-        AlertDialog dialog = (AlertDialog) getDialog();
-        // This callback is called before the dialog has been fully constructed
-        if (dialog != null) {
-            String url = s.toString();
-            dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(
-                    url.length() == 0 || url.equals("about:blank") ||
-                    Regex.WEB_URL_PATTERN.matcher(url).matches());
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            String url = getEditText().getText().toString();
+            if (url.length() > 0
+                    && !BrowserActivity.ACCEPTED_URI_SCHEMA.matcher(url)
+                            .matches()) {
+                int colon = url.indexOf(':');
+                int space = url.indexOf(' ');
+                if (colon == -1 && space == -1) {
+                    // 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
+                    // 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();
+                    positiveResult = false;
+                }
+            }
         }
-    }
-
-    public void beforeTextChanged(CharSequence s, int start, int count,
-            int after) {
-    }
-
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        super.onDialogClosed(positiveResult);
     }
 }
diff --git a/src/com/android/browser/BrowserPluginList.java b/src/com/android/browser/BrowserPluginList.java
deleted file mode 100644
index 6689b0e..0000000
--- a/src/com/android/browser/BrowserPluginList.java
+++ /dev/null
@@ -1,66 +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.app.ListActivity;
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup;
-import android.webkit.Plugin;
-import android.webkit.PluginList;
-import android.webkit.WebView;
-import android.widget.ArrayAdapter;
-import android.widget.LinearLayout;
-import android.widget.LinearLayout.LayoutParams;
-import android.widget.ListView;
-import android.widget.TextView;
-import java.util.ArrayList;
-import java.util.List;
-
-// Manages the list of installed (and loaded) plugins.
-public class BrowserPluginList extends ListActivity {
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        // The list of plugins can change under us, as the plugins are
-        // loaded and unloaded in a different thread. We make a copy
-        // of the list here.
-        List loadedPlugins = WebView.getPluginList().getList();
-        ArrayList localLoadedPluginList = new ArrayList();
-        synchronized (loadedPlugins) {
-            localLoadedPluginList.addAll(loadedPlugins);
-        }
-        setListAdapter(new ArrayAdapter(this,
-                                        android.R.layout.simple_list_item_1,
-                                        localLoadedPluginList));
-        setTitle(R.string.pref_plugin_installed);
-        // Add a text view to this ListActivity. This text view
-        // will be displayed when the list of plugins is empty.
-        TextView textView = new TextView(this);
-        textView.setId(android.R.id.empty);
-        textView.setText(R.string.pref_plugin_installed_empty_list);
-        addContentView(textView, new LinearLayout.LayoutParams(
-                               ViewGroup.LayoutParams.FILL_PARENT,
-                               ViewGroup.LayoutParams.WRAP_CONTENT));
-
-    }
-
-    @Override
-    public void onListItemClick(ListView l, View v, int position, long id) {
-        WebView.getPluginList().pluginClicked(this, position);
-    }
-}
diff --git a/src/com/android/browser/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java
index 3b747d1..fb0b2ba 100644
--- a/src/com/android/browser/BrowserPreferencesPage.java
+++ b/src/com/android/browser/BrowserPreferencesPage.java
@@ -17,18 +17,28 @@
 package com.android.browser;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
 
+import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.preference.EditTextPreference;
+import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
+import android.webkit.WebStorage;
 import android.webkit.WebView;
-import android.webkit.Plugin;
 
 public class BrowserPreferencesPage extends PreferenceActivity
-        implements Preference.OnPreferenceChangeListener, 
-        Preference.OnPreferenceClickListener {
+        implements Preference.OnPreferenceChangeListener {
+
+    private String LOGTAG = "BrowserPreferencesPage";
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -59,13 +69,42 @@
 
         e = findPreference(BrowserSettings.PREF_DEFAULT_TEXT_ENCODING);
         e.setOnPreferenceChangeListener(this);
-        
+
         if (BrowserSettings.getInstance().showDebugSettings()) {
             addPreferencesFromResource(R.xml.debug_preferences);
         }
-        
-        e = findPreference(BrowserSettings.PREF_GEARS_SETTINGS);
-        e.setOnPreferenceClickListener(this);
+
+        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>() {
+            public void onReceiveValue(Set geolocationOrigins) {
+                if ((geolocationOrigins != null) && !geolocationOrigins.isEmpty()) {
+                    websiteSettings.setEnabled(true);
+                }
+            }
+        });
     }
 
     @Override
@@ -121,20 +160,6 @@
         
         return false;
     }
-    
-    public boolean onPreferenceClick(Preference pref) {
-        if (pref.getKey().equals(BrowserSettings.PREF_GEARS_SETTINGS)) {
-            List<Plugin> loadedPlugins = WebView.getPluginList().getList();
-            for(Plugin p : loadedPlugins) {
-                if (p.getName().equals("gears")) {
-                    p.dispatchClickEvent(this);
-                    return true;
-                }
-            }
-            
-        }
-        return true;
-    }
 
     private CharSequence getVisualTextSizeName(String enumName) {
         CharSequence[] visualNames = getResources().getTextArray(
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index a3ccf04..8e0929b 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -19,8 +19,10 @@
 import com.google.android.providers.GoogleSettings.Partner;
 
 import android.app.SearchManager;
+import android.backup.BackupManager;
 import android.content.ComponentName;
 import android.content.ContentProvider;
+import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -36,16 +38,20 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Handler;
 import android.preference.PreferenceManager;
 import android.provider.Browser;
 import android.provider.Settings;
+import android.provider.Browser.BookmarkColumns;
 import android.server.search.SearchableInfo;
 import android.text.TextUtils;
 import android.text.util.Regex;
 import android.util.Log;
 import android.util.TypedValue;
 
+import java.io.File;
+import java.io.FilenameFilter;
 import java.util.Date;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -54,6 +60,7 @@
 public class BrowserProvider extends ContentProvider {
 
     private SQLiteOpenHelper mOpenHelper;
+    private BackupManager mBackupManager;
     private static final String sDatabaseName = "browser.db";
     private static final String TAG = "BrowserProvider";
     private static final String ORDER_BY = "visits DESC, date DESC";
@@ -81,6 +88,7 @@
     private static final int SUGGEST_COLUMN_ICON_2_ID = 6;
     private static final int SUGGEST_COLUMN_QUERY_ID = 7;
     private static final int SUGGEST_COLUMN_FORMAT = 8;
+    private static final int SUGGEST_COLUMN_INTENT_EXTRA_DATA = 9;
 
     // shared suggestion columns
     private static final String[] COLUMNS = new String[] {
@@ -92,10 +100,13 @@
             SearchManager.SUGGEST_COLUMN_ICON_1,
             SearchManager.SUGGEST_COLUMN_ICON_2,
             SearchManager.SUGGEST_COLUMN_QUERY,
-            SearchManager.SUGGEST_COLUMN_FORMAT};
+            SearchManager.SUGGEST_COLUMN_FORMAT,
+            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;
@@ -143,7 +154,10 @@
     // 15 -> 17 Set it up for the SearchManager
     // 17 -> 18 Added favicon in bookmarks table for Home shortcuts
     // 18 -> 19 Remove labels table
-    private static final int DATABASE_VERSION = 19;
+    // 19 -> 20 Added thumbnail
+    // 20 -> 21 Added touch_icon
+    // 21 -> 22 Remove "clientid"
+    private static final int DATABASE_VERSION = 22;
 
     // Regular expression which matches http://, followed by some stuff, followed by
     // optionally a trailing slash, all matched as separate groups.
@@ -215,7 +229,9 @@
                     "created LONG," +
                     "description TEXT," +
                     "bookmark INTEGER," +
-                    "favicon BLOB DEFAULT NULL" +
+                    "favicon BLOB DEFAULT NULL," +
+                    "thumbnail BLOB DEFAULT NULL," +
+                    "touch_icon BLOB DEFAULT NULL" +
                     ");");
 
             final CharSequence[] bookmarks = mContext.getResources()
@@ -242,21 +258,81 @@
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
-                    + newVersion + ", which will destroy all old data");
+                    + newVersion);
             if (oldVersion == 18) {
                 db.execSQL("DROP TABLE IF EXISTS labels");
+            }
+            if (oldVersion <= 19) {
+                db.execSQL("ALTER TABLE bookmarks ADD COLUMN thumbnail BLOB DEFAULT NULL;");
+            }
+            if (oldVersion < 21) {
+                db.execSQL("ALTER TABLE bookmarks ADD COLUMN touch_icon BLOB DEFAULT NULL;");
+            }
+            if (oldVersion < 22) {
+                db.execSQL("DELETE FROM bookmarks WHERE (bookmark = 0 AND url LIKE \"%.google.%client=ms-%\")");
+                removeGears();
             } else {
                 db.execSQL("DROP TABLE IF EXISTS bookmarks");
                 db.execSQL("DROP TABLE IF EXISTS searches");
                 onCreate(db);
             }
         }
+
+        private void removeGears() {
+            AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+                public Void doInBackground(Void... unused) {
+                    String browserDataDirString = mContext.getApplicationInfo().dataDir;
+                    final String appPluginsDirString = "app_plugins";
+                    final String gearsPrefix = "gears";
+                    File appPluginsDir = new File(browserDataDirString + File.separator
+                            + appPluginsDirString);
+                    if (!appPluginsDir.exists()) {
+                        return null;
+                    }
+                    // Delete the Gears plugin files
+                    File[] gearsFiles = appPluginsDir.listFiles(new FilenameFilter() {
+                        public boolean accept(File dir, String filename) {
+                            return filename.startsWith(gearsPrefix);
+                        }
+                    });
+                    for (int i = 0; i < gearsFiles.length; ++i) {
+                        if (gearsFiles[i].isDirectory()) {
+                            deleteDirectory(gearsFiles[i]);
+                        } else {
+                            gearsFiles[i].delete();
+                        }
+                    }
+                    // Delete the Gears data files
+                    File gearsDataDir = new File(browserDataDirString + File.separator
+                            + gearsPrefix);
+                    if (!gearsDataDir.exists()) {
+                        return null;
+                    }
+                    deleteDirectory(gearsDataDir);
+                    return null;
+                }
+
+                private void deleteDirectory(File currentDir) {
+                    File[] files = currentDir.listFiles();
+                    for (int i = 0; i < files.length; ++i) {
+                        if (files[i].isDirectory()) {
+                            deleteDirectory(files[i]);
+                        }
+                        files[i].delete();
+                    }
+                    currentDir.delete();
+                }
+            };
+
+            task.execute();
+        }
     }
 
     @Override
     public boolean onCreate() {
         final Context context = getContext();
         mOpenHelper = new DatabaseHelper(context);
+        mBackupManager = new BackupManager(context);
         // we added "picasa web album" into default bookmarks for version 19.
         // To avoid erasing the bookmark table, we added it explicitly for
         // version 18 and 19 as in the other cases, we will erase the table.
@@ -370,6 +446,7 @@
         private int     mSuggestText1Id;
         private int     mSuggestText2Id;
         private int     mSuggestQueryId;
+        private int     mSuggestIntentExtraDataId;
 
         public MySuggestionCursor(Cursor hc, Cursor sc, String string) {
             mHistoryCursor = hc;
@@ -389,6 +466,7 @@
                 mSuggestText1Id = -1;
                 mSuggestText2Id = -1;
                 mSuggestQueryId = -1;
+                mSuggestIntentExtraDataId = -1;
             } else {
                 mSuggestText1Id = mSuggestCursor.getColumnIndex(
                                 SearchManager.SUGGEST_COLUMN_TEXT_1);
@@ -396,6 +474,8 @@
                                 SearchManager.SUGGEST_COLUMN_TEXT_2);
                 mSuggestQueryId = mSuggestCursor.getColumnIndex(
                                 SearchManager.SUGGEST_COLUMN_QUERY);
+                mSuggestIntentExtraDataId = mSuggestCursor.getColumnIndex(
+                                SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
             }
         }
 
@@ -471,22 +551,22 @@
                     case SUGGEST_COLUMN_ICON_1_ID:
                         if (mHistoryCount > mPos) {
                             if (mHistoryCursor.getInt(3) == 1) {
-                                return new Integer(
+                                return Integer.valueOf(
                                         R.drawable.ic_search_category_bookmark)
                                         .toString();
                             } else {
-                                return new Integer(
+                                return Integer.valueOf(
                                         R.drawable.ic_search_category_history)
                                         .toString();
                             }
                         } else {
-                            return new Integer(
+                            return Integer.valueOf(
                                     R.drawable.ic_search_category_suggest)
                                     .toString();
                         }
 
                     case SUGGEST_COLUMN_ICON_2_ID:
-                        return new String("0");
+                        return "0";
 
                     case SUGGEST_COLUMN_QUERY_ID:
                         if (mHistoryCount > mPos) {
@@ -504,6 +584,16 @@
 
                     case SUGGEST_COLUMN_FORMAT:
                         return "html";
+
+                    case SUGGEST_COLUMN_INTENT_EXTRA_DATA:
+                        if (mHistoryCount > mPos) {
+                            return null;
+                        } else if (!mBeyondCursor) {
+                            if (mSuggestIntentExtraDataId == -1) return null;
+                            return mSuggestCursor.getString(mSuggestIntentExtraDataId);
+                        } else {
+                            return null;
+                        }
                 }
             }
             return null;
@@ -637,7 +727,8 @@
                 myArgs = null;
             } else {
                 String like = selectionArgs[0] + "%";
-                if (selectionArgs[0].startsWith("http")) {
+                if (selectionArgs[0].startsWith("http")
+                        || selectionArgs[0].startsWith("file")) {
                     myArgs = new String[1];
                     myArgs[0] = like;
                     suggestSelection = selection;
@@ -655,8 +746,7 @@
 
             Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
                     SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
-                    ORDER_BY,
-                    (new Integer(MAX_SUGGESTION_LONG_ENTRIES)).toString());
+                    ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING);
 
             if (match == URI_MATCH_BOOKMARKS_SUGGEST
                     || Regex.WEB_URL_PATTERN.matcher(selectionArgs[0]).matches()) {
@@ -729,6 +819,7 @@
 
     @Override
     public Uri insert(Uri url, ContentValues initialValues) {
+        boolean isBookmarkTable = false;
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 
         int match = URI_MATCHER.match(url);
@@ -742,6 +833,7 @@
                     uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
                             rowID);
                 }
+                isBookmarkTable = true;
                 break;
             }
 
@@ -764,6 +856,15 @@
             throw new IllegalArgumentException("Unknown URL");
         }
         getContext().getContentResolver().notifyChange(uri, null);
+
+        // Back up the new bookmark set if we just inserted one.
+        // A row created when bookmarks are added from scratch will have
+        // bookmark=1 in the initial value set.
+        if (isBookmarkTable
+                && initialValues.containsKey(BookmarkColumns.BOOKMARK)
+                && initialValues.getAsInteger(BookmarkColumns.BOOKMARK) != 0) {
+            mBackupManager.dataChanged();
+        }
         return uri;
     }
 
@@ -776,20 +877,41 @@
             throw new IllegalArgumentException("Unknown URL");
         }
 
-        if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
+        // need to know whether it's the bookmarks table for a couple of reasons
+        boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID);
+        String id = null;
+
+        if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) {
             StringBuilder sb = new StringBuilder();
             if (where != null && where.length() > 0) {
                 sb.append("( ");
                 sb.append(where);
                 sb.append(" ) AND ");
             }
+            id = url.getPathSegments().get(1);
             sb.append("_id = ");
-            sb.append(url.getPathSegments().get(1));
+            sb.append(id);
             where = sb.toString();
         }
 
+        ContentResolver cr = getContext().getContentResolver();
+
+        // we'lll need to back up the bookmark set if we are about to delete one
+        if (isBookmarkTable) {
+            Cursor cursor = cr.query(Browser.BOOKMARKS_URI,
+                    new String[] { BookmarkColumns.BOOKMARK },
+                    "_id = " + id, null, null);
+            if (cursor.moveToNext()) {
+                if (cursor.getInt(0) != 0) {
+                    // yep, this record is a bookmark
+                    mBackupManager.dataChanged();
+                }
+            }
+            cursor.close();
+        }
+
         int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs);
-        getContext().getContentResolver().notifyChange(url, null);
+        cr.notifyChange(url, null);
         return count;
     }
 
@@ -803,20 +925,59 @@
             throw new IllegalArgumentException("Unknown URL");
         }
 
-        if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
+        String id = null;
+        boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID);
+        boolean changingBookmarks = false;
+
+        if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) {
             StringBuilder sb = new StringBuilder();
             if (where != null && where.length() > 0) {
                 sb.append("( ");
                 sb.append(where);
                 sb.append(" ) AND ");
             }
+            id = url.getPathSegments().get(1);
             sb.append("_id = ");
-            sb.append(url.getPathSegments().get(1));
+            sb.append(id);
             where = sb.toString();
         }
 
+        ContentResolver cr = getContext().getContentResolver();
+
+        // Not all bookmark-table updates should be backed up.  Look to see
+        // whether we changed the title, url, or "is a bookmark" state, and
+        // request a backup if so.
+        if (isBookmarkTable) {
+            // Alterations to the bookmark field inherently change the bookmark
+            // set, so we don't need to query the record; we know a priori that
+            // we will need to back up this change.
+            if (values.containsKey(BookmarkColumns.BOOKMARK)) {
+                changingBookmarks = true;
+            }
+            // changing the title or URL of a bookmark record requires a backup,
+            // but we don't know wether such an update is on a bookmark without
+            // querying the record
+            if (!changingBookmarks &&
+                    (values.containsKey(BookmarkColumns.TITLE)
+                     || values.containsKey(BookmarkColumns.URL))) {
+                // when isBookmarkTable is true, the 'id' var was assigned above
+                Cursor cursor = cr.query(Browser.BOOKMARKS_URI,
+                        new String[] { BookmarkColumns.BOOKMARK },
+                        "_id = " + id, null, null);
+                if (cursor.moveToNext()) {
+                    changingBookmarks = (cursor.getInt(0) != 0);
+                }
+                cursor.close();
+            }
+
+            // if this *is* a bookmark row we're altering, we need to back it up.
+            if (changingBookmarks) {
+                mBackupManager.dataChanged();
+            }
+        }
+
         int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs);
-        getContext().getContentResolver().notifyChange(url, null);
+        cr.notifyChange(url, null);
         return ret;
     }
 
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index a5e23c9..e36d54b 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2007 The Android Open Source Project
  *
@@ -20,17 +21,25 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
 import android.webkit.CookieManager;
+import android.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
 import android.webkit.WebView;
 import android.webkit.WebViewDatabase;
 import android.webkit.WebIconDatabase;
 import android.webkit.WebSettings;
+import android.webkit.WebStorage;
 import android.preference.PreferenceManager;
 import android.provider.Browser;
 
 import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 import java.util.Observable;
 
 /*
@@ -48,14 +57,13 @@
  */
 class BrowserSettings extends Observable {
 
-    // Public variables for settings
+    // Private variables for settings
     // NOTE: these defaults need to be kept in sync with the XML
     // until the performance of PreferenceManager.setDefaultValues()
     // is improved.
     private boolean loadsImagesAutomatically = true;
     private boolean javaScriptEnabled = true;
     private boolean pluginsEnabled = true;
-    private String pluginsPath;  // default value set in loadFromDb().
     private boolean javaScriptCanOpenWindowsAutomatically = false;
     private boolean showSecurityWarnings = true;
     private boolean rememberPasswords = true;
@@ -65,7 +73,25 @@
     private String homeUrl = "";
     private boolean loginInitialized = false;
     private boolean autoFitPage = true;
+    private boolean landscapeOnly = false;
+    private boolean loadsPageInOverviewMode = true;
     private boolean showDebugSettings = false;
+    // HTML5 API flags
+    private boolean appCacheEnabled = true;
+    private boolean databaseEnabled = true;
+    private boolean domStorageEnabled = true;
+    private boolean geolocationEnabled = true;
+    private boolean workersEnabled = true;  // only affects V8. JSC does not have a similar setting
+    // HTML5 API configuration params
+    private long appCacheMaxSize = Long.MAX_VALUE;
+    private String appCachePath;  // default value set in loadFromDb().
+    private String databasePath; // default value set in loadFromDb()
+    private String geolocationDatabasePath; // default value set in loadFromDb()
+    private WebStorageSizeManager webStorageSizeManager;
+
+    private String jsFlags = "";
+
+    private final static String TAG = "BrowserSettings";
 
     // Development settings
     public WebSettings.LayoutAlgorithm layoutAlgorithm =
@@ -75,6 +101,11 @@
     private boolean tracing = false;
     private boolean lightTouch = false;
     private boolean navDump = false;
+
+    // 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;
+
     // Browser only settings
     private boolean doFlick = false;
 
@@ -100,19 +131,21 @@
     public final static String PREF_EXTRAS_RESET_DEFAULTS =
             "reset_default_preferences";
     public final static String PREF_DEBUG_SETTINGS = "debug_menu";
-    public final static String PREF_GEARS_SETTINGS = "gears_settings";
+    public final static String PREF_WEBSITE_SETTINGS = "website_settings";
     public final static String PREF_TEXT_SIZE = "text_size";
     public final static String PREF_DEFAULT_ZOOM = "default_zoom";
     public final static String PREF_DEFAULT_TEXT_ENCODING =
             "default_text_encoding";
+    public final static String PREF_CLEAR_GEOLOCATION_ACCESS =
+            "privacy_clear_geolocation_access";
 
     private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (Macintosh; " +
-            "U; Intel Mac OS X 10_5_5; en-us) AppleWebKit/525.18 (KHTML, " +
-            "like Gecko) Version/3.1.2 Safari/525.20.1";
+            "U; Intel Mac OS X 10_5_7; en-us) AppleWebKit/530.17 (KHTML, " +
+            "like Gecko) Version/4.0 Safari/530.17";
 
     private static final String IPHONE_USERAGENT = "Mozilla/5.0 (iPhone; U; " +
-            "CPU iPhone OS 2_2 like Mac OS X; en-us) AppleWebKit/525.18.1 " +
-            "(KHTML, like Gecko) Version/3.1.1 Mobile/5G77 Safari/525.20";
+            "CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 " +
+            "(KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16";
 
     // Value to truncate strings when adding them to a TextView within
     // a ListView
@@ -157,7 +190,6 @@
             s.setLoadsImagesAutomatically(b.loadsImagesAutomatically);
             s.setJavaScriptEnabled(b.javaScriptEnabled);
             s.setPluginsEnabled(b.pluginsEnabled);
-            s.setPluginsPath(b.pluginsPath);
             s.setJavaScriptCanOpenWindowsAutomatically(
                     b.javaScriptCanOpenWindowsAutomatically);
             s.setDefaultTextEncodingName(b.defaultTextEncodingName);
@@ -171,13 +203,29 @@
             s.setLightTouchEnabled(b.lightTouch);
             s.setSaveFormData(b.saveFormData);
             s.setSavePassword(b.rememberPasswords);
+            s.setLoadWithOverviewMode(b.loadsPageInOverviewMode);
 
             // WebView inside Browser doesn't want initial focus to be set.
             s.setNeedInitialFocus(false);
             // Browser supports multiple windows
             s.setSupportMultipleWindows(true);
-            // Turn off file access
-            s.setAllowFileAccess(false);
+
+            // HTML5 API flags
+            s.setAppCacheEnabled(b.appCacheEnabled);
+            s.setDatabaseEnabled(b.databaseEnabled);
+            s.setDomStorageEnabled(b.domStorageEnabled);
+            s.setWorkersEnabled(b.workersEnabled);  // This only affects V8.
+            s.setGeolocationEnabled(b.geolocationEnabled);
+
+            // HTML5 configuration parameters.
+            s.setAppCacheMaxSize(b.appCacheMaxSize);
+            s.setAppCachePath(b.appCachePath);
+            s.setDatabasePath(b.databasePath);
+            s.setGeolocationDatabasePath(b.geolocationDatabasePath);
+
+            // Enable/Disable the error console.
+            b.mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+                    b.showDebugSettings && b.showConsole);
         }
     }
 
@@ -193,10 +241,18 @@
     public void loadFromDb(Context ctx) {
         SharedPreferences p =
                 PreferenceManager.getDefaultSharedPreferences(ctx);
-
-        // Set the default value for the plugins path to the application's
-        // local directory.
-        pluginsPath = ctx.getDir("plugins", 0).getPath();
+        // 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();
 
         homeUrl = getFactoryResetHomeUrl(ctx);
 
@@ -218,7 +274,6 @@
                 javaScriptEnabled);
         pluginsEnabled = p.getBoolean("enable_plugins",
                 pluginsEnabled);
-        pluginsPath = p.getString("plugins_path", pluginsPath);
         javaScriptCanOpenWindowsAutomatically = !p.getBoolean(
             "block_popup_windows",
             !javaScriptCanOpenWindowsAutomatically);
@@ -238,6 +293,16 @@
         zoomDensity = WebSettings.ZoomDensity.valueOf(
                 p.getString(PREF_DEFAULT_ZOOM, zoomDensity.name()));
         autoFitPage = p.getBoolean("autofit_pages", autoFitPage);
+        loadsPageInOverviewMode = p.getBoolean("load_page",
+                loadsPageInOverviewMode);
+        boolean landscapeOnlyTemp =
+                p.getBoolean("landscape_only", landscapeOnly);
+        if (landscapeOnlyTemp != landscapeOnly) {
+            landscapeOnly = landscapeOnlyTemp;
+            mTabControl.getBrowserActivity().setRequestedOrientation(
+                    landscapeOnly ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+                    : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+        }
         useWideViewPort = true; // use wide view port for either setting
         if (autoFitPage) {
             layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS;
@@ -274,17 +339,41 @@
             doFlick = p.getBoolean("enable_flick", doFlick);
             userAgent = Integer.parseInt(p.getString("user_agent", "0"));
         }
-        update();
-    }
+        // 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", "");
 
-    public String getPluginsPath() {
-        return pluginsPath;
+        // Read the setting for showing/hiding the JS Console always so that should the
+        // user enable debug settings, we already know if we should show the console.
+        // The user will never see the console unless they navigate to about:debug,
+        // regardless of the setting we read here. This setting is only used after debug
+        // is enabled.
+        showConsole = p.getBoolean("javascript_console", showConsole);
+        mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+                showDebugSettings && showConsole);
+
+        // HTML5 API flags
+        appCacheEnabled = p.getBoolean("enable_appcache", appCacheEnabled);
+        databaseEnabled = p.getBoolean("enable_database", databaseEnabled);
+        domStorageEnabled = p.getBoolean("enable_domstorage", domStorageEnabled);
+        geolocationEnabled = p.getBoolean("enable_geolocation", geolocationEnabled);
+        workersEnabled = p.getBoolean("enable_workers", workersEnabled);
+
+        update();
     }
 
     public String getHomePage() {
         return homeUrl;
     }
 
+    public String getJsFlags() {
+        return jsFlags;
+    }
+
+    public WebStorageSizeManager getWebStorageSizeManager() {
+        return webStorageSizeManager;
+    }
+
     public void setHomePage(Context context, String url) {
         Editor ed = PreferenceManager.
                 getDefaultSharedPreferences(context).edit();
@@ -436,14 +525,48 @@
         db.clearHttpAuthUsernamePassword();
     }
 
-    /*package*/ void resetDefaultPreferences(Context context) {
+    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>() {
+            public void onReceiveValue(Set 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) {
         SharedPreferences p =
-            PreferenceManager.getDefaultSharedPreferences(context);
+            PreferenceManager.getDefaultSharedPreferences(ctx);
         p.edit().clear().commit();
-        PreferenceManager.setDefaultValues(context, R.xml.browser_preferences,
+        PreferenceManager.setDefaultValues(ctx, R.xml.browser_preferences,
                 true);
         // reset homeUrl
-        setHomePage(context, getFactoryResetHomeUrl(context));
+        setHomePage(ctx, getFactoryResetHomeUrl(ctx));
+        // reset appcache max size
+        appCacheMaxSize = webStorageSizeManager.getAppCacheMaxSize();
     }
 
     private String getFactoryResetHomeUrl(Context context) {
diff --git a/src/com/android/browser/BrowserYesNoPreference.java b/src/com/android/browser/BrowserYesNoPreference.java
index 65cde71..caea092 100644
--- a/src/com/android/browser/BrowserYesNoPreference.java
+++ b/src/com/android/browser/BrowserYesNoPreference.java
@@ -38,6 +38,7 @@
             Context context = getContext();
             if (BrowserSettings.PREF_CLEAR_CACHE.equals(getKey())) {
                 BrowserSettings.getInstance().clearCache(context);
+                BrowserSettings.getInstance().clearDatabases(context);
             } else if (BrowserSettings.PREF_CLEAR_COOKIES.equals(getKey())) {
                 BrowserSettings.getInstance().clearCookies(context);
             } else if (BrowserSettings.PREF_CLEAR_HISTORY.equals(getKey())) {
@@ -50,6 +51,9 @@
                     getKey())) {
                 BrowserSettings.getInstance().resetDefaultPreferences(context);
                 setEnabled(true);
+            } else if (BrowserSettings.PREF_CLEAR_GEOLOCATION_ACCESS.equals(
+                    getKey())) {
+                BrowserSettings.getInstance().clearLocationAccess(context);
             }
         }
     }
diff --git a/src/com/android/browser/CombinedBookmarkHistoryActivity.java b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
index 5678b44..a611d79 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryActivity.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
@@ -24,10 +24,10 @@
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.provider.Browser;
+import android.view.Window;
 import android.webkit.WebIconDatabase.IconListener;
 import android.widget.TabHost;
 import android.widget.TabHost.TabSpec;
-import android.view.Window;
 
 import java.util.HashMap;
 import java.util.Vector;
@@ -67,10 +67,9 @@
         }
     }
     private static IconListenerSet sIconListenerSet;
-    static IconListenerSet getIconListenerSet(ContentResolver cr) {
+    static IconListenerSet getIconListenerSet() {
         if (null == sIconListenerSet) {
             sIconListenerSet = new IconListenerSet();
-            Browser.requestAllIcons(cr, null, sIconListenerSet);
         }
         return sIconListenerSet;
     }
@@ -78,42 +77,52 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        requestWindowFeature(Window.FEATURE_NO_TITLE);
         setContentView(R.layout.tabs);
-        TabHost tabHost = getTabHost();
-        tabHost.setOnTabChangedListener(this);
+
+        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+
+        getTabHost().setOnTabChangedListener(this);
 
         Bundle extras = getIntent().getExtras();
-        Resources resources = getResources();
 
-        getIconListenerSet(getContentResolver());
+        getIconListenerSet();
+        // Do this every time we create a new activity so that we get the
+        // newest icons.
+        Browser.requestAllIcons(getContentResolver(), null, sIconListenerSet);
+
         Intent bookmarksIntent = new Intent(this, BrowserBookmarksPage.class);
         bookmarksIntent.putExtras(extras);
-        tabHost.addTab(tabHost.newTabSpec(BOOKMARKS_TAB)
-                .setIndicator(resources.getString(R.string.tab_bookmarks),
-                resources.getDrawable(R.drawable.browser_bookmark_tab))
-                .setContent(bookmarksIntent));
+        createTab(bookmarksIntent, R.string.tab_bookmarks,
+                R.drawable.browser_bookmark_tab, BOOKMARKS_TAB);
 
-        Intent visitedIntent = new Intent(this, MostVisitedActivity.class);
-        visitedIntent.putExtras(extras);
-        tabHost.addTab(tabHost.newTabSpec(VISITED_TAB)
-                .setIndicator(resources.getString(R.string.tab_most_visited),
-                resources.getDrawable(R.drawable.browser_visited_tab))
-                .setContent(visitedIntent));
+        Intent visitedIntent = new Intent(this, BrowserBookmarksPage.class);
+        // Need to copy extras so the bookmarks activity and this one will be
+        // different
+        Bundle visitedExtras = 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);
         historyIntent.putExtras(extras);
-        tabHost.addTab(tabHost.newTabSpec(HISTORY_TAB)
-                .setIndicator(resources.getString(R.string.tab_history),
-                resources.getDrawable(R.drawable.
-                browser_history_tab)).setContent(historyIntent));
+        createTab(historyIntent, R.string.tab_history,
+                R.drawable.browser_history_tab, HISTORY_TAB);
 
         String defaultTab = extras.getString(STARTING_TAB);
         if (defaultTab != null) {
-            tabHost.setCurrentTab(2);
+            getTabHost().setCurrentTab(2);
         }
     }
 
+    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) {
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
new file mode 100644
index 0000000..07d2d3a
--- /dev/null
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -0,0 +1,136 @@
+/*
+ * 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.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.http.AndroidHttpClient;
+import android.os.AsyncTask;
+import android.provider.Browser;
+import android.webkit.WebView;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.params.HttpClientParams;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+class DownloadTouchIcon extends AsyncTask<String, Void, Bitmap> {
+    private final ContentResolver mContentResolver;
+    private final Cursor mCursor;
+    private final String mOriginalUrl;
+    private final String mUrl;
+    private final String mUserAgent;
+    /* package */ BrowserActivity mActivity;
+
+    public DownloadTouchIcon(BrowserActivity activity, ContentResolver cr,
+            Cursor c, WebView view) {
+        mActivity = activity;
+        mContentResolver = cr;
+        mCursor = c;
+        // Store these in case they change.
+        mOriginalUrl = view.getOriginalUrl();
+        mUrl = view.getUrl();
+        mUserAgent = view.getSettings().getUserAgentString();
+    }
+
+    public DownloadTouchIcon(ContentResolver cr, Cursor c, String url) {
+        mActivity = null;
+        mContentResolver = cr;
+        mCursor = c;
+        mOriginalUrl = null;
+        mUrl = url;
+        mUserAgent = null;
+    }
+
+    @Override
+    public Bitmap doInBackground(String... values) {
+        String url = values[0];
+
+        AndroidHttpClient client = AndroidHttpClient.newInstance(
+                mUserAgent);
+        HttpGet request = new HttpGet(url);
+
+        // Follow redirects
+        HttpClientParams.setRedirecting(client.getParams(), true);
+
+        try {
+            HttpResponse response = client.execute(request);
+
+            if (response.getStatusLine().getStatusCode() == 200) {
+                HttpEntity entity = response.getEntity();
+                if (entity != null) {
+                    InputStream content = entity.getContent();
+                    if (content != null) {
+                        Bitmap icon = BitmapFactory.decodeStream(
+                                content, null, null);
+                        return icon;
+                    }
+                }
+            }
+        } catch (IllegalArgumentException ex) {
+            request.abort();
+        } catch (IOException ex) {
+            request.abort();
+        } finally {
+            client.close();
+        }
+        return null;
+    }
+
+    @Override
+    protected void onCancelled() {
+        if (mCursor != null) {
+            mCursor.close();
+        }
+    }
+
+    @Override
+    public void onPostExecute(Bitmap icon) {
+        // Do this first in case the download failed.
+        if (mActivity != null) {
+            // Remove the touch icon loader from the BrowserActivity.
+            mActivity.mTouchIconLoader = null;
+        }
+
+        if (icon == null || mCursor == null || isCancelled()) {
+            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()) {
+            do {
+                mContentResolver.update(ContentUris.withAppendedId(
+                        Browser.BOOKMARKS_URI, mCursor.getInt(0)),
+                        values, null, null);
+            } while (mCursor.moveToNext());
+        }
+        mCursor.close();
+    }
+}
diff --git a/src/com/android/browser/ErrorConsoleView.java b/src/com/android/browser/ErrorConsoleView.java
new file mode 100644
index 0000000..56f663b
--- /dev/null
+++ b/src/com/android/browser/ErrorConsoleView.java
@@ -0,0 +1,339 @@
+/*
+ * 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.content.Context;
+import android.database.DataSetObserver;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.webkit.WebView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TwoLineListItem;
+
+import java.util.Vector;
+
+/* package */ class ErrorConsoleView extends LinearLayout {
+
+    /**
+     * Define some constants to describe the visibility of the error console.
+     */
+    public static final int SHOW_MINIMIZED = 0;
+    public static final int SHOW_MAXIMIZED = 1;
+    public static final int SHOW_NONE      = 2;
+
+    private TextView mConsoleHeader;
+    private ErrorConsoleListView mErrorList;
+    private LinearLayout mEvalJsViewGroup;
+    private EditText mEvalEditText;
+    private Button mEvalButton;
+    private WebView mWebView;
+    private int mCurrentShowState = SHOW_NONE;
+
+    private boolean mSetupComplete = false;
+
+    // Before we've been asked to display the console, cache any messages that should
+    // be added to the console. Then when we do display the console, add them to the view
+    // then.
+    private Vector<ErrorConsoleMessage> mErrorMessageCache;
+
+    public ErrorConsoleView(Context context) {
+        super(context);
+    }
+
+    public ErrorConsoleView(Context context, AttributeSet attributes) {
+        super(context, attributes);
+    }
+
+    private void commonSetupIfNeeded() {
+        if (mSetupComplete) {
+            return;
+        }
+
+        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.error_console, this);
+
+        // Get references to each ui element.
+        mConsoleHeader = (TextView) findViewById(R.id.error_console_header_id);
+        mErrorList = (ErrorConsoleListView) findViewById(R.id.error_console_list_id);
+        mEvalJsViewGroup = (LinearLayout) findViewById(R.id.error_console_eval_view_group_id);
+        mEvalEditText = (EditText) findViewById(R.id.error_console_eval_text_id);
+        mEvalButton = (Button) findViewById(R.id.error_console_eval_button_id);
+
+        mEvalButton.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                // Send the javascript to be evaluated to webkit as a javascript: url
+                // TODO: Can we expose access to webkit's JS interpreter here and evaluate it that
+                // way? Note that this is called on the UI thread so we will need to post a message
+                // to the WebCore thread to implement this.
+                if (mWebView != null) {
+                    mWebView.loadUrl("javascript:" + mEvalEditText.getText());
+                }
+
+                mEvalEditText.setText("");
+            }
+        });
+
+        // Make clicking on the console title bar min/maximse it.
+        mConsoleHeader.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                if (mCurrentShowState == SHOW_MINIMIZED) {
+                    showConsole(SHOW_MAXIMIZED);
+                } else {
+                    showConsole(SHOW_MINIMIZED);
+                }
+            }
+        });
+
+        // Add any cached messages to the list now that we've assembled the view.
+        if (mErrorMessageCache != null) {
+            for (ErrorConsoleMessage msg : mErrorMessageCache) {
+                mErrorList.addErrorMessage(msg.getMessage(), msg.getSourceID(), msg.getLineNumber());
+            }
+            mErrorMessageCache.clear();
+        }
+
+        mSetupComplete = true;
+    }
+
+    /**
+     * Adds a message to the set of messages the console uses.
+     */
+    public void addErrorMessage(String msg, String sourceId, int lineNumber) {
+        if (mSetupComplete) {
+            mErrorList.addErrorMessage(msg, sourceId, lineNumber);
+        } else {
+            if (mErrorMessageCache == null) {
+                mErrorMessageCache = new Vector<ErrorConsoleMessage>();
+            }
+            mErrorMessageCache.add(new ErrorConsoleMessage(msg, sourceId, lineNumber));
+        }
+    }
+
+    /**
+     * Removes all error messages from the console.
+     */
+    public void clearErrorMessages() {
+        if (mSetupComplete) {
+            mErrorList.clearErrorMessages();
+        } else if (mErrorMessageCache != null) {
+            mErrorMessageCache.clear();
+        }
+    }
+
+    /**
+     * Returns the current number of errors displayed in the console.
+     */
+    public int numberOfErrors() {
+        if (mSetupComplete) {
+            return mErrorList.getCount();
+        } else {
+            return (mErrorMessageCache == null) ? 0 : mErrorMessageCache.size();
+        }
+    }
+
+    /**
+     * Sets the webview that this console is associated with. Currently this is used so
+     * we can call into webkit to evaluate JS expressions in the console.
+     */
+    public void setWebView(WebView webview) {
+        mWebView = webview;
+    }
+
+    /**
+     * Sets the visibility state of the console.
+     */
+    public void showConsole(int show_state) {
+        commonSetupIfNeeded();
+        switch (show_state) {
+            case SHOW_MINIMIZED:
+                mConsoleHeader.setVisibility(View.VISIBLE);
+                mConsoleHeader.setText(R.string.error_console_header_text_minimized);
+                mErrorList.setVisibility(View.GONE);
+                mEvalJsViewGroup.setVisibility(View.GONE);
+                break;
+
+            case SHOW_MAXIMIZED:
+                mConsoleHeader.setVisibility(View.VISIBLE);
+                mConsoleHeader.setText(R.string.error_console_header_text_maximized);
+                mErrorList.setVisibility(View.VISIBLE);
+                mEvalJsViewGroup.setVisibility(View.VISIBLE);
+                break;
+
+            case SHOW_NONE:
+                mConsoleHeader.setVisibility(View.GONE);
+                mErrorList.setVisibility(View.GONE);
+                mEvalJsViewGroup.setVisibility(View.GONE);
+                break;
+        }
+        mCurrentShowState = show_state;
+    }
+
+    /**
+     * Returns the current visibility state of the console.
+     */
+    public int getShowState() {
+        if (mSetupComplete) {
+            return mCurrentShowState;
+        } else {
+            return SHOW_NONE;
+        }
+    }
+
+    /**
+     * This class extends ListView to implement the View that will actually display the set of
+     * errors encountered on the current page.
+     */
+    private static class ErrorConsoleListView extends ListView {
+        // An adapter for this View that contains a list of error messages.
+        private ErrorConsoleMessageList mConsoleMessages;
+
+        public ErrorConsoleListView(Context context, AttributeSet attributes) {
+            super(context, attributes);
+            mConsoleMessages = new ErrorConsoleMessageList(context);
+            setAdapter(mConsoleMessages);
+        }
+
+        public void addErrorMessage(String msg, String sourceId, int lineNumber) {
+            mConsoleMessages.add(msg, sourceId, lineNumber);
+            setSelection(mConsoleMessages.getCount());
+        }
+
+        public void clearErrorMessages() {
+            mConsoleMessages.clear();
+        }
+
+        /**
+         * This class is an adapter for ErrorConsoleListView that contains the error console
+         * message data.
+         */
+        private class ErrorConsoleMessageList extends android.widget.BaseAdapter
+                implements android.widget.ListAdapter {
+
+            private Vector<ErrorConsoleMessage> mMessages;
+            private LayoutInflater mInflater;
+
+            public ErrorConsoleMessageList(Context context) {
+                mMessages = new Vector<ErrorConsoleMessage>();
+                mInflater = (LayoutInflater)context.getSystemService(
+                        Context.LAYOUT_INFLATER_SERVICE);
+            }
+
+            /**
+             * Add a new message to the list and update the View.
+             */
+            public void add(String msg, String sourceID, int lineNumber) {
+                mMessages.add(new ErrorConsoleMessage(msg, sourceID, lineNumber));
+                notifyDataSetChanged();
+            }
+
+            /**
+             * Remove all messages from the list and update the view.
+             */
+            public void clear() {
+                mMessages.clear();
+                notifyDataSetChanged();
+            }
+
+            @Override
+            public boolean areAllItemsEnabled() {
+                return false;
+            }
+
+            @Override
+            public boolean isEnabled(int position) {
+                return false;
+            }
+
+            public long getItemId(int position) {
+                return position;
+            }
+
+            public Object getItem(int position) {
+                return mMessages.get(position);
+            }
+
+            public int getCount() {
+                return mMessages.size();
+            }
+
+            @Override
+            public boolean hasStableIds() {
+                return true;
+            }
+
+            /**
+             * Constructs a TwoLineListItem for the error at position.
+             */
+            public View getView(int position, View convertView, ViewGroup parent) {
+                View view;
+                ErrorConsoleMessage error = mMessages.get(position);
+
+                if (error == null) {
+                    return null;
+                }
+
+                if (convertView == null) {
+                    view = mInflater.inflate(android.R.layout.two_line_list_item, parent, false);
+                } else {
+                    view = convertView;
+                }
+
+                TextView headline = (TextView) view.findViewById(android.R.id.text1);
+                TextView subText = (TextView) view.findViewById(android.R.id.text2);
+                headline.setText(error.getSourceID() + ":" + error.getLineNumber());
+                subText.setText(error.getMessage());
+                return view;
+            }
+
+        }
+    }
+
+    /**
+     * This class holds the data for a single error message in the console.
+     */
+    private static class ErrorConsoleMessage {
+        private String mMessage;
+        private String mSourceID;
+        private int mLineNumber;
+
+        public ErrorConsoleMessage(String msg, String sourceID, int lineNumber) {
+            mMessage = msg;
+            mSourceID = sourceID;
+            mLineNumber = lineNumber;
+        }
+
+        public String getMessage() {
+            return mMessage;
+        }
+
+        public String getSourceID() {
+            return mSourceID;
+        }
+
+        public int getLineNumber() {
+            return mLineNumber;
+        }
+    }
+}
diff --git a/src/com/android/browser/FakeWebView.java b/src/com/android/browser/FakeWebView.java
deleted file mode 100644
index da5ef5f..0000000
--- a/src/com/android/browser/FakeWebView.java
+++ /dev/null
@@ -1,106 +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.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Picture;
-import android.util.AttributeSet;
-import android.view.View;
-import android.webkit.WebView;
-import android.widget.ImageView;
-
-import android.util.Log;
-
-/**
- *  This class is used by ImageAdapter to draw a representation of each tab. It 
- *  overrides ImageView so it can be used for the new tab image as well.
- */
-public class FakeWebView extends ImageView {
-    private TabControl.PickerData mPickerData;
-    private boolean        mUsesResource;
-
-    public FakeWebView(Context context) {
-        this(context, null);
-    }
-    
-    public FakeWebView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-    
-    public FakeWebView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mUsesResource) {
-            super.onDraw(canvas);
-        } else {
-            // Always draw white behind the picture just in case the picture
-            // draws nothing.
-            // FIXME: We used to draw white only when the WebView was null but
-            // sometimes the picture was empty. So now we always draw white. It
-            // would be nice to know if the picture is empty so we can avoid
-            // drawing white.
-            canvas.drawColor(Color.WHITE);
-            if (mPickerData != null) {
-                final Picture p = mPickerData.mPicture;
-                if (p != null) {
-                    canvas.save();
-                    float scale = getWidth() * mPickerData.mScale
-                            / mPickerData.mWidth;
-                    // Check for NaN and infinity.
-                    if (Float.isNaN(scale) || Float.isInfinite(scale)) {
-                        scale = 1.0f;
-                    }
-                    canvas.scale(scale, scale);
-                    canvas.translate(-mPickerData.mScrollX,
-                            -mPickerData.mScrollY);
-                    canvas.drawPicture(p);
-                    canvas.restore();
-                }
-            }
-        }
-    }
-    
-    @Override
-    public void setImageResource(int resId) {
-        mUsesResource = true;
-        mPickerData = null;
-        super.setImageResource(resId);
-    }
-
-    /**
-     *  Set a WebView for this FakeWebView to represent.
-     *  @param  t The tab whose picture and other data will be used in onDraw.
-     */
-    public void setTab(TabControl.Tab t) {
-        mUsesResource = false;
-        if (mPickerData != null) {
-            // Clear the old tab's view first
-            mPickerData.mFakeWebView = null;
-        }
-        mPickerData = null;
-        if (t != null && t.getPickerData() != null) {
-            mPickerData = t.getPickerData();
-            mPickerData.mFakeWebView = this;
-        }
-    }
-}
diff --git a/src/com/android/browser/FetchUrlMimeType.java b/src/com/android/browser/FetchUrlMimeType.java
index 8578643..0081d32 100644
--- a/src/com/android/browser/FetchUrlMimeType.java
+++ b/src/com/android/browser/FetchUrlMimeType.java
@@ -58,7 +58,7 @@
         mValues = values[0];
 
         // Check to make sure we have a URI to download
-        String uri = mValues.getAsString(Downloads.URI);
+        String uri = mValues.getAsString(Downloads.COLUMN_URI);
         if (uri == null || uri.length() == 0) {
             return null;
         }
@@ -66,21 +66,20 @@
         // User agent is likely to be null, though the AndroidHttpClient
         // seems ok with that.
         AndroidHttpClient client = AndroidHttpClient.newInstance(
-                mValues.getAsString(Downloads.USER_AGENT));
+                mValues.getAsString(Downloads.COLUMN_USER_AGENT));
         HttpHead request = new HttpHead(uri);
 
-        String cookie = mValues.getAsString(Downloads.COOKIE_DATA);
+        String cookie = mValues.getAsString(Downloads.COLUMN_COOKIE_DATA);
         if (cookie != null && cookie.length() > 0) {
             request.addHeader("Cookie", cookie);
         }
 
-        String referer = mValues.getAsString(Downloads.REFERER);
+        String referer = mValues.getAsString(Downloads.COLUMN_REFERER);
         if (referer != null && referer.length() > 0) {
             request.addHeader("Referer", referer);
         }
 
         HttpResponse response;
-        Boolean succeeded = true;
         String mimeType = null;
         try {
             response = client.execute(request);
@@ -111,19 +110,19 @@
    @Override
     public void onPostExecute(String mimeType) {
        if (mimeType != null) {
-           String url = mValues.getAsString(Downloads.URI);
+           String url = mValues.getAsString(Downloads.COLUMN_URI);
            if (mimeType.equalsIgnoreCase("text/plain") ||
                    mimeType.equalsIgnoreCase("application/octet-stream")) {
                String newMimeType =
                        MimeTypeMap.getSingleton().getMimeTypeFromExtension(
                            MimeTypeMap.getFileExtensionFromUrl(url));
                if (newMimeType != null) {
-                   mValues.put(Downloads.MIMETYPE, newMimeType);
+                   mValues.put(Downloads.COLUMN_MIME_TYPE, newMimeType);
                }
            }
            String filename = URLUtil.guessFileName(url,
                    null, mimeType);
-           mValues.put(Downloads.FILENAME_HINT, filename);
+           mValues.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
        }
 
        // Start the download
diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java
index 6e9574c..df212d0 100644
--- a/src/com/android/browser/FindDialog.java
+++ b/src/com/android/browser/FindDialog.java
@@ -42,7 +42,6 @@
     private BrowserActivity mBrowserActivity;
     
     // Views with which the user can interact.
-    private View            mOk;
     private EditText        mEditText;
     private View            mNextButton;
     private View            mPrevButton;
@@ -129,7 +128,6 @@
         
         button = findViewById(R.id.done);
         button.setOnClickListener(mFindCancelListener);
-        mOk = button;
         
         mMatches = (TextView) findViewById(R.id.matches);
         mMatchesView = findViewById(R.id.matches_view);
@@ -143,23 +141,14 @@
         mBrowserActivity.closeFind();
         mWebView.clearMatches();
     }
-    
+
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        int code = event.getKeyCode();
-        boolean up = event.getAction() == KeyEvent.ACTION_UP;
-        switch (code) {
-            case KeyEvent.KEYCODE_DPAD_CENTER:
-            case KeyEvent.KEYCODE_ENTER:
-                if (!mEditText.hasFocus()) {
-                    break;
-                }
-                if (up) {
-                    findNext();
-                }
-                return true;
-            default:
-                break;
+        if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
+                && event.getAction() == KeyEvent.ACTION_UP
+                && mEditText.hasFocus()) {
+            findNext();
+            return true;
         }
         return super.dispatchKeyEvent(event);
     }
@@ -205,6 +194,8 @@
             mMatchesView.setVisibility(View.INVISIBLE);
         } else {
             mMatchesView.setVisibility(View.VISIBLE);
+            mWebView.setFindDialogHeight(
+                getWindow().getDecorView().getHeight());
             int found = mWebView.findAll(find.toString());
             setMatchesFound(found);
             if (found < 2) {
diff --git a/src/com/android/browser/GearsBaseDialog.java b/src/com/android/browser/GearsBaseDialog.java
deleted file mode 100644
index 638ba27..0000000
--- a/src/com/android/browser/GearsBaseDialog.java
+++ /dev/null
@@ -1,474 +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.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Handler;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.UnderlineSpan;
-import android.util.Log;
-import android.view.InflateException;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import java.io.InputStream;
-import java.io.IOException;
-import java.lang.ClassCastException;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Base dialog class for gears
- */
-class GearsBaseDialog {
-
-  private static final String TAG = "GearsNativeDialog";
-  protected Handler mHandler;
-  protected Activity mActivity;
-  protected String mDialogArguments;
-
-  private Bitmap mIcon;
-  private final int MAX_ICON_SIZE = 64;
-  protected int mChoosenIconSize;
-
-  // Dialog closing types
-  public static final int CANCEL = 0;
-  public static final int ALWAYS_DENY = 1;
-  public static final int ALLOW = 2;
-  public static final int DENY = 3;
-  public static final int NEW_ICON = 4;
-  public static final int UPDATE_ICON = 5;
-  public static final int REQUEST_ICON = 6;
-  public static final int PAUSE_REQUEST_ICON = 7;
-  public static final int CLEAR_REQUEST_ICON = 8;
-
-  protected final String LOCAL_DATA_STRING = "localData";
-  protected final String LOCAL_STORAGE_STRING = "localStorage";
-  protected final String LOCATION_DATA_STRING = "locationData";
-
-  protected String mGearsVersion = "UNDEFINED";
-  protected boolean mDebug = false;
-
-  public GearsBaseDialog(Activity activity, Handler handler, String arguments) {
-    mActivity = activity;
-    mHandler = handler;
-    mDialogArguments = arguments;
-  }
-
-  Resources getResources() {
-    return mActivity.getResources();
-  }
-
-  Object getSystemService(String name) {
-    return mActivity.getSystemService(name);
-  }
-
-  View findViewById(int id) {
-    return mActivity.findViewById(id);
-  }
-
-  private String getString(int id) {
-    return mActivity.getString(id);
-  }
-
-  public void setDebug(boolean debug) {
-    mDebug = debug;
-  }
-
-  public void setGearsVersion(String version) {
-    mGearsVersion = version;
-  }
-
-  public String closeDialog(int closingType) {
-    return null;
-  }
-
-  /*
-   * Utility methods for setting up the dialogs elements
-   */
-
-  /**
-   * Inflate a given layout in a view (which has to be
-   * a ViewGroup, e.g. LinearLayout).
-   * This is used to share the basic dialog outline among
-   * the different dialog types.
-   */
-  void inflate(int layout, int viewID) {
-    LayoutInflater inflater = (LayoutInflater) getSystemService(
-        Context.LAYOUT_INFLATER_SERVICE);
-    View view = findViewById(viewID);
-    if (view != null) {
-      try {
-        ViewGroup viewGroup = (ViewGroup) view;
-        inflater.inflate(layout, viewGroup);
-      } catch (ClassCastException e) {
-        String msg = "exception, the view (" + view + ")";
-        msg += " is not a ViewGroup";
-        Log.e(TAG, msg, e);
-      } catch (InflateException e) {
-        Log.e(TAG, "exception while inflating the layout", e);
-      }
-    } else {
-      String msg = "problem, trying to inflate a non-existent view";
-      msg += " (" + viewID + ")";
-      Log.e(TAG, msg);
-    }
-  }
-
-  /**
-   * Button setup.
-   * Set the button's text and its listener. If the text resource's id
-   * is 0, makes the button invisible.
-   */
-  void setupButton(int buttonRscID,
-                   int rscString,
-                   View.OnClickListener listener,
-                   boolean isLink,
-                   boolean requestFocus) {
-    View view = findViewById(buttonRscID);
-    if (view == null) {
-      return;
-    }
-
-    Button button = (Button) view;
-
-    if (rscString == 0) {
-      button.setVisibility(View.GONE);
-    } else {
-      CharSequence text = getString(rscString);
-      button.setText(text);
-      button.setOnClickListener(listener);
-      if (isLink) {
-        displayAsLink(button);
-      }
-      if (requestFocus) {
-        button.requestFocus();
-      }
-    }
-  }
-
-  /**
-   * Button setup: as the above method, except that 'isLink' and
-   * 'requestFocus' default to false.
-   */
-  void setupButton(int buttonRsc, int rsc,
-                   View.OnClickListener listener) {
-    setupButton(buttonRsc, rsc, listener, false, false);
-  }
-
-  /**
-   * Utility method to setup the three dialog buttons.
-   */
-  void setupButtons(int alwaysDenyRsc, int allowRsc, int denyRsc) {
-    setupButton(R.id.button_alwaysdeny, alwaysDenyRsc,
-                new Button.OnClickListener() {
-                  public void onClick(View v) {
-                    mHandler.sendEmptyMessage(ALWAYS_DENY);
-                  }
-                });
-
-    setupButton(R.id.button_allow, allowRsc,
-                new Button.OnClickListener() {
-                  public void onClick(View v) {
-                    mHandler.sendEmptyMessage(ALLOW);
-                  }
-                });
-
-    setupButton(R.id.button_deny, denyRsc,
-                new Button.OnClickListener() {
-                  public void onClick(View v) {
-                    mHandler.sendEmptyMessage(DENY);
-                  }
-                });
-  }
-
-  /**
-   * Display a button as an HTML link. Remove the background, set the
-   * text color to R.color.dialog_link and draw an underline
-   */
-  void displayAsLink(Button button) {
-    if (button == null) {
-      return;
-    }
-
-    CharSequence text = button.getText();
-    button.setBackgroundDrawable(null);
-    int color = getResources().getColor(R.color.dialog_link);
-    button.setTextColor(color);
-    SpannableString str = new SpannableString(text);
-    str.setSpan(new UnderlineSpan(), 0, str.length(),
-                Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-    button.setText(str);
-    button.setFocusable(false);
-  }
-
-  /**
-   * Utility method to set elements' text indicated in
-   * the dialogs' arguments.
-   */
-  void setLabel(JSONObject json, String name, int rsc) {
-    try {
-      if (json.has(name)) {
-        String text = json.getString(name);
-        View view = findViewById(rsc);
-        if (view != null && text != null) {
-          TextView textView = (TextView) view;
-          textView.setText(text);
-          textView.setVisibility(View.VISIBLE);
-        }
-      }
-    } catch (JSONException e) {
-      Log.e(TAG, "json exception", e);
-    }
-  }
-
-  /**
-   * Utility method to hide a view.
-   */
-  void hideView(View v, int rsc) {
-    if (rsc == 0) {
-      return;
-    }
-    View view;
-    if (v == null) {
-      view = findViewById(rsc);
-    } else {
-      view = v.findViewById(rsc);
-    }
-    if (view != null) {
-      view.setVisibility(View.GONE);
-    }
-  }
-
-  /**
-   * Utility method to show a view.
-   */
-  void showView(View v, int rsc) {
-    if (rsc == 0) {
-      return;
-    }
-    View view;
-    if (v == null) {
-      view = findViewById(rsc);
-    } else {
-      view = v.findViewById(rsc);
-    }
-    if (view != null) {
-      view.setVisibility(View.VISIBLE);
-    }
-  }
-
-  /**
-   * Utility method to set a text.
-   */
-  void setText(View v, int rsc, CharSequence text) {
-    if (rsc == 0) {
-      return;
-    }
-    View view = v.findViewById(rsc);
-    if (view != null) {
-      TextView textView = (TextView) view;
-      textView.setText(text);
-      textView.setVisibility(View.VISIBLE);
-    }
-  }
-
-  /**
-   * Utility method to set a text.
-   */
-  void setText(View v, int rsc, int txtRsc) {
-    if (rsc == 0) {
-      return;
-    }
-    View view = v.findViewById(rsc);
-    if (view != null) {
-      TextView textView = (TextView) view;
-      if (txtRsc == 0) {
-        textView.setVisibility(View.GONE);
-      } else {
-        CharSequence text = getString(txtRsc);
-        textView.setText(text);
-        textView.setVisibility(View.VISIBLE);
-      }
-    }
-  }
-
-  /**
-   * Utility class to download an icon in the background.
-   * Once done ask the UI thread to update the icon.
-   */
-  class IconDownload implements Runnable {
-    private String mUrlString;
-
-    IconDownload(String url) {
-      mUrlString = url;
-    }
-
-    public void run() {
-      if (mUrlString == null) {
-        return;
-      }
-      try {
-        URL url = new URL(mUrlString);
-        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-        connection.setDoInput(true);
-        connection.connect();
-        int length = connection.getContentLength();
-        InputStream is = connection.getInputStream();
-        Bitmap customIcon = BitmapFactory.decodeStream(is);
-        if (customIcon != null) {
-          mIcon = customIcon;
-          mHandler.sendEmptyMessage(UPDATE_ICON);
-        }
-      } catch (ClassCastException e) {
-        Log.e(TAG, "Class cast exception (" + mUrlString + ")", e);
-      } catch (MalformedURLException e) {
-        Log.e(TAG, "Malformed url (" + mUrlString + ") ", e);
-      } catch (IOException e) {
-        Log.e(TAG, "Exception downloading icon (" + mUrlString + ") ", e);
-      }
-    }
-  }
-
-  /**
-   * Utility method to update the icon.
-   * Called on the UI thread.
-   */
-  public void updateIcon() {
-    if (mIcon == null) {
-      return;
-    }
-    View view = findViewById(R.id.origin_icon);
-    if (view != null) {
-      ImageView imageView = (ImageView) view;
-      imageView.setMaxHeight(MAX_ICON_SIZE);
-      imageView.setMaxWidth(MAX_ICON_SIZE);
-      imageView.setScaleType(ImageView.ScaleType.FIT_XY);
-      imageView.setImageBitmap(mIcon);
-      imageView.setVisibility(View.VISIBLE);
-    }
-  }
-
-  /**
-   * Utility method to download an icon from a url and set
-   * it to the GUI element R.id.origin_icon.
-   * It is used both in the shortcut dialog and the
-   * permission dialog.
-   * The actual download is done in the background via
-   * IconDownload; once the icon is downlowded the UI is updated
-   * via updateIcon().
-   * The icon size is included in the layout with the choosen
-   * size, although not displayed, to limit text reflow once
-   * the icon is received.
-   */
-  void downloadIcon(String url) {
-    if (url == null) {
-      return;
-    }
-    View view = findViewById(R.id.origin_icon);
-    if (view != null) {
-      view.setMinimumWidth(mChoosenIconSize);
-      view.setMinimumHeight(mChoosenIconSize);
-      view.setVisibility(View.INVISIBLE);
-    }
-    Thread thread = new Thread(new IconDownload(url));
-    thread.start();
-  }
-
-  /**
-   * Utility method that get the dialogMessage
-   * and icon and ask the setupDialog(message,icon)
-   * method to set the values.
-   */
-  public void setupDialog() {
-    TextView dialogMessage = null;
-    ImageView icon = null;
-
-    View view = findViewById(R.id.dialog_message);
-    if (view != null) {
-      dialogMessage = (TextView) view;
-    }
-
-    View iconView = findViewById(R.id.icon);
-    if (iconView != null) {
-      icon = (ImageView) iconView;
-    }
-
-    if ((dialogMessage != null) && (icon != null)) {
-      setupDialog(dialogMessage, icon);
-      dialogMessage.setVisibility(View.VISIBLE);
-    }
-  }
-
-  /*
-   * Set the message and icon of the dialog
-   */
-  public void setupDialog(TextView message, ImageView icon) {
-    message.setText(R.string.unrecognized_dialog_message);
-    icon.setImageResource(R.drawable.ic_dialog_menu_generic);
-    message.setVisibility(View.VISIBLE);
-  }
-
-  /**
-   * Setup the dialog
-   * By default, just display a simple message.
-   */
-  public void setup() {
-    setupButtons(0, 0, R.string.default_button);
-    setupDialog();
-  }
-
-  /**
-   * Method called when the back button is pressed,
-   * allowing the dialog to intercept the default behaviour.
-   */
-  public boolean handleBackButton() {
-    return false;
-  }
-
-  /**
-   * Returns the resource string of the notification displayed
-   * after the dialog. By default, does not return one.
-   */
-  public int notification() {
-    return 0;
-  }
-
-  /**
-   * If a secondary dialog (e.g. a confirmation dialog) is created,
-   * GearsNativeDialog will call this method.
-   */
-  public Dialog onCreateDialog(int id) {
-    // This should be redefined by subclasses as needed.
-    return null;
-  }
-
-}
diff --git a/src/com/android/browser/GearsNativeDialog.java b/src/com/android/browser/GearsNativeDialog.java
deleted file mode 100644
index b44ec2a..0000000
--- a/src/com/android/browser/GearsNativeDialog.java
+++ /dev/null
@@ -1,279 +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.app.Activity;
-import android.app.Dialog;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.Window;
-import android.widget.BaseAdapter;
-import android.widget.Toast;
-
-import android.webkit.gears.NativeDialog;
-
-import com.android.browser.GearsBaseDialog;
-import com.android.browser.GearsPermissionsDialog;
-import com.android.browser.GearsSettingsDialog;
-
-/**
- * Native dialog Activity used by gears
- * TODO: rename in GearsNativeDialogActivity
- * @hide
- */
-public class GearsNativeDialog extends Activity {
-
-  private static final String TAG = "GearsNativeDialog";
-
-  private String mDialogArguments;
-
-  private String mGearsVersion = null;
-
-  private boolean mDebug = false;
-
-  private int mDialogType;
-  private final int SETTINGS_DIALOG = 1;
-  private final int PERMISSION_DIALOG = 2;
-  private final int LOCATION_DIALOG = 3;
-
-  private final String VERSION_STRING = "version";
-  private final String SETTINGS_DIALOG_STRING = "settings_dialog";
-  private final String PERMISSION_DIALOG_STRING = "permissions_dialog";
-  private final String LOCATION_DIALOG_STRING = "locations_dialog";
-
-  private boolean mDialogDismissed = false;
-
-  GearsBaseDialog dialog;
-
-  // Handler for callbacks to the UI thread
-  final Handler mHandler = new Handler() {
-    public void handleMessage(Message msg) {
-      if (msg.what == GearsBaseDialog.NEW_ICON) {
-        BaseAdapter adapter = (BaseAdapter) msg.obj;
-        adapter.notifyDataSetChanged();
-      } else if (msg.what == GearsBaseDialog.UPDATE_ICON) {
-        dialog.updateIcon();
-      } else if (msg.what == GearsBaseDialog.ALWAYS_DENY) {
-        closeDialog(GearsBaseDialog.ALWAYS_DENY);
-      } else if (msg.what == GearsBaseDialog.ALLOW) {
-        closeDialog(GearsBaseDialog.ALLOW);
-      } else if (msg.what == GearsBaseDialog.DENY) {
-        closeDialog(GearsBaseDialog.DENY);
-      }
-      super.handleMessage(msg);
-    }
-  };
-
-  @Override
-  public void onCreate(Bundle icicle) {
-    getArguments();
-    if (mDialogType == SETTINGS_DIALOG) {
-      setTheme(android.R.style.Theme);
-    }
-    super.onCreate(icicle);
-    if (mDialogType != SETTINGS_DIALOG) {
-      requestWindowFeature(Window.FEATURE_NO_TITLE);
-      setContentView(R.layout.gears_dialog);
-    }
-
-    switch (mDialogType) {
-      case SETTINGS_DIALOG:
-        dialog = new GearsSettingsDialog(this, mHandler, mDialogArguments);
-        dialog.setGearsVersion(mGearsVersion);
-        break;
-      case PERMISSION_DIALOG:
-        dialog = new GearsPermissionsDialog(this, mHandler, mDialogArguments);
-        break;
-      case LOCATION_DIALOG:
-        dialog = new GearsPermissionsDialog(this, mHandler, mDialogArguments);
-        break;
-      default:
-        dialog = new GearsBaseDialog(this, mHandler, mDialogArguments);
-    }
-    dialog.setDebug(mDebug);
-    dialog.setup();
-  }
-
-  /**
-   * Get the arguments for the dialog
-   *
-   * The dialog needs a json string as an argument, as
-   * well as a dialogType. In debug mode the arguments
-   * are mocked.
-   */
-  private void getArguments() {
-    if (mDebug) {
-      mDialogType = LOCATION_DIALOG +1;
-      mockArguments();
-
-      return;
-    }
-
-    Intent intent = getIntent();
-    mDialogArguments = intent.getStringExtra("dialogArguments");
-    String dialogTypeString = intent.getStringExtra("dialogType");
-    if (dialogTypeString == null) {
-      return;
-    }
-
-    if (Browser.LOGV_ENABLED) {
-      Log.v(TAG, "dialogtype: " + dialogTypeString);
-    }
-
-    if (dialogTypeString.equalsIgnoreCase(SETTINGS_DIALOG_STRING)) {
-      mDialogType = SETTINGS_DIALOG;
-      mGearsVersion = intent.getStringExtra(VERSION_STRING);
-    } else if (dialogTypeString.equalsIgnoreCase(PERMISSION_DIALOG_STRING)) {
-      mDialogType = PERMISSION_DIALOG;
-    } else if (dialogTypeString.equalsIgnoreCase(LOCATION_DIALOG_STRING)) {
-      mDialogType = LOCATION_DIALOG;
-    }
-  }
-
-  /**
-   * Utility method for debugging the dialog.
-   *
-   * Set mock arguments.
-   */
-  private void mockArguments() {
-
-    String argumentsPermissions = "{ locale: \"en-US\", "
-        + "origin: \"http://www.google.com\", dialogType: \"localData\","
-        + "customIcon: \"http://google-gears.googlecode.com/"
-        + "svn/trunk/gears/test/manual/shortcuts/32.png\","
-        + "customName: \"My Application\","
-        + "customMessage: \"Press the button to enable my "
-        + "application to run offline!\" };";
-
-    String argumentsPermissions2 = "{ locale: \"en-US\", "
-        + "origin: \"http://www.google.com\", dialogType: \"localData\" };";
-
-    String argumentsLocation = "{ locale: \"en-US\", "
-        + "origin: \"http://www.google.com\", dialogType: \"locationData\","
-        + "customIcon: \"http://google-gears.googlecode.com/"
-        + "svn/trunk/gears/test/manual/shortcuts/32.png\","
-        + "customName: \"My Application\","
-        + "customMessage: \"Press the button to enable my "
-        + "application to run offline!\" };";
-
-    String argumentsSettings = "{ locale: \"en-US\", permissions: [ { "
-        + "name: \"http://www.google.com\", "
-        + "localStorage: { permissionState: 0 }, "
-        + "locationData: { permissionState: 1 } }, "
-        + "{ name: \"http://www.aaronboodman.com\", "
-        + "localStorage: { permissionState: 1 }, "
-        + "locationData: { permissionState: 2 } }, "
-        + "{ name: \"http://www.evil.org\", "
-        + "localStorage: { permissionState: 2 }, "
-        + "locationData: { permissionState: 2 } } ] }";
-
-    switch (mDialogType) {
-      case PERMISSION_DIALOG:
-        mDialogArguments = argumentsPermissions;
-        break;
-      case LOCATION_DIALOG:
-        mDialogArguments = argumentsLocation;
-        break;
-      case SETTINGS_DIALOG:
-        mDialogArguments = argumentsSettings;
-        break;
-    }
-  }
-
-  /**
-   * Close the dialog and set the return string value.
-   */
-  private void closeDialog(int closingType) {
-    String ret = dialog.closeDialog(closingType);
-
-    if (mDebug) {
-      Log.v(TAG, "closeDialog ret value: " + ret);
-    }
-
-    NativeDialog.closeDialog(ret);
-    notifyEndOfDialog();
-    finish();
-
-    // If the dialog sets a notification, we display it.
-    int notification = dialog.notification();
-    if (notification != 0) {
-      Toast toast = Toast.makeText(this, notification, Toast.LENGTH_LONG);
-      toast.setGravity(Gravity.BOTTOM, 0, 0);
-      toast.show();
-    }
-  }
-
-  @Override
-  public void onDestroy() {
-    super.onDestroy();
-    // In case we reach this point without
-    // notifying NativeDialog, we do it now.
-    if (!mDialogDismissed) {
-      notifyEndOfDialog();
-    }
-  }
-
-  @Override
-  public void onPause(){
-    super.onPause();
-    if (!mDialogDismissed) {
-      closeDialog(GearsBaseDialog.CANCEL);
-    }
-  }
-
-  /**
-   * Signal to NativeDialog that we are done.
-   */
-  private void notifyEndOfDialog() {
-    NativeDialog.signalFinishedDialog();
-    mDialogDismissed = true;
-  }
-
-  /**
-   * Intercepts the back key to immediately notify
-   * NativeDialog that we are done.
-   */
-  public boolean dispatchKeyEvent(KeyEvent event) {
-    if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK)
-      && (event.getAction() == KeyEvent.ACTION_DOWN)) {
-      if (!dialog.handleBackButton()) {
-        // if the dialog doesn't do anything with the back button
-        closeDialog(GearsBaseDialog.CANCEL);
-      }
-      return true; // event consumed
-    }
-    return super.dispatchKeyEvent(event);
-  }
-
-  /**
-   * If the dialog call showDialog() on ourself, we let
-   * it handle the creation of this secondary dialog.
-   * It is used in GearsSettingsDialog, to create the confirmation
-   * dialog when the user click on "Remove this site from Gears"
-   */
-  @Override
-  protected Dialog onCreateDialog(int id) {
-    return dialog.onCreateDialog(id);
-  }
-
-}
diff --git a/src/com/android/browser/GearsPermissions.java b/src/com/android/browser/GearsPermissions.java
deleted file mode 100644
index e48e045..0000000
--- a/src/com/android/browser/GearsPermissions.java
+++ /dev/null
@@ -1,196 +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.util.Log;
-
-import java.util.HashMap;
-import java.util.Iterator;
-
-/**
- * The permission mechanism works the following way:
- *
- * PermissionType allows to define a type of permission
- *   (e.g. localStorage/locationData), storing a name and a set of
- *   resource ids corresponding to the GUI resources.
- *
- * Permission defines an actual permission instance, with a type and a value.
- *
- * OriginPermissions holds an origin with a set of Permission objects
- */
-class GearsPermissions {
-
-  private static final String TAG = "GearsPermissions";
-
-  /**
-   * Defines a type of permission
-   *
-   * Store the permission's name (used in the json result)
-   * Graphically, each permission is a label followed by two radio buttons.
-   * We store the resources ids here.
-   */
-  public static class PermissionType {
-    public static final int PERMISSION_NOT_SET = 0;
-    public static final int PERMISSION_ALLOWED = 1;
-    public static final int PERMISSION_DENIED = 2;
-
-    String mName;
-    int mTitleRsc;
-    int mSubtitleOnRsc;
-    int mSubtitleOffRsc;
-
-    PermissionType(String name) {
-      mName = name;
-    }
-
-    public void setResources(int titleRsc,
-        int subtitleOnRsc, int subtitleOffRsc) {
-      mTitleRsc = titleRsc;
-      mSubtitleOnRsc = subtitleOnRsc;
-      mSubtitleOffRsc = subtitleOffRsc;
-    }
-
-    public String getName() {
-      return mName;
-    }
-
-    public int getTitleRsc() {
-      return mTitleRsc;
-    }
-
-    public int getSubtitleOnRsc() {
-      return mSubtitleOnRsc;
-    }
-
-    public int getSubtitleOffRsc() {
-      return mSubtitleOffRsc;
-    }
-
-  }
-
-  /**
-   * Simple class to store an instance of a permission
-   *
-   * i.e. a permission type and a value
-   * Value can be either PERMISSION_NOT_SET,
-   * PERMISSION_ALLOWED or PERMISSION_DENIED
-   * (defined in PermissionType).
-   */
-  public static class Permission {
-    PermissionType mType;
-    int mValue;
-
-    Permission(PermissionType type, int value) {
-      mType = type;
-      mValue = value;
-    }
-
-    Permission(PermissionType type) {
-      mType = type;
-      mValue = 0;
-    }
-
-    public PermissionType getType() {
-      return mType;
-    }
-
-    public void setValue(int value) {
-      mValue = value;
-    }
-
-    public int getValue() {
-      return mValue;
-    }
-  }
-
-  /**
-   * Interface used by the GearsNativeDialog implementation
-   * to listen to changes in the permissions.
-   */
-  public interface PermissionsChangesListener {
-    public boolean setPermission(PermissionType type, int perm);
-  }
-
-  /**
-   * Holds the model for an origin -- each origin has a set of
-   * permissions.
-   */
-  public static class OriginPermissions {
-    HashMap<PermissionType, Permission> mPermissions;
-    String mOrigin;
-    public static PermissionsChangesListener mListener;
-
-    public static void setListener(PermissionsChangesListener listener) {
-      mListener = listener;
-    }
-
-    OriginPermissions(String anOrigin) {
-      mOrigin = anOrigin;
-      mPermissions = new HashMap<PermissionType, Permission>();
-    }
-
-    OriginPermissions(OriginPermissions perms) {
-      mOrigin = perms.getOrigin();
-      mPermissions = new HashMap<PermissionType, Permission>();
-      HashMap<PermissionType, Permission> permissions = perms.getPermissions();
-      Iterator<PermissionType> iterator = permissions.keySet().iterator();
-      while (iterator.hasNext()) {
-        Permission permission = permissions.get(iterator.next());
-        int value = permission.getValue();
-        setPermission(permission.getType(), value);
-      }
-    }
-
-    public String getOrigin() {
-      return mOrigin;
-    }
-
-    public HashMap<PermissionType, Permission> getPermissions() {
-      return mPermissions;
-    }
-
-    public int getPermission(PermissionType type) {
-      return mPermissions.get(type).getValue();
-    }
-
-    public void setPermission(PermissionType type, int perm) {
-      if (mPermissions.get(type) == null) {
-        Permission permission = new Permission(type, perm);
-        mPermissions.put(type, permission);
-        return;
-      }
-
-      if (mListener != null) {
-        mListener.setPermission(type, perm);
-      }
-
-      mPermissions.get(type).setValue(perm);
-    }
-
-    public void print() {
-      Log.v(TAG, "Permissions for " + mOrigin);
-      Iterator<PermissionType> iterator = mPermissions.keySet().iterator();
-      while (iterator.hasNext()) {
-        Permission permission = mPermissions.get(iterator.next());
-        String name = permission.getType().getName();
-        int value = permission.getValue();
-        Log.v(TAG, "  " + name + ": " + value);
-      }
-    }
-  }
-
-}
diff --git a/src/com/android/browser/GearsPermissionsDialog.java b/src/com/android/browser/GearsPermissionsDialog.java
deleted file mode 100644
index dbec363..0000000
--- a/src/com/android/browser/GearsPermissionsDialog.java
+++ /dev/null
@@ -1,133 +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.app.Activity;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Gears permission dialog
- */
-class GearsPermissionsDialog extends GearsBaseDialog {
-
-  private static final String TAG = "GearsPermissionsDialog";
-
-  private String mDialogType;
-  private int mNotification = 0;
-
-  public GearsPermissionsDialog(Activity activity,
-                                Handler handler,
-                                String arguments) {
-    super (activity, handler, arguments);
-  }
-
-  public void setup() {
-    inflate(R.layout.gears_dialog_permission, R.id.panel_content);
-    setupButtons(R.string.permission_button_alwaysdeny,
-                 R.string.permission_button_allow,
-                 R.string.permission_button_deny);
-
-    try {
-      JSONObject json = new JSONObject(mDialogArguments);
-
-      if (json.has("dialogType")) {
-        mDialogType = json.getString("dialogType");
-        setupDialog();
-      }
-
-      if (!json.has("customName")) {
-        setLabel(json, "origin", R.id.origin_title);
-        View titleView = findViewById(R.id.origin_title);
-        if (titleView != null) {
-          TextView title = (TextView) titleView;
-          title.setGravity(Gravity.CENTER);
-        }
-      } else {
-        setLabel(json, "customName", R.id.origin_title);
-        setLabel(json, "origin", R.id.origin_subtitle);
-        setLabel(json, "customMessage", R.id.origin_message);
-      }
-
-      if (json.has("customIcon")) {
-        String iconUrl = json.getString("customIcon");
-        mChoosenIconSize = 32;
-        downloadIcon(iconUrl);
-      }
-
-      View msg = findViewById(R.id.permission_dialog_message);
-      if (msg != null) {
-        TextView dialogMessage = (TextView) msg;
-        if (mDialogType.equalsIgnoreCase(LOCAL_DATA_STRING)) {
-          dialogMessage.setText(R.string.query_data_message);
-        } else if (mDialogType.equalsIgnoreCase(LOCATION_DATA_STRING)) {
-          dialogMessage.setText(R.string.location_message);
-        }
-      }
-
-    } catch (JSONException e) {
-      Log.e(TAG, "JSON exception ", e);
-    }
-  }
-
-  public void setupDialog(TextView message, ImageView icon) {
-    if (mDialogType.equalsIgnoreCase(LOCAL_DATA_STRING)) {
-      message.setText(R.string.query_data_prompt);
-      icon.setImageResource(android.R.drawable.ic_popup_disk_full);
-    } else if (mDialogType.equalsIgnoreCase(LOCATION_DATA_STRING)) {
-      message.setText(R.string.location_prompt);
-      icon.setImageResource(R.drawable.ic_dialog_menu_generic);
-    }
-  }
-
-  public String closeDialog(int closingType) {
-    String ret = null;
-    switch (closingType) {
-      case ALWAYS_DENY:
-        ret = "{\"allow\": false, \"permanently\": true }";
-        if (mDialogType.equalsIgnoreCase(LOCAL_DATA_STRING)) {
-          mNotification = R.string.storage_notification_alwaysdeny;
-        } else if (mDialogType.equalsIgnoreCase(LOCATION_DATA_STRING)) {
-          mNotification = R.string.location_notification_alwaysdeny;
-        }
-        break;
-      case ALLOW:
-        ret = "{\"allow\": true, \"permanently\": true }";
-        if (mDialogType.equalsIgnoreCase(LOCAL_DATA_STRING)) {
-          mNotification = R.string.storage_notification;
-        } else if (mDialogType.equalsIgnoreCase(LOCATION_DATA_STRING)) {
-          mNotification = R.string.location_notification;
-        }
-        break;
-      case DENY:
-        ret = "{\"allow\": false, \"permanently\": false }";
-        break;
-    }
-    return ret;
-  }
-
-  public int notification() {
-    return mNotification;
-  }
-}
diff --git a/src/com/android/browser/GearsSettingsDialog.java b/src/com/android/browser/GearsSettingsDialog.java
deleted file mode 100644
index 5ea2342..0000000
--- a/src/com/android/browser/GearsSettingsDialog.java
+++ /dev/null
@@ -1,460 +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.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Handler;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.BaseAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.RadioButton;
-import android.widget.TextView;
-
-import com.android.browser.GearsPermissions.OriginPermissions;
-import com.android.browser.GearsPermissions.Permission;
-import com.android.browser.GearsPermissions.PermissionsChangesListener;
-import com.android.browser.GearsPermissions.PermissionType;
-
-import java.util.Vector;
-import java.util.List;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Gears Settings dialog
- */
-class GearsSettingsDialog extends GearsBaseDialog
-    implements PermissionsChangesListener {
-
-  private static final String TAG = "GearsPermissionsDialog";
-  private Vector<OriginPermissions> mSitesPermissions = null;
-  private Vector<OriginPermissions> mOriginalPermissions = null;
-  private Vector<OriginPermissions> mCurrentPermissions = null;
-
-  private Vector<PermissionType> mPermissions;
-  private static final int CONFIRMATION_REMOVE_DIALOG = 1;
-
-  // We declare the permissions globally to simplify the code
-  private final PermissionType LOCAL_STORAGE =
-      new PermissionType(LOCAL_STORAGE_STRING);
-  private final PermissionType LOCATION_DATA =
-      new PermissionType(LOCATION_DATA_STRING);
-
-  private boolean mChanges = false;
-
-  SettingsAdapter mListAdapter;
-
-  public GearsSettingsDialog(Activity activity,
-                             Handler handler,
-                             String arguments) {
-    super (activity, handler, arguments);
-    activity.setContentView(R.layout.gears_settings);
-  }
-
-  public void setup() {
-    // First let's add the permissions' resources
-    LOCAL_STORAGE.setResources(R.string.settings_storage_title,
-                               R.string.settings_storage_subtitle_on,
-                               R.string.settings_storage_subtitle_off);
-    LOCATION_DATA.setResources(R.string.settings_location_title,
-                               R.string.settings_location_subtitle_on,
-                               R.string.settings_location_subtitle_off);
-    // add the permissions to the list of permissions.
-    mPermissions = new Vector<PermissionType>();
-    mPermissions.add(LOCAL_STORAGE);
-    mPermissions.add(LOCATION_DATA);
-    OriginPermissions.setListener(this);
-
-
-    setupDialog();
-
-    // We manage the permissions using three vectors, mSitesPermissions,
-    // mOriginalPermissions and mCurrentPermissions.
-    // The dialog's arguments are parsed and a list of permissions is
-    // generated and stored in those three vectors.
-    // mOriginalPermissions is a separate copy and will not be modified;
-    // mSitesPermissions contains the current permissions _only_ --
-    // if an origin is removed, it is also removed from mSitesPermissions.
-    // Finally, mCurrentPermissions contains the current permissions and
-    // is a clone of mSitesPermissions, but removed sites aren't removed,
-    // their permissions are simply set to PERMISSION_NOT_SET. This
-    // allows us to easily generate the final difference between the
-    // original permissions and the final permissions, while directly
-    // using mSitesPermissions for the listView adapter (SettingsAdapter).
-
-    mSitesPermissions = new Vector<OriginPermissions>();
-    mOriginalPermissions = new Vector<OriginPermissions>();
-
-    try {
-      JSONObject json = new JSONObject(mDialogArguments);
-      if (json.has("permissions")) {
-        JSONArray jsonArray = json.getJSONArray("permissions");
-        for (int i = 0; i < jsonArray.length(); i++) {
-          JSONObject infos = jsonArray.getJSONObject(i);
-          String name = null;
-          int localStorage = PermissionType.PERMISSION_NOT_SET;
-          int locationData = PermissionType.PERMISSION_NOT_SET;
-          if (infos.has("name")) {
-            name = infos.getString("name");
-          }
-          if (infos.has(LOCAL_STORAGE_STRING)) {
-            JSONObject perm = infos.getJSONObject(LOCAL_STORAGE_STRING);
-            if (perm.has("permissionState")) {
-              localStorage = perm.getInt("permissionState");
-            }
-          }
-          if (infos.has(LOCATION_DATA_STRING)) {
-            JSONObject perm = infos.getJSONObject(LOCATION_DATA_STRING);
-            if (perm.has("permissionState")) {
-              locationData = perm.getInt("permissionState");
-            }
-          }
-          OriginPermissions perms = new OriginPermissions(name);
-          perms.setPermission(LOCAL_STORAGE, localStorage);
-          perms.setPermission(LOCATION_DATA, locationData);
-
-          mSitesPermissions.add(perms);
-          mOriginalPermissions.add(new OriginPermissions(perms));
-        }
-      }
-    } catch (JSONException e) {
-      Log.e(TAG, "JSON exception ", e);
-    }
-    mCurrentPermissions = (Vector<OriginPermissions>)mSitesPermissions.clone();
-
-    View listView = findViewById(R.id.sites_list);
-    if (listView != null) {
-      ListView list = (ListView) listView;
-      mListAdapter = new SettingsAdapter(mActivity, mSitesPermissions);
-      list.setAdapter(mListAdapter);
-      list.setScrollBarStyle(android.view.View.SCROLLBARS_OUTSIDE_INSET);
-      list.setOnItemClickListener(mListAdapter);
-    }
-    if (mDebug) {
-      printPermissions();
-    }
-  }
-
-  private void setMainTitle() {
-    String windowTitle = mActivity.getString(R.string.pref_extras_gears_settings);
-    mActivity.setTitle(windowTitle);
-  }
-
-  public void setupDialog() {
-    setMainTitle();
-  }
-
-  /**
-   * GearsPermissions.PermissionsChangesListener delegate
-   */
-  public boolean setPermission(PermissionType type, int perm) {
-    if (mChanges == false) {
-      mChanges = true;
-    }
-    return mChanges;
-  }
-
-  public boolean handleBackButton() {
-    return mListAdapter.backButtonPressed();
-  }
-
-  /**
-   * We use this to create a confirmation dialog when the user
-   * clicks on "remove this site from gears"
-   */
-  public Dialog onCreateDialog(int id) {
-    return new AlertDialog.Builder(mActivity)
-        .setTitle(R.string.settings_confirmation_remove_title)
-        .setMessage(R.string.settings_confirmation_remove)
-        .setPositiveButton(android.R.string.ok,
-                           new AlertDialog.OnClickListener() {
-          public void onClick(DialogInterface dlg, int which) {
-            mListAdapter.removeCurrentSite();
-          }
-        })
-        .setNegativeButton(android.R.string.cancel, null)
-        .setIcon(android.R.drawable.ic_dialog_alert)
-        .create();
-  }
-
-  /**
-   * Adapter class for the list view in the settings dialog
-   *
-   * We first display a list of all the origins (sites), or
-   * a message saying that no permission is set if the list is empty.
-   * When the user click on one of the origin, we then display
-   * the list of the permissions existing for that origin.
-   * Each permission can be either allowed or denied by clicking
-   * on the checkbox.
-   * The last row is a special case, allowing to remove the entire origin.
-   */
-  class SettingsAdapter extends BaseAdapter
-      implements AdapterView.OnItemClickListener {
-    private Activity mContext;
-    private List mItems;
-    private OriginPermissions mCurrentSite;
-    private Vector mCurrentPermissions;
-    private int MAX_ROW_HEIGHT = 64;
-
-    SettingsAdapter(Activity context, List items) {
-      mContext = context;
-      mItems = items;
-      mCurrentSite = null;
-    }
-
-    public int getCount() {
-      if (mCurrentSite == null) {
-        int size = mItems.size();
-        if (size == 0) {
-          return 1;
-        } else {
-          return size;
-        }
-      }
-      return mCurrentPermissions.size() + 1;
-    }
-
-    public long getItemId(int position) {
-      return position;
-    }
-
-    private String shortName(String url) {
-        // We remove the http and https prefix
-        if (url.startsWith("http://")) {
-          return url.substring(7);
-        }
-        if (url.startsWith("https://")) {
-          return url.substring(8);
-        }
-        return url;
-    }
-
-    public Object getItem(int position) {
-      if (mCurrentSite == null) {
-        if (mItems.size() == 0) {
-          return null;
-        } else {
-          return mItems.get(position);
-        }
-      }
-      return mCurrentPermissions.get(position);
-    }
-
-    public View getView(int position, View convertView, ViewGroup parent) {
-      View row = convertView;
-      if (row == null) { // no cached view, we create one
-        LayoutInflater inflater = (LayoutInflater) getSystemService(
-            Context.LAYOUT_INFLATER_SERVICE);
-        row = inflater.inflate(R.layout.gears_settings_row, null);
-      }
-      row.setMinimumHeight(MAX_ROW_HEIGHT);
-
-      if (mCurrentSite == null) {
-        if (mItems.size() == 0) {
-          hideView(row, R.id.title);
-          hideView(row, R.id.subtitle);
-          hideView(row, R.id.checkbox);
-          hideView(row, R.id.icon);
-          setText(row, R.id.info, R.string.settings_empty);
-        } else {
-          hideView(row, R.id.subtitle);
-          hideView(row, R.id.info);
-          hideView(row, R.id.checkbox);
-          OriginPermissions perms = (OriginPermissions) mItems.get(position);
-          setText(row, R.id.title, shortName(perms.getOrigin()));
-          showView(row, R.id.icon);
-        }
-      } else {
-        if (position == getCount() - 1) {
-          // last position: "remove this site from gears"
-          hideView(row, R.id.subtitle);
-          hideView(row, R.id.info);
-          hideView(row, R.id.checkbox);
-          hideView(row, R.id.icon);
-          setText(row, R.id.title, R.string.settings_remove_site);
-        } else {
-          hideView(row, R.id.info);
-          hideView(row, R.id.icon);
-          showView(row, R.id.checkbox);
-
-          PermissionType type =
-              (PermissionType) mCurrentPermissions.get(position);
-          setText(row, R.id.title, type.getTitleRsc());
-
-          View checkboxView = row.findViewById(R.id.checkbox);
-          if (checkboxView != null) {
-            CheckBox checkbox = (CheckBox) checkboxView;
-            int perm = mCurrentSite.getPermission(type);
-            if (perm == PermissionType.PERMISSION_DENIED) {
-              setText(row, R.id.subtitle, type.getSubtitleOffRsc());
-              checkbox.setChecked(false);
-            } else {
-              setText(row, R.id.subtitle, type.getSubtitleOnRsc());
-              checkbox.setChecked(true);
-            }
-          }
-        }
-      }
-      return row;
-    }
-
-    public void removeCurrentSite() {
-      mCurrentSite.setPermission(LOCAL_STORAGE,
-                                 PermissionType.PERMISSION_NOT_SET);
-      mCurrentSite.setPermission(LOCATION_DATA,
-                                 PermissionType.PERMISSION_NOT_SET);
-      mSitesPermissions.remove(mCurrentSite);
-      mCurrentSite = null;
-      setMainTitle();
-      notifyDataSetChanged();
-    }
-
-    public void onItemClick(AdapterView<?> parent,
-                            View view,
-                            int position,
-                            long id) {
-      if (mItems.size() == 0) {
-        return;
-      }
-      if (mCurrentSite == null) {
-         mCurrentSite = (OriginPermissions) mItems.get(position);
-         mCurrentPermissions = new Vector();
-         for (int i = 0; i < mPermissions.size(); i++) {
-           PermissionType type = mPermissions.get(i);
-           int perm = mCurrentSite.getPermission(type);
-           if (perm != PermissionType.PERMISSION_NOT_SET) {
-             mCurrentPermissions.add(type);
-           }
-         }
-         mContext.setTitle(shortName(mCurrentSite.getOrigin()));
-      } else {
-        if (position == getCount() - 1) { // last item (remove site)
-          // Ask the user to confirm
-          // If yes, removeCurrentSite() will be called via the dialog callback.
-          mActivity.showDialog(CONFIRMATION_REMOVE_DIALOG);
-        } else {
-          PermissionType type =
-              (PermissionType) mCurrentPermissions.get(position);
-          if (mCurrentSite.getPermission(type) ==
-              PermissionType.PERMISSION_ALLOWED) {
-            mCurrentSite.setPermission(type, PermissionType.PERMISSION_DENIED);
-          } else {
-            mCurrentSite.setPermission(type, PermissionType.PERMISSION_ALLOWED);
-          }
-        }
-      }
-      notifyDataSetChanged();
-    }
-
-    public boolean backButtonPressed() {
-      if (mCurrentSite != null) { // we intercept the back button
-        mCurrentSite = null;
-        setMainTitle();
-        notifyDataSetChanged();
-        return true;
-      }
-      return false;
-    }
-
-  }
-
-  /**
-   * Utility method used in debug mode to print the list of
-   * permissions (original values and current values).
-   */
-  public void printPermissions() {
-    Log.v(TAG, "Original Permissions: ");
-    for (int i = 0; i < mOriginalPermissions.size(); i++) {
-      OriginPermissions p = mOriginalPermissions.get(i);
-      p.print();
-    }
-    Log.v(TAG, "Current Permissions: ");
-    for (int i = 0; i < mSitesPermissions.size(); i++) {
-      OriginPermissions p = mSitesPermissions.get(i);
-      p.print();
-    }
-  }
-
-  /**
-   * Computes the difference between the original permissions and the
-   * current ones. Returns a json-formatted string.
-   * It is used by the Settings dialog.
-   */
-  public String computeDiff(boolean modif) {
-    String ret = null;
-    try {
-      JSONObject results = new JSONObject();
-      JSONArray permissions = new JSONArray();
-
-      for (int i = 0; modif && i < mOriginalPermissions.size(); i++) {
-        OriginPermissions original = mOriginalPermissions.get(i);
-        OriginPermissions current = mCurrentPermissions.get(i);
-        JSONObject permission = new JSONObject();
-        boolean modifications = false;
-
-        for (int j = 0; j < mPermissions.size(); j++) {
-          PermissionType type = mPermissions.get(j);
-
-          if (current.getPermission(type) != original.getPermission(type)) {
-            JSONObject state = new JSONObject();
-            state.put("permissionState", current.getPermission(type));
-            permission.put(type.getName(), state);
-            modifications = true;
-          }
-        }
-
-        if (modifications) {
-          permission.put("name", current.getOrigin());
-          permissions.put(permission);
-        }
-      }
-      results.put("modifiedOrigins", permissions);
-      ret = results.toString();
-    } catch (JSONException e) {
-      Log.e(TAG, "JSON exception ", e);
-    }
-    return ret;
-  }
-
-  public String closeDialog(int closingType) {
-    String ret = computeDiff(mChanges);
-
-    if (mDebug) {
-      printPermissions();
-    }
-
-    return ret;
-  }
-
-}
diff --git a/src/com/android/browser/GeolocationPermissionsPrompt.java b/src/com/android/browser/GeolocationPermissionsPrompt.java
new file mode 100755
index 0000000..a21bc3e
--- /dev/null
+++ b/src/com/android/browser/GeolocationPermissionsPrompt.java
@@ -0,0 +1,120 @@
+/*
+ * 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.content.Context;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.webkit.WebView;
+import android.webkit.GeolocationPermissions;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class GeolocationPermissionsPrompt extends LinearLayout {
+    private LinearLayout mInner;
+    private TextView mMessage;
+    private Button mShareButton;
+    private Button mDontShareButton;
+    private CheckBox mRemember;
+    private GeolocationPermissions.Callback mCallback;
+    private String mOrigin;
+
+    public GeolocationPermissionsPrompt(Context context) {
+        this(context, null);
+    }
+
+    public GeolocationPermissionsPrompt(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        LayoutInflater factory = LayoutInflater.from(context);
+        factory.inflate(R.layout.geolocation_permissions_prompt, this);
+
+        mInner = (LinearLayout) findViewById(R.id.inner);
+        mMessage = (TextView) findViewById(R.id.message);
+        mShareButton = (Button) findViewById(R.id.share_button);
+        mDontShareButton = (Button) findViewById(R.id.dont_share_button);
+        mRemember = (CheckBox) findViewById(R.id.remember);
+        setButtonClickListeners();
+    }
+
+    /**
+     * Shows the prompt for the given origin. When the user clicks on one of
+     * the buttons, the supplied callback is be called.
+     */
+    public void show(String origin, GeolocationPermissions.Callback callback) {
+        mOrigin = origin;
+        mCallback = callback;
+        Uri uri = Uri.parse(mOrigin);
+        setMessage("http".equals(uri.getScheme()) ?  mOrigin.substring(7) : mOrigin);
+        // The checkbox should always be intially checked.
+        mRemember.setChecked(true);
+        showDialog(true);
+    }
+
+    /**
+     * Hides the prompt.
+     */
+    public void hide() {
+        showDialog(false);
+    }
+
+    /**
+     * Sets the on click listeners for the buttons.
+     */
+    private void setButtonClickListeners() {
+        final GeolocationPermissionsPrompt me = this;
+        mShareButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                me.handleButtonClick(true);
+            }
+        });
+        mDontShareButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                me.handleButtonClick(false);
+            }
+        });
+    }
+
+    /**
+     * Handles a click on one the buttons by invoking the callback.
+     */
+    private void handleButtonClick(boolean allow) {
+        boolean remember = mRemember.isChecked();
+        showDialog(false);
+        mCallback.invoke(mOrigin, allow, remember);
+    }
+
+    /**
+     * Sets the prompt's message.
+     */
+    private void setMessage(CharSequence origin) {
+        mMessage.setText(String.format(
+            getResources().getString(R.string.geolocation_permissions_prompt_message),
+            origin));
+    }
+
+    /**
+     * Shows or hides the prompt.
+     */
+    private void showDialog(boolean shown) {
+        mInner.setVisibility(shown ? View.VISIBLE : View.GONE);
+    }
+}
diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java
index 55e43f0..51cb026 100644
--- a/src/com/android/browser/HistoryItem.java
+++ b/src/com/android/browser/HistoryItem.java
@@ -17,23 +17,13 @@
  
 package com.android.browser;
 
-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.net.Uri;
 import android.provider.Browser;
-import android.util.Log;
 import android.view.View;
-import android.webkit.WebIconDatabase;
 import android.widget.CompoundButton;
 import android.widget.ImageView;
 import android.widget.TextView;
-import android.widget.Toast;
-
-import java.util.Date;
 
 /**
  *  Layout representing a history item in the classic history viewer.
@@ -54,61 +44,18 @@
         mListener = new CompoundButton.OnCheckedChangeListener() {
             public void onCheckedChanged(CompoundButton buttonView,
                     boolean isChecked) {
-                ContentResolver cr = mContext.getContentResolver();
-                Cursor cursor = cr.query(
-                        Browser.BOOKMARKS_URI,
-                        Browser.HISTORY_PROJECTION,
-                        "url = ?",
-                        new String[] { mUrl },
-                        null);
-                boolean first = cursor.moveToFirst();
-                // Should be in the database no matter what
-                if (!first) {
-                    throw new AssertionError("URL is not in the database!");
-                }
                 if (isChecked) {
-                    // Add to bookmarks
-                    // FIXME: Share code with AddBookmarkPage.java
-                    ContentValues map = new ContentValues();
-                    map.put(Browser.BookmarkColumns.CREATED,
-                            new Date().getTime());
-                    map.put(Browser.BookmarkColumns.TITLE, getName());
-                    map.put(Browser.BookmarkColumns.BOOKMARK, 1);
-                    try {
-                        cr.update(Browser.BOOKMARKS_URI, map, 
-                                "_id = " + cursor.getInt(0), null);
-                    } catch (IllegalStateException e) {
-                        Log.e("HistoryItem", "no database!");
-                    }
-                    WebIconDatabase.getInstance().retainIconForPageUrl(mUrl);
-                    // catch IllegalStateException?
-                    Toast.makeText(mContext, R.string.added_to_bookmarks,
-                            Toast.LENGTH_LONG).show();
+                    Bookmarks.addBookmark(mContext,
+                            mContext.getContentResolver(), mUrl, getName(), null, true);
                 } else {
-                    // Remove from bookmarks
-                    // FIXME: This code should be shared with
-                    // BrowserBookmarksAdapter.java
-                    WebIconDatabase.getInstance().releaseIconForPageUrl(mUrl);
-                    Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
-                            cursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
-                    // 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("HistoryItem", "no database!");
-                    }
-                    Toast.makeText(mContext, R.string.removed_from_bookmarks,
-                            Toast.LENGTH_LONG).show();
+                    Bookmarks.removeFromBookmarks(mContext,
+                            mContext.getContentResolver(), mUrl, getName());
                 }
-                cursor.deactivate();
             }
         };
     }
     
-    void copyTo(HistoryItem item) {
+    /* package */ void copyTo(HistoryItem item) {
         item.mTextView.setText(mTextView.getText());
         item.mUrlText.setText(mUrlText.getText());
         item.setIsBookmark(mStar.isChecked());
@@ -116,10 +63,17 @@
     }
 
     /**
+     * Whether or not this item represents a bookmarked site
+     */
+    /* package */ boolean isBookmark() {
+        return mStar.isChecked();
+    }
+
+    /**
      *  Set whether or not this represents a bookmark, and make sure the star
      *  behaves appropriately.
      */
-    void setIsBookmark(boolean isBookmark) {
+    /* package */ void setIsBookmark(boolean isBookmark) {
         mStar.setOnCheckedChangeListener(null);
         mStar.setChecked(isBookmark);
         mStar.setOnCheckedChangeListener(mListener);
diff --git a/src/com/android/browser/ImageAdapter.java b/src/com/android/browser/ImageAdapter.java
deleted file mode 100644
index f95753a..0000000
--- a/src/com/android/browser/ImageAdapter.java
+++ /dev/null
@@ -1,265 +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.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.DataSetObserver;
-import android.graphics.Color;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.LayoutInflater;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-
-/**
- * Adapter used by ImageGrid.
- */
-public class ImageAdapter implements ListAdapter {
-    
-    ArrayList<TabControl.Tab> mItems;  // Items shown in the grid
-    private ArrayList<DataSetObserver> mDataObservers; // Data change listeners
-    private Context mContext;  // Context to use to inflate views
-    private boolean mMaxedOut;
-    private ImageGrid mImageGrid;
-    private boolean mIsLive;
-    private int mTabHeight;
-
-    ImageAdapter(Context context, ImageGrid grid, boolean live) {
-        mContext = context;
-        mIsLive = live;
-        mItems = new ArrayList<TabControl.Tab>();
-        mImageGrid = grid;
-        mDataObservers = new ArrayList<DataSetObserver>();
-    }
-
-    void heightChanged(int newHeight) {
-        mTabHeight = newHeight;
-    }
-
-    /**
-     *  Whether the adapter is at its limit, determined by TabControl.MAX_TABS
-     *
-     *  @return True if the number of Tabs represented in this Adapter is at its
-     *          maximum.
-     */
-    public boolean maxedOut() {
-        return mMaxedOut;
-    }
-
-    /**
-     * Clear the internal WebViews and remove their picture listeners.
-     */
-    public void clear() {
-        mItems.clear();
-        notifyObservers();
-    }
-
-    /**
-     * Add a new window web page to the grid
-     * 
-     * @param t The tab to display
-     */
-    public void add(TabControl.Tab t) {
-        if (mMaxedOut) {
-            return;
-        }
-        mItems.add(t);
-        notifyObservers();
-        if (mItems.size() == TabControl.MAX_TABS) {
-            mMaxedOut = true;
-        }
-    }
-    
-    /**
-     * Remove a window from the list. At this point, the window
-     * has already gone. It just needs to be removed from the screen
-     * 
-     * @param index window to remove
-     */
-    public void remove(int index) {
-        if (index >= 0 && index < mItems.size()) {
-            mItems.remove(index);
-            notifyObservers();
-            mMaxedOut = false;
-        }
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.ListAdapter#areAllItemsSelectable()
-     */
-    public boolean areAllItemsEnabled() {
-        return true;
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.ListAdapter#isSelectable(int)
-     */
-    public boolean isEnabled(int position) {
-        if (position >= 0 && position <= mItems.size()) {
-            return true;
-        }
-        return false;
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#getCount()
-     */
-    public int getCount() {
-        // Include the New Window button if we have not reached the tab limit
-        if (!mMaxedOut) {
-            return mItems.size()+1;
-        }
-        return mItems.size();
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#getItem(int)
-     */
-    public Object getItem(int position) {
-        if (!mMaxedOut) {
-            if (0 == position) {
-                return null;
-            }
-            return mItems.get(position);
-        }
-        return mItems.get(position);
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#getItemId(int)
-     */
-    public long getItemId(int position) {
-        return position;
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#getView(int, android.view.View, 
-     * android.view.ViewGroup)
-     */
-    public View getView(int position, View convertView, ViewGroup parent) {
-        View v = null;
-        if (convertView != null) {
-            v = convertView;
-        } else {
-            LayoutInflater factory = LayoutInflater.from(mContext);
-            v = factory.inflate(R.layout.tabitem, null);
-        }
-        FakeWebView img = (FakeWebView) v.findViewById(R.id.icon);
-        ImageView close = (ImageView) v.findViewById(R.id.close);
-        TextView tv = (TextView) v.findViewById(R.id.label);
-
-        // position needs to be in the range of Tab indices.
-        if (!mMaxedOut) {
-            position--;
-        }
-
-        // Create the View for actual tabs
-        if (position != ImageGrid.NEW_TAB) {
-            TabControl.Tab t = mItems.get(position);
-            img.setTab(t);
-            tv.setText(t.getTitle());
-            // Do not put the 'X' if the tab picker isn't "live" (meaning the
-            // user cannot click on a tab)
-            if (!mIsLive) {
-                close.setVisibility(View.GONE);
-            } else {
-                close.setVisibility(View.VISIBLE);
-                final int pos = position;
-                close.setOnClickListener(new View.OnClickListener() {
-                        public void onClick(View v) {
-                            ImageAdapter.this.confirmClose(pos);
-                        }
-                    });
-            }
-        } else {
-            img.setBackgroundColor(Color.BLACK);
-            img.setImageResource(R.drawable.ic_new_window);
-            img.setScaleType(ImageView.ScaleType.CENTER);
-            img.setPadding(0, 0, 0, 34);
-            tv.setText(R.string.new_window);
-            close.setVisibility(View.GONE);
-        }
-        ViewGroup.LayoutParams lp = img.getLayoutParams();
-        if (lp.height != mTabHeight) {
-            lp.height = mTabHeight;
-            img.requestLayout();
-        }
-        return v;
-    }
-
-    /*
-     * Pop a confirmation dialog to the user asking if they want to close this
-     * tab.
-     */
-    private void confirmClose(final int position) {
-        final ImageGrid.Listener l = mImageGrid.getListener();
-        if (l == null) {
-            return;
-        }
-        l.remove(position);
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#registerDataSetObserver(android.database.DataSetObserver)
-     */
-    public void registerDataSetObserver(DataSetObserver observer) {
-        mDataObservers.add(observer);
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#hasStableIds()
-     */
-    public boolean hasStableIds() {
-        return true;
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#unregisterDataSetObserver(android.database.DataSetObserver)
-     */
-    public void unregisterDataSetObserver(DataSetObserver observer) {
-        mDataObservers.remove(observer);
-    }
-
-    /**
-     * Notify all the observers that a change has happened.
-     */
-    void notifyObservers() {
-        for (DataSetObserver observer : mDataObservers) {
-            observer.onChanged();
-        }
-    }
-
-    public int getItemViewType(int position) {
-        return 0;
-    }
-
-    public int getViewTypeCount() {
-        return 1;
-    }
-
-    public boolean isEmpty() {
-        return getCount() == 0;
-    }
-}
diff --git a/src/com/android/browser/ImageGrid.java b/src/com/android/browser/ImageGrid.java
deleted file mode 100644
index 9967f36..0000000
--- a/src/com/android/browser/ImageGrid.java
+++ /dev/null
@@ -1,232 +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.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.KeyEvent;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnCreateContextMenuListener;
-import android.webkit.WebView;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.GridView;
-
-/**
- * This class implements a Grid layout of Views for the Tab picker.
- */
-class ImageGrid extends GridView implements OnItemClickListener, 
-        OnCreateContextMenuListener  {
-    
-    private Listener     mListener;
-    private ImageAdapter mAdapter;
-    private boolean      mIsLive;
-    private static final int SPACING = 10;
-    public static final int CANCEL  = -99;
-    public static final int NEW_TAB = -1;
-
-    /**
-     * Constructor
-     * @param context Context to use when inflating resources.
-     * @param live  TRUE if the view can accept touch or click
-     * @param l     Listener to respond to clicks etc.
-     */
-    public ImageGrid(Context context, boolean live, Listener l) {
-        super(context);
-
-        mIsLive = live;
-        if (live) {
-            setFocusable(true);
-            setFocusableInTouchMode(true);
-            setOnItemClickListener(this);
-            setOnCreateContextMenuListener(this);
-        }
-        mListener = l;
-
-        mAdapter = new ImageAdapter(context, this, live);
-        setAdapter(mAdapter);
-
-        setBackgroundColor(0xFF000000);
-
-        setVerticalSpacing(SPACING);
-        setHorizontalSpacing(SPACING);
-        setNumColumns(2);
-        setStretchMode(GridView.STRETCH_COLUMN_WIDTH);
-        setSelector(android.R.drawable.gallery_thumb);
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        // We always consume the BACK key even if mListener is null or the
-        // ImageGrid is not "live." This prevents crashes during tab animations
-        // if the user presses BACK.
-        if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
-                (event.getKeyCode() == KeyEvent.KEYCODE_BACK)) {
-            if (mListener != null && mIsLive) {
-                mListener.onClick(CANCEL);
-                invalidate();
-            }
-            return true;
-        }
-        return super.dispatchKeyEvent(event);
-    }
-    
-    /**
-     * Called by BrowserActivity to add a new window to the tab picker.
-     * This does not happen dynamically, this only happens during view
-     * setup.
-     * 
-     * @param v Webview of the tab to add
-     * @param name Web page title
-     * @param url URL of the webpage
-     */
-    public void add(TabControl.Tab t) {
-        mAdapter.add(t);
-    }
-
-    /**
-     * Called by BrowserActivity when a window has been removed from the
-     * tab list.
-     * 
-     * @param index Window to remove, from 0 to MAX_TABS-1
-     */
-    public void remove(int index) {
-        if (Browser.DEBUG && (index < 0 || index >= TabControl.MAX_TABS)) {
-            throw new AssertionError();
-        }
-        mAdapter.remove(index);
-    }
-
-    /**
-     * Request focus to initially set to a particular tab. 
-     *
-     * @param startingIndex This is a Tab index from 0 - MAX_TABS-1 and does not
-     *                      include the "New Tab" cell.
-     */
-    public void setCurrentIndex(int startingIndex) {
-        if (!mAdapter.maxedOut()) {
-            startingIndex++;
-        }
-        setSelection(startingIndex);
-    }
-
-    public Listener getListener() {
-        return mListener;
-    }
-
-    public void setListener(Listener l) {
-        mListener = l;
-    }
-
-    /**
-     * Return true if the ImageGrid is live. This means that tabs can be chosen
-     * and the menu can be invoked.
-     */
-    public boolean isLive() {
-        return mIsLive;
-    }
-
-    /**
-     * Do some internal cleanup of the ImageGrid's adapter.
-     */
-    public void clear() {
-        mAdapter.clear();
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView, android.view.View, int, long)
-     */
-    public void onItemClick(AdapterView parent, View v, int position, long id) {
-        if (!mAdapter.maxedOut()) {
-            position--;
-        }
-        // Position will be -1 for the "New Tab" cell.
-        if (mListener != null) {
-            mListener.onClick(position);
-        }
-    }
-    
-    /* (non-Javadoc)
-     * @see android.view.View.OnCreateContextMenuListener#onCreateContextMenu(android.view.ContextMenu, android.view.View, java.lang.Object)
-     */
-    public void onCreateContextMenu(ContextMenu menu, View v, 
-            ContextMenuInfo menuInfo) {
-        // Do not create the context menu if there is no listener or the Tab
-        // overview is not "live."
-        if (mListener == null || !mIsLive) {
-            return;
-        }
-        AdapterView.AdapterContextMenuInfo info = 
-                (AdapterView.AdapterContextMenuInfo) menuInfo;
-        boolean maxed = mAdapter.maxedOut();
-        if (info.position > 0 || maxed) {
-            MenuInflater inflater = new MenuInflater(mContext);
-            inflater.inflate(R.menu.tabscontext, menu);
-            int position = info.position;
-            if (!maxed) {
-                position--;
-            }
-            menu.setHeaderTitle(mAdapter.mItems.get(position).getTitle());
-        }
-    }
-
-    // convert a context menu position to an actual tab position. Since context
-    // menus are not created for the "New Tab" cell, this will always return a
-    // valid tab position.
-    public int getContextMenuPosition(MenuItem menu) {
-        AdapterView.AdapterContextMenuInfo info =
-                (AdapterView.AdapterContextMenuInfo) menu.getMenuInfo();
-        int pos = info.position;
-        if (!mAdapter.maxedOut()) {
-            pos--;
-        }
-        return pos;
-    }
-    
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        // Called when our orientation changes. Tell the adapter about the new
-        // size. Compute the individual tab height by taking the grid height
-        // and subtracting the SPACING. Then subtract the list padding twice
-        // (once for each tab on screen) and divide the remaining height by 2.
-        int tabHeight = (h - SPACING
-                - 2 * (getListPaddingTop() + getListPaddingBottom())) / 2;
-        mAdapter.heightChanged(tabHeight);
-        super.onSizeChanged(w, h, oldw, oldh);
-    }
-
-    /**
-     * Listener to be notified by behavior of ImageGrid.
-     */
-    public interface Listener {
-        /**
-         * Called when enter is pressed on the list.
-         * @param position  The index of the selected image when
-         *                  enter is pressed.
-         */
-        void onClick(int position);
-
-        /**
-         * Called when remove is called on the grid.
-         */
-        void remove(int position);
-    }
-
-}
diff --git a/src/com/android/browser/KeyTracker.java b/src/com/android/browser/KeyTracker.java
deleted file mode 100644
index 344e4f8..0000000
--- a/src/com/android/browser/KeyTracker.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2006 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.view.KeyEvent;
-import android.view.ViewConfiguration;
-
-class KeyTracker {
-
-    public enum Stage {
-        DOWN,           //!< the key has just been pressed
-        SHORT_REPEAT,   //!< repeated key, but duration is under the long-press threshold
-        LONG_REPEAT,    //!< repeated key, but duration is over the long-press threshold
-        UP              //!< the key is being released
-    }
-    
-    public enum State {
-        KEEP_TRACKING,  //!< return this to continue to track the key
-        DONE_TRACKING,  //!< return this if you handled the key, but need not track it anymore
-        NOT_TRACKING    //!< return this if you will not handle this key
-    }
-    
-    public interface OnKeyTracker {
-
-        /** Called whenever there is a key event [down, short/long repeat, up]
-            @param keyCode  The current keyCode (see KeyEvent class)
-            @param msg      The message associated with the keyCode
-            @maram stage    The state the key press is in [down, short/long repeat, up]
-            @param duration The number of milliseconds since this key was initially pressed
-            @return your state after seeing the key. If you return DONE_TRACKING or NOT_TRACKING,
-                    you will not be called again for the lifetime of this key event.
-        */
-        public State onKeyTracker(int keyCode, KeyEvent event, Stage stage, int duration);
-    }
-    
-    public KeyTracker(OnKeyTracker tracker) {
-        mTracker = tracker;
-    }
-    
-    public boolean doKeyDown(int keyCode, KeyEvent event) {
-        long now = System.currentTimeMillis();
-        Stage stage = null;
-
-        // check if its a new/different key
-        if (mKeyCode != keyCode || event.getRepeatCount() == 0) {
-            mKeyCode = keyCode;
-            mStartMS = now;
-            stage = Stage.DOWN;
-        }
-        else if (mState == State.KEEP_TRACKING) {
-            stage = (now - mStartMS) >= LONG_PRESS_DURATION_MS ? Stage.LONG_REPEAT : Stage.SHORT_REPEAT;
-        }
-
-        if (stage != null) {
-            mEvent = event;        
-            callTracker(stage, now);
-        }
-
-        return mState != State.NOT_TRACKING;
-    }
-    
-    public boolean doKeyUp(int keyCode, KeyEvent event) {
-        boolean handled = false;
-
-        if (mState == State.KEEP_TRACKING && mKeyCode == keyCode) {
-            mEvent = event;
-            callTracker(Stage.UP, System.currentTimeMillis());
-            handled = mState != State.NOT_TRACKING;
-        }
-        mKeyCode = NOT_A_KEYCODE;
-        return handled;
-    }
-    
-    private void callTracker(Stage stage, long now) {
-        mState = mTracker.onKeyTracker(mKeyCode, mEvent, stage, (int)(now - mStartMS));
-    }
-    
-    private void dump() {
-        System.out.println(" key=" + mKeyCode + " dur=" + (System.currentTimeMillis() - mStartMS) +
-                            " state=" + mState);
-    }
-
-    private int             mKeyCode = NOT_A_KEYCODE;
-    private KeyEvent        mEvent;
-    private long            mStartMS;
-    private State           mState;
-    private OnKeyTracker    mTracker;
-
-    private static final int LONG_PRESS_DURATION_MS = 
-            ViewConfiguration.getLongPressTimeout();
-    private static final int NOT_A_KEYCODE = -123456;
-}
-
diff --git a/src/com/android/browser/MostVisitedActivity.java b/src/com/android/browser/MostVisitedActivity.java
index 83342a1..d03c7a3 100644
--- a/src/com/android/browser/MostVisitedActivity.java
+++ b/src/com/android/browser/MostVisitedActivity.java
@@ -23,6 +23,7 @@
 import android.database.Cursor;
 import android.database.DataSetObserver;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.os.Bundle;
 import android.os.Handler;
 import android.provider.Browser;
@@ -35,6 +36,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
+import android.view.ViewStub;
 
 import java.util.Vector;
 
@@ -49,12 +51,11 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mAdapter = new MyAdapter();
-        CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
+        CombinedBookmarkHistoryActivity.getIconListenerSet()
                 .addListener(mIconReceiver);
         setListAdapter(mAdapter);
         ListView list = getListView();
-        LayoutInflater factory = LayoutInflater.from(this);
-        View v = factory.inflate(R.layout.empty_history, null);
+        View v = new ViewStub(this, R.layout.empty_history);
         addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
                 LayoutParams.FILL_PARENT));
         list.setEmptyView(v);
@@ -63,7 +64,7 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
+        CombinedBookmarkHistoryActivity.getIconListenerSet()
                .removeListener(mIconReceiver);
     }
 
@@ -94,16 +95,18 @@
         private Vector<DataSetObserver> mObservers;
         private Cursor mCursor;
         // These correspond with projection below.
-        private final int mUrlIndex = 0;
-        private final int mTitleIndex = 1;
-        private final int mBookmarkIndex = 2;
+        private static final int mUrlIndex = 0;
+        private static final int mTitleIndex = 1;
+        private static final int mBookmarkIndex = 2;
+        private static final int mFaviconIndex = 3;
 
         MyAdapter() {
             mObservers = new Vector<DataSetObserver>();
             String[] projection = new String[] {
                     Browser.BookmarkColumns.URL,
                     Browser.BookmarkColumns.TITLE,
-                    Browser.BookmarkColumns.BOOKMARK };
+                    Browser.BookmarkColumns.BOOKMARK,
+                    Browser.BookmarkColumns.FAVICON };
             String whereClause = Browser.BookmarkColumns.VISITS + " != 0";
             String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
             mCursor = managedQuery(Browser.BOOKMARKS_URI, projection,
@@ -145,8 +148,14 @@
             item.setName(mCursor.getString(mTitleIndex));
             String url = mCursor.getString(mUrlIndex);
             item.setUrl(url);
-            item.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet(
-                    getContentResolver()).getFavicon(url));
+            byte[] data = mCursor.getBlob(mFaviconIndex);
+            if (data != null) {
+                item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
+                        data.length));
+            } else {
+                item.setFavicon(CombinedBookmarkHistoryActivity
+                        .getIconListenerSet().getFavicon(url));
+            }
             item.setIsBookmark(1 == mCursor.getInt(mBookmarkIndex));
             return item;
         }
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 575be8d..2f15a9c 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -17,6 +17,7 @@
 package com.android.browser;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Picture;
 import android.net.http.SslError;
 import android.os.Bundle;
@@ -38,6 +39,7 @@
 import android.webkit.WebViewClient;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
+import android.widget.LinearLayout;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -68,7 +70,7 @@
     private final LayoutInflater mInflateService;
     // Subclass of WebViewClient used in subwindows to notify the main
     // WebViewClient of certain WebView activities.
-    private class SubWindowClient extends WebViewClient {
+    private static class SubWindowClient extends WebViewClient {
         // The main WebViewClient.
         private final WebViewClient mClient;
 
@@ -104,6 +106,16 @@
                 String description, String failingUrl) {
             mClient.onReceivedError(view, errorCode, description, failingUrl);
         }
+        @Override
+        public boolean shouldOverrideKeyEvent(WebView view,
+                android.view.KeyEvent event) {
+            return mClient.shouldOverrideKeyEvent(view, event);
+        }
+        @Override
+        public void onUnhandledKeyEvent(WebView view,
+                android.view.KeyEvent event) {
+            mClient.onUnhandledKeyEvent(view, event);
+        }
     }
     // Subclass of WebChromeClient to display javascript dialogs.
     private class SubWindowChromeClient extends WebChromeClient {
@@ -139,7 +151,7 @@
         public void onRequestFocus(WebView view) {
             Tab t = getTabFromView(view);
             if (t != getCurrentTab()) {
-                mActivity.showTab(t);
+                mActivity.switchToTab(getTabIndex(t));
             }
         }
     }
@@ -148,20 +160,19 @@
     public static class PickerData {
         String  mUrl;
         String  mTitle;
+        Bitmap  mFavicon;
         float   mScale;
         int     mScrollX;
         int     mScrollY;
-        int     mWidth;
-        Picture mPicture;
-        // This can be null. When a new picture comes in, this view should be
-        // invalidated to show the new picture.
-        FakeWebView mFakeWebView;
     }
 
     /**
      * Private class for maintaining Tabs with a main WebView and a subwindow.
      */
-    public class Tab implements WebView.PictureListener {
+    public class Tab {
+        // The Geolocation permissions prompt
+        private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
+        private View mContainer;
         // Main WebView
         private WebView mMainView;
         // Subwindow WebView
@@ -195,12 +206,103 @@
         // url has not changed.
         private String mOriginalUrl;
 
+        private ErrorConsoleView mErrorConsole;
+        // the lock icon type and previous lock icon type for the tab
+        private int mSavedLockIconType;
+        private int mSavedPrevLockIconType;
+
         // Construct a new tab
-        private Tab(WebView w, boolean closeOnExit, String appId, String url) {
-            mMainView = w;
+        private Tab(WebView w, boolean closeOnExit, String appId, String url, Context context) {
             mCloseOnExit = closeOnExit;
             mAppId = appId;
             mOriginalUrl = url;
+            mSavedLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
+            mSavedPrevLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
+
+            // The tab consists of a container view, which contains the main
+            // WebView, as well as any other UI elements associated with the tab.
+            LayoutInflater factory = LayoutInflater.from(context);
+            mContainer = factory.inflate(R.layout.tab, null);
+
+            mGeolocationPermissionsPrompt =
+                (GeolocationPermissionsPrompt) mContainer.findViewById(
+                    R.id.geolocation_permissions_prompt);
+
+            setWebView(w);
+        }
+
+        /**
+         * Sets the WebView for this tab, correctly removing the old WebView
+         * from the container view.
+         */
+        public void setWebView(WebView w) {
+            if (mMainView == w) {
+                return;
+            }
+            // If the WebView is changing, the page will be reloaded, so any ongoing Geolocation
+            // permission requests are void.
+            mGeolocationPermissionsPrompt.hide();
+
+            // Just remove the old one.
+            FrameLayout wrapper =
+                    (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
+            wrapper.removeView(mMainView);
+            mMainView = w;
+        }
+
+        /**
+         * This method attaches both the WebView and any sub window to the
+         * given content view.
+         */
+        public 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);
+            wrapper.addView(mMainView);
+            content.addView(mContainer, BrowserActivity.COVER_SCREEN_PARAMS);
+            attachSubWindow(content);
+        }
+
+        /**
+         * Remove the WebView and any sub window from the given content view.
+         */
+        public 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);
+            removeSubWindow(content);
+        }
+
+        /**
+         * Attach the sub window to the content view.
+         */
+        public void attachSubWindow(ViewGroup content) {
+            if (mSubView != null) {
+                content.addView(mSubViewContainer,
+                        BrowserActivity.COVER_SCREEN_PARAMS);
+            }
+        }
+
+        /**
+         * Remove the sub window from the content view.
+         */
+        public void removeSubWindow(ViewGroup content) {
+            if (mSubView != null) {
+                content.removeView(mSubViewContainer);
+            }
         }
 
         /**
@@ -226,6 +328,13 @@
         }
 
         /**
+         * @return The geolocation permissions prompt for this tab.
+         */
+        public GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
+            return mGeolocationPermissionsPrompt;
+        }
+
+        /**
          * Return the subwindow of this tab or null if there is no subwindow.
          * @return The subwindow of this tab or null.
          */
@@ -234,15 +343,6 @@
         }
 
         /**
-         * Return the subwindow container of this tab or null if there is no
-         * subwindow.
-         * @return The subwindow's container View.
-         */
-        public View getSubWebViewContainer() {
-            return mSubViewContainer;
-        }
-
-        /**
          * Get the url of this tab.  Valid after calling populatePickerData, but
          * before calling wipePickerData, or if the webview has been destroyed.
          * 
@@ -269,11 +369,11 @@
             return null;
         }
 
-        /**
-         * Returns the picker data.
-         */
-        public PickerData getPickerData() {
-            return mPickerData;
+        public Bitmap getFavicon() {
+            if (mPickerData != null) {
+                return mPickerData.mFavicon;
+            }
+            return null;
         }
 
         private void setParentTab(Tab parent) {
@@ -336,16 +436,20 @@
             return mCloseOnExit;
         }
 
-        public void onNewPicture(WebView view, Picture p) {
-            if (mPickerData == null) {
-                return;
-            }
+        void setLockIconType(int type) {
+            mSavedLockIconType = type;
+        }
 
-            mPickerData.mPicture = p;
-            // Tell the FakeWebView to redraw.
-            if (mPickerData.mFakeWebView != null) {
-                mPickerData.mFakeWebView.invalidate();
-            }
+        int getLockIconType() {
+            return mSavedLockIconType;
+        }
+
+        void setPrevLockIconType(int type) {
+            mSavedPrevLockIconType = type;
+        }
+
+        int getPrevLockIconType() {
+            return mSavedPrevLockIconType;
         }
     };
 
@@ -388,6 +492,28 @@
     }
 
     /**
+     * Return the current tab's error console. Creates the console if createIfNEcessary
+     * is true and we haven't already created the console.
+     * @param createIfNecessary Flag to indicate if the console should be created if it has
+     *                          not been already.
+     * @return The current tab's error console, or null if one has not been created and
+     *         createIfNecessary is false.
+     */
+    ErrorConsoleView getCurrentErrorConsole(boolean createIfNecessary) {
+        Tab t = getTab(mCurrentTab);
+        if (t == null) {
+            return null;
+        }
+
+        if (createIfNecessary && t.mErrorConsole == null) {
+            t.mErrorConsole = new ErrorConsoleView(mActivity);
+            t.mErrorConsole.setWebView(t.mMainView);
+        }
+
+        return t.mErrorConsole;
+    }
+
+    /**
      * Return the current tab's top-level WebView. This can return a subwindow
      * if one exists.
      * @return The top-level WebView of the current tab.
@@ -446,6 +572,9 @@
      * @return index of Tab or -1 if not found
      */
     int getTabIndex(Tab tab) {
+        if (tab == null) {
+            return -1;
+        }
         return mTabs.indexOf(tab);
     }
 
@@ -461,8 +590,9 @@
             return null;
         }
         final WebView w = createNewWebView();
+
         // Create a new tab and add it to the tab list
-        Tab t = new Tab(w, closeOnExit, appId, url);
+        Tab t = new Tab(w, closeOnExit, appId, url, mActivity);
         mTabs.add(t);
         // Initially put the tab in the background.
         putTabInBackground(t);
@@ -499,9 +629,10 @@
             // observers.
             BrowserSettings.getInstance().deleteObserver(
                     t.mMainView.getSettings());
-            // Destroy the main view and subview
-            t.mMainView.destroy();
-            t.mMainView = null;
+            WebView w = t.mMainView;
+            t.setWebView(null);
+            // Destroy the main view
+            w.destroy();
         }
         // clear it's references to parent and children
         t.removeFromTree();
@@ -561,8 +692,9 @@
             if (t.mMainView != null) {
                 dismissSubWindow(t);
                 s.deleteObserver(t.mMainView.getSettings());
-                t.mMainView.destroy();
-                t.mMainView = null;
+                WebView w = t.mMainView;
+                t.setWebView(null);
+                w.destroy();
             }
         }
         mTabs.clear();
@@ -583,7 +715,6 @@
     private static final String CURRTAB = "currentTab";
     private static final String CURRURL = "currentUrl";
     private static final String CURRTITLE = "currentTitle";
-    private static final String CURRWIDTH = "currentWidth";
     private static final String CURRPICTURE = "currentPicture";
     private static final String CLOSEONEXIT = "closeonexit";
     private static final String PARENTTAB = "parentTab";
@@ -634,7 +765,7 @@
                 } else {
                     // Create a new tab and don't restore the state yet, add it
                     // to the tab list
-                    Tab t = new Tab(null, false, null, null);
+                    Tab t = new Tab(null, false, null, null, mActivity);
                     t.mSavedState = inState.getBundle(WEBVIEW + i);
                     if (t.mSavedState != null) {
                         populatePickerDataFromSavedState(t);
@@ -671,8 +802,10 @@
      * WebView cache;
      */
     void freeMemory() {
+        if (getTabCount() == 0) return;
+
         // free the least frequently used background tab
-        Tab t = getLeastUsedTab();
+        Tab t = getLeastUsedTab(getCurrentTab());
         if (t != null) {
             Log.w(LOGTAG, "Free a tab in the browser");
             freeTab(t);
@@ -681,19 +814,20 @@
             return;
         }
 
-        // free the WebView cache
-        Log.w(LOGTAG, "Free WebView cache");
+        // free the WebView's unused memory (this includes the cache)
+        Log.w(LOGTAG, "Free WebView's unused memory and cache");
         WebView view = getCurrentWebView();
         if (view != null) {
-            view.clearCache(false);
+            view.freeMemory();
         }
         // force a gc
         System.gc();
     }
 
-    private Tab getLeastUsedTab() {
-        // Don't do anything if we only have 1 tab.
-        if (getTabCount() == 1) {
+    private Tab getLeastUsedTab(Tab current) {
+        // Don't do anything if we only have 1 tab or if the current tab is
+        // null.
+        if (getTabCount() == 1 || current == null) {
             return null;
         }
 
@@ -707,11 +841,13 @@
         }
         do {
             t = mTabQueue.get(i++);
-        } while (i < queueSize && t != null && t.mMainView == null);
+        } while (i < queueSize
+                && ((t != null && t.mMainView == null)
+                    || t == current.mParentTab));
 
         // Don't do anything if the last remaining tab is the current one or if
         // the last tab has been freed already.
-        if (t == getCurrentTab() || t.mMainView == null) {
+        if (t == current || t.mMainView == null) {
             return null;
         }
 
@@ -727,8 +863,9 @@
         // Remove the WebView's settings from the BrowserSettings list of
         // observers.
         BrowserSettings.getInstance().deleteObserver(t.mMainView.getSettings());
-        t.mMainView.destroy();
-        t.mMainView = null;
+        WebView w = t.mMainView;
+        t.setWebView(null);
+        w.destroy();
     }
 
     /**
@@ -801,6 +938,45 @@
         return null;
     }
 
+    // This method checks if a non-app tab (one created within the browser)
+    // matches the given url.
+    private boolean tabMatchesUrl(Tab t, String url) {
+        if (t.mAppId != null) {
+            return false;
+        } else if (t.mMainView == null) {
+            return false;
+        } else if (url.equals(t.mMainView.getUrl()) ||
+                url.equals(t.mMainView.getOriginalUrl())) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Return the tab that has no app id associated with it and the url of the
+     * tab matches the given url.
+     * @param url The url to search for.
+     */
+    Tab findUnusedTabWithUrl(String url) {
+        if (url == null) {
+            return null;
+        }
+        // Check the current tab first.
+        Tab t = getCurrentTab();
+        if (t != null && tabMatchesUrl(t, url)) {
+            return t;
+        }
+        // Now check all the rest.
+        final int size = getTabCount();
+        for (int i = 0; i < size; i++) {
+            t = getTab(i);
+            if (tabMatchesUrl(t, url)) {
+                return t;
+            }
+        }
+        return null;
+    }
+
     /**
      * Recreate the main WebView of the given tab. Returns true if the WebView
      * was deleted.
@@ -827,7 +1003,7 @@
         }
         // Create a new WebView. If this tab is the current tab, we need to put
         // back all the clients so force it to be the current tab.
-        t.mMainView = createNewWebView();
+        t.setWebView(createNewWebView());
         if (getCurrentTab() == t) {
             setCurrentTab(t, true);
         }
@@ -846,6 +1022,8 @@
     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);
@@ -865,6 +1043,48 @@
         return setCurrentTab(newTab, false);
     }
 
+    /*package*/ void pauseCurrentTab() {
+        Tab t = getCurrentTab();
+        if (t != null) {
+            t.mMainView.onPause();
+            if (t.mSubView != null) {
+                t.mSubView.onPause();
+            }
+        }
+    }
+
+    /*package*/ void resumeCurrentTab() {
+        Tab t = getCurrentTab();
+        if (t != null) {
+            t.mMainView.onResume();
+            if (t.mSubView != null) {
+                t.mSubView.onResume();
+            }
+        }
+    }
+
+    private void putViewInForeground(WebView v, WebViewClient vc,
+                                     WebChromeClient cc) {
+        v.setWebViewClient(vc);
+        v.setWebChromeClient(cc);
+        v.setOnCreateContextMenuListener(mActivity);
+        v.setDownloadListener(mActivity);
+        v.onResume();
+    }
+
+    private void putViewInBackground(WebView v) {
+        // Set an empty callback so that default actions are not triggered.
+        v.setWebViewClient(mEmptyClient);
+        v.setWebChromeClient(mBackgroundChromeClient);
+        v.setOnCreateContextMenuListener(null);
+        // Leave the DownloadManager attached so that downloads can start in
+        // a non-active window. This can happen when going to a site that does
+        // a redirect after a period of time. The user could have switched to
+        // another tab while waiting for the download to start.
+        v.setDownloadListener(mActivity);
+        v.onPause();
+    }
+
     /**
      * If force is true, this method skips the check for newTab == current.
      */
@@ -890,7 +1110,6 @@
         mTabQueue.add(newTab);
 
         WebView mainView;
-        WebView subView;
 
         // Display the new current tab
         mCurrentTab = mTabs.indexOf(newTab);
@@ -898,19 +1117,15 @@
         boolean needRestore = (mainView == null);
         if (needRestore) {
             // Same work as in createNewTab() except don't do new Tab()
-            newTab.mMainView = mainView = createNewWebView();
+            mainView = createNewWebView();
+            newTab.setWebView(mainView);
         }
-        mainView.setWebViewClient(mActivity.getWebViewClient());
-        mainView.setWebChromeClient(mActivity.getWebChromeClient());
-        mainView.setOnCreateContextMenuListener(mActivity);
-        mainView.setDownloadListener(mActivity);
+        putViewInForeground(mainView, mActivity.getWebViewClient(),
+                            mActivity.getWebChromeClient());
         // Add the subwindow if it exists
         if (newTab.mSubViewContainer != null) {
-            subView = newTab.mSubView;
-            subView.setWebViewClient(newTab.mSubViewClient);
-            subView.setWebChromeClient(newTab.mSubViewChromeClient);
-            subView.setOnCreateContextMenuListener(mActivity);
-            subView.setDownloadListener(mActivity);
+            putViewInForeground(newTab.mSubView, newTab.mSubViewClient,
+                                newTab.mSubViewChromeClient);
         }
         if (needRestore) {
             // Have to finish setCurrentTab work before calling restoreState
@@ -925,23 +1140,9 @@
      * Put the tab in the background using all the empty/background clients.
      */
     private void putTabInBackground(Tab t) {
-        WebView mainView = t.mMainView;
-        // Set an empty callback so that default actions are not triggered.
-        mainView.setWebViewClient(mEmptyClient);
-        mainView.setWebChromeClient(mBackgroundChromeClient);
-        mainView.setOnCreateContextMenuListener(null);
-        // Leave the DownloadManager attached so that downloads can start in
-        // a non-active window. This can happen when going to a site that does
-        // a redirect after a period of time. The user could have switched to
-        // another tab while waiting for the download to start.
-        mainView.setDownloadListener(mActivity);
-        WebView subView = t.mSubView;
-        if (subView != null) {
-            // Set an empty callback so that default actions are not triggered.
-            subView.setWebViewClient(mEmptyClient);
-            subView.setWebChromeClient(mBackgroundChromeClient);
-            subView.setOnCreateContextMenuListener(null);
-            subView.setDownloadListener(mActivity);
+        putViewInBackground(t.mMainView);
+        if (t.mSubView != null) {
+            putViewInBackground(t.mSubView);
         }
     }
 
@@ -979,15 +1180,6 @@
         final WebHistoryItem item =
                 list != null ? list.getCurrentItem() : null;
         populatePickerData(t, item);
-
-        // This method is only called during the tab picker creation. At this
-        // point we need to listen for new pictures since the WebView is still
-        // active.
-        final WebView w = t.getTopWindow();
-        w.setPictureListener(t);
-        // Capture the picture here instead of populatePickerData since it can
-        // be called when saving the state of a tab.
-        t.mPickerData.mPicture = w.capturePicture();
     }
 
     // Create the PickerData and populate it using the saved state of the tab.
@@ -1000,25 +1192,12 @@
         final Bundle state = t.mSavedState;
         data.mUrl = state.getString(CURRURL);
         data.mTitle = state.getString(CURRTITLE);
-        data.mWidth = state.getInt(CURRWIDTH, 0);
         // XXX: These keys are from WebView.savePicture so if they change, this
         // will break.
         data.mScale = state.getFloat("scale", 1.0f);
         data.mScrollX = state.getInt("scrollX", 0);
         data.mScrollY = state.getInt("scrollY", 0);
 
-        if (state.containsKey(CURRPICTURE)) {
-            final File f = new File(t.mSavedState.getString(CURRPICTURE));
-            try {
-                final FileInputStream in = new FileInputStream(f);
-                data.mPicture = Picture.createFromStream(in);
-                in.close();
-            } catch (Exception ex) {
-                // Ignore any problems with inflating the picture. We just
-                // won't draw anything.
-            }
-        }
-
         // Set the tab's picker data.
         t.mPickerData = data;
     }
@@ -1030,6 +1209,7 @@
         if (item != null) {
             data.mUrl = item.getUrl();
             data.mTitle = item.getTitle();
+            data.mFavicon = item.getFavicon();
             if (data.mTitle == null) {
                 data.mTitle = data.mUrl;
             }
@@ -1037,10 +1217,10 @@
         // We want to display the top window in the tab picker but use the url
         // and title of the main window.
         final WebView w = t.getTopWindow();
-        data.mWidth = w.getWidth();
         data.mScale = w.getScale();
         data.mScrollX = w.getScrollX();
         data.mScrollY = w.getScrollY();
+
         t.mPickerData = data;
     }
     
@@ -1054,13 +1234,6 @@
             if (t != null && t.mSavedState == null) {
                 t.mPickerData = null;
             }
-            if (t.mMainView != null) {
-                // Clear the picture listeners.
-                t.mMainView.setPictureListener(null);
-                if (t.mSubView != null) {
-                    t.mSubView.setPictureListener(null);
-                }
-            }
         }
     }
 
@@ -1099,7 +1272,6 @@
             if (data.mTitle != null) {
                 b.putString(CURRTITLE, data.mTitle);
             }
-            b.putInt(CURRWIDTH, data.mWidth);
             b.putBoolean(CLOSEONEXIT, t.mCloseOnExit);
             if (t.mAppId != null) {
                 b.putString(APPID, t.mAppId);
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
new file mode 100644
index 0000000..23b1ed5
--- /dev/null
+++ b/src/com/android/browser/TitleBar.java
@@ -0,0 +1,269 @@
+/*
+ * 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.content.Context;
+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.util.TypedValue;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+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;
+
+/**
+ * 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 Drawable        mStopDrawable;
+    private Drawable        mBookmarkDrawable;
+    private boolean         mInLoad;
+    private BrowserActivity mBrowserActivity;
+    private Drawable        mGenericFavicon;
+    private int             mIconDimension;
+    private View            mTitleBg;
+    private MyHandler       mHandler;
+
+    private static int LONG_PRESS = 1;
+
+    public TitleBar(BrowserActivity context) {
+        super(context, null);
+        mHandler = new MyHandler();
+        LayoutInflater factory = LayoutInflater.from(context);
+        factory.inflate(R.layout.title_bar, this);
+        mBrowserActivity = context;
+
+        mTitle = (TextView) findViewById(R.id.title);
+        mTitle.setCompoundDrawablePadding(5);
+
+        mTitleBg = findViewById(R.id.title_bg);
+        mLockIcon = (ImageView) findViewById(R.id.lock);
+        mFavicon = (ImageView) findViewById(R.id.favicon);
+
+        mRtButton = (ImageView) findViewById(R.id.rt_btn);
+        Resources resources = context.getResources();
+        mCircularProgress = (Drawable) resources.getDrawable(
+                com.android.internal.R.drawable.search_spinner);
+        mIconDimension = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, 20f,
+                resources.getDisplayMetrics());
+        mCircularProgress.setBounds(0, 0, mIconDimension, mIconDimension);
+        mHorizontalProgress = (ProgressBar) findViewById(
+                R.id.progress_horizontal);
+        mGenericFavicon = context.getResources().getDrawable(
+                R.drawable.app_web_browser_sm);
+    }
+
+    private class MyHandler extends Handler {
+        public void handleMessage(Message msg) {
+            if (msg.what == LONG_PRESS) {
+                // Prevent the normal action from happening by setting the title
+                // bar's state to false.
+                mTitleBg.setPressed(false);
+                // 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();
+            }
+        }
+    };
+
+    @Override
+    protected void onCreateContextMenu(ContextMenu menu) {
+        MenuInflater inflater = mBrowserActivity.getMenuInflater();
+        inflater.inflate(R.menu.title_context, menu);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                // Make all touches hit either the textfield or the button,
+                // depending on which side of the right edge of the textfield
+                // they hit.
+                if ((int) event.getX() > mTitleBg.getRight()) {
+                    mRtButton.setPressed(true);
+                } else {
+                    mTitleBg.setPressed(true);
+                    mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                            LONG_PRESS),
+                            ViewConfiguration.getLongPressTimeout());
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                int slop = ViewConfiguration.get(mBrowserActivity)
+                        .getScaledTouchSlop();
+                if ((int) event.getY() > getHeight() + slop) {
+                    // We only trigger the actions in ACTION_UP if one or the
+                    // other is pressed.  Since the user moved off the title
+                    // bar, mark both as not pressed.
+                    mTitleBg.setPressed(false);
+                    mRtButton.setPressed(false);
+                    mHandler.removeMessages(LONG_PRESS);
+                    break;
+                }
+                int x = (int) event.getX();
+                int titleRight = mTitleBg.getRight();
+                if (mTitleBg.isPressed() && x > titleRight + slop) {
+                    mTitleBg.setPressed(false);
+                    mHandler.removeMessages(LONG_PRESS);
+                } else if (mRtButton.isPressed() && x < titleRight - slop) {
+                    mRtButton.setPressed(false);
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                mRtButton.setPressed(false);
+                mTitleBg.setPressed(false);
+                mHandler.removeMessages(LONG_PRESS);
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mRtButton.isPressed()) {
+                    if (mInLoad) {
+                        mBrowserActivity.stopLoading();
+                    } else {
+                        mBrowserActivity.bookmarksOrHistoryPicker(false);
+                    }
+                    mRtButton.setPressed(false);
+                } else if (mTitleBg.isPressed()) {
+                    mHandler.removeMessages(LONG_PRESS);
+                    mBrowserActivity.onSearchRequested();
+                    mTitleBg.setPressed(false);
+                }
+                break;
+            default:
+                break;
+        }
+        return true;
+    }
+
+    /**
+     * Return whether the associated WebView is currently loading.  Needed to
+     * determine whether a click should stop the load or close the tab.
+     */
+    /* package */ boolean isInLoad() {
+        return mInLoad;
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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) {
+        if (newProgress >= mHorizontalProgress.getMax()) {
+            mTitle.setCompoundDrawables(null, null, null, null);
+            ((Animatable) mCircularProgress).stop();
+            mHorizontalProgress.setVisibility(View.INVISIBLE);
+            if (mBookmarkDrawable != null) {
+                mRtButton.setImageDrawable(mBookmarkDrawable);
+            }
+            mInLoad = false;
+        } else {
+            mHorizontalProgress.setProgress(newProgress);
+            if (!mInLoad && getWindowToken() != null) {
+                // checking the window token lets us be sure that we
+                // are attached to a window before starting the animation,
+                // preventing a potential race condition
+                // (fix for bug http://b/2115736)
+                mTitle.setCompoundDrawables(null, null, mCircularProgress,
+                        null);
+                ((Animatable) mCircularProgress).start();
+                mHorizontalProgress.setVisibility(View.VISIBLE);
+                if (mBookmarkDrawable == null) {
+                    mBookmarkDrawable = mRtButton.getDrawable();
+                }
+                if (mStopDrawable == null) {
+                    mRtButton.setImageResource(R.drawable.ic_btn_stop_v2);
+                    mStopDrawable = mRtButton.getDrawable();
+                } else {
+                    mRtButton.setImageDrawable(mStopDrawable);
+                }
+                mInLoad = true;
+            }
+        }
+    }
+
+    /**
+     * Update the title and url.
+     */
+    /* package */ void setTitleAndUrl(CharSequence title, CharSequence url) {
+        if (url == null) {
+            mTitle.setText(R.string.title_bar_loading);
+        } else {
+            mTitle.setText(url.toString());
+        }
+    }
+
+    /* 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/WebStorageSizeManager.java b/src/com/android/browser/WebStorageSizeManager.java
new file mode 100644
index 0000000..3afcadc
--- /dev/null
+++ b/src/com/android/browser/WebStorageSizeManager.java
@@ -0,0 +1,406 @@
+/*
+ * 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.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.StatFs;
+import android.util.Log;
+import android.webkit.WebStorage;
+
+import java.io.File;
+import java.util.Set;
+
+
+/**
+ * Package level class for managing the disk size consumed by the WebDatabase
+ * and ApplicationCaches APIs (henceforth called Web storage).
+ *
+ * Currently, the situation on the WebKit side is as follows:
+ *  - WebDatabase enforces a quota for each origin.
+ *  - Session/LocalStorage do not enforce any disk limits.
+ *  - ApplicationCaches enforces a maximum size for all origins.
+ *
+ * The WebStorageSizeManager maintains a global limit for the disk space
+ * consumed by the WebDatabase and ApplicationCaches. As soon as WebKit will
+ * have a limit for Session/LocalStorage, this class will manage the space used
+ * by those APIs as well.
+ *
+ * The global limit is computed as a function of the size of the partition where
+ * these APIs store their data (they must store it on the same partition for
+ * this to work) and the size of the available space on that partition.
+ * The global limit is not subject to user configuration but we do provide
+ * a debug-only setting.
+ * TODO(andreip): implement the debug setting.
+ *
+ * The size of the disk space used for Web storage is initially divided between
+ * WebDatabase and ApplicationCaches as follows:
+ *
+ * 75% for WebDatabase
+ * 25% for ApplicationCaches
+ *
+ * When an origin's database usage reaches its current quota, WebKit invokes
+ * the following callback function:
+ * - exceededDatabaseQuota(Frame* frame, const String& database_name);
+ * Note that the default quota for a new origin is 0, so we will receive the
+ * 'exceededDatabaseQuota' callback before a new origin gets the chance to
+ * create its first database.
+ *
+ * When the total ApplicationCaches usage reaches its current quota, WebKit
+ * invokes the following callback function:
+ * - void reachedMaxAppCacheSize(int64_t spaceNeeded);
+ *
+ * The WebStorageSizeManager's main job is to respond to the above two callbacks
+ * by inspecting the amount of unused Web storage quota (i.e. global limit -
+ * sum of all other origins' quota) and deciding if a quota increase for the
+ * out-of-space origin is allowed or not.
+ *
+ * The default quota for an origin is its estimated size. If we cannot satisfy
+ * the estimated size, then WebCore will not create the database.
+ * Quota increases are done in steps, where the increase step is
+ * min(QUOTA_INCREASE_STEP, unused_quota).
+ *
+ * When all the Web storage space is used, the WebStorageSizeManager creates
+ * a system notification that will guide the user to the WebSettings UI. There,
+ * the user can free some of the Web storage space by deleting all the data used
+ * by an origin.
+ */
+class WebStorageSizeManager {
+    // Logging flags.
+    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";
+    // The default quota value for an origin.
+    public final static long ORIGIN_DEFAULT_QUOTA = 3 * 1024 * 1024;  // 3MB
+    // The default value for quota increases.
+    public final static long QUOTA_INCREASE_STEP = 1 * 1024 * 1024;  // 1MB
+    // Extra padding space for appcache maximum size increases. This is needed
+    // because WebKit sends us an estimate of the amount of space needed
+    // but this estimate may, currently, be slightly less than what is actually
+    // needed. We therefore add some 'padding'.
+    // TODO(andreip): fix this in WebKit.
+    public final static long APPCACHE_MAXSIZE_PADDING = 512 * 1024; // 512KB
+    // The system status bar notification id.
+    private final static int OUT_OF_SPACE_ID = 1;
+    // The time of the last out of space notification
+    private static long mLastOutOfSpaceNotificationTime = -1;
+    // Delay between two notification in ms
+    private final static long NOTIFICATION_INTERVAL = 5 * 60 * 1000;
+    // Delay in ms used when resetting the notification time
+    private final static long RESET_NOTIFICATION_INTERVAL = 3 * 1000;
+    // The application context.
+    private final Context mContext;
+    // The global Web storage limit.
+    private final long mGlobalLimit;
+    // The maximum size of the application cache file.
+    private long mAppCacheMaxSize;
+
+    /**
+     * Interface used by the WebStorageSizeManager to obtain information
+     * about the underlying file system. This functionality is separated
+     * into its own interface mainly for testing purposes.
+     */
+    public interface DiskInfo {
+        /**
+         * @return the size of the free space in the file system.
+         */
+        public long getFreeSpaceSizeBytes();
+
+        /**
+         * @return the total size of the file system.
+         */
+        public long getTotalSizeBytes();
+    };
+
+    private DiskInfo mDiskInfo;
+    // For convenience, we provide a DiskInfo implementation that uses StatFs.
+    public static class StatFsDiskInfo implements DiskInfo {
+        private StatFs mFs;
+
+        public StatFsDiskInfo(String path) {
+            mFs = new StatFs(path);
+        }
+
+        public long getFreeSpaceSizeBytes() {
+            return mFs.getAvailableBlocks() * mFs.getBlockSize();
+        }
+
+        public long getTotalSizeBytes() {
+            return mFs.getBlockCount() * mFs.getBlockSize();
+        }
+    };
+
+    /**
+     * Interface used by the WebStorageSizeManager to obtain information
+     * about the appcache file. This functionality is separated into its own
+     * interface mainly for testing purposes.
+     */
+    public interface AppCacheInfo {
+        /**
+         * @return the current size of the appcache file.
+         */
+        public long getAppCacheSizeBytes();
+    };
+
+    // For convenience, we provide an AppCacheInfo implementation.
+    public static class WebKitAppCacheInfo implements AppCacheInfo {
+        // The name of the application cache file. Keep in sync with
+        // WebCore/loader/appcache/ApplicationCacheStorage.cpp
+        private final static String APPCACHE_FILE = "ApplicationCache.db";
+        private String mAppCachePath;
+
+        public WebKitAppCacheInfo(String path) {
+            mAppCachePath = path;
+        }
+
+        public long getAppCacheSizeBytes() {
+            File file = new File(mAppCachePath
+                    + File.separator
+                    + APPCACHE_FILE);
+            return file.length();
+        }
+    };
+
+    /**
+     * Public ctor
+     * @param ctx is the application context
+     * @param diskInfo is the DiskInfo instance used to query the file system.
+     * @param appCacheInfo is the AppCacheInfo used to query info about the
+     * appcache file.
+     */
+    public WebStorageSizeManager(Context ctx, DiskInfo diskInfo,
+            AppCacheInfo appCacheInfo) {
+        mContext = ctx;
+        mDiskInfo = diskInfo;
+        mGlobalLimit = getGlobalLimit();
+        // The initial max size of the app cache is either 25% of the global
+        // limit or the current size of the app cache file, whichever is bigger.
+        mAppCacheMaxSize = Math.max(mGlobalLimit / 4,
+                appCacheInfo.getAppCacheSizeBytes());
+    }
+
+    /**
+     * Returns the maximum size of the application cache.
+     */
+    public long getAppCacheMaxSize() {
+        return mAppCacheMaxSize;
+    }
+
+    /**
+     * The origin has exceeded its database quota.
+     * @param url the URL that exceeded the quota
+     * @param databaseIdentifier the identifier of the database on
+     *     which the transaction that caused the quota overflow was run
+     * @param currentQuota the current quota for the origin.
+     * @param totalUsedQuota is the sum of all origins' quota.
+     * @param quotaUpdater The callback to run when a decision to allow or
+     *     deny quota has been made. Don't forget to call this!
+     */
+    public void onExceededDatabaseQuota(String url,
+        String databaseIdentifier, long currentQuota, long estimatedSize,
+        long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
+        if(LOGV_ENABLED) {
+            Log.v(LOGTAG,
+                  "Received onExceededDatabaseQuota for "
+                  + url
+                  + ":"
+                  + databaseIdentifier
+                  + "(current quota: "
+                  + currentQuota
+                  + ", total used quota: "
+                  + totalUsedQuota
+                  + ")");
+        }
+        long totalUnusedQuota = mGlobalLimit - totalUsedQuota - mAppCacheMaxSize;
+
+        if (totalUnusedQuota <= 0) {
+            // There definitely isn't any more space. Fire notifications
+            // if needed and exit.
+            if (totalUsedQuota > 0) {
+                // We only fire the notification if there are some other websites
+                // using some of the quota. This avoids the degenerate case where
+                // the first ever website to use Web storage tries to use more
+                // data than it is actually available. In such a case, showing
+                // the notification would not help at all since there is nothing
+                // the user can do.
+                scheduleOutOfSpaceNotification();
+            }
+            quotaUpdater.updateQuota(currentQuota);
+            if(LOGV_ENABLED) {
+                Log.v(LOGTAG, "onExceededDatabaseQuota: out of space.");
+            }
+            return;
+        }
+        // We have enough space inside mGlobalLimit.
+        long newOriginQuota = currentQuota;
+        if (newOriginQuota == 0) {
+            // This is a new origin, give it the size it asked for if possible.
+            // If we cannot satisfy the estimatedSize, we should return 0 as
+            // returning a value less that what the site requested will lead
+            // to webcore not creating the database.
+            if (totalUnusedQuota >= estimatedSize) {
+                newOriginQuota = estimatedSize;
+            } else {
+                if (LOGV_ENABLED) {
+                    Log.v(LOGTAG,
+                          "onExceededDatabaseQuota: Unable to satisfy" +
+                          " estimatedSize for the new database " +
+                          " (estimatedSize: " + estimatedSize +
+                          ", unused quota: " + totalUnusedQuota);
+                }
+                newOriginQuota = 0;
+            }
+        } else {
+            // This is an origin we have seen before. It wants a quota
+            // increase.
+            newOriginQuota +=
+                Math.min(QUOTA_INCREASE_STEP, totalUnusedQuota);
+        }
+        quotaUpdater.updateQuota(newOriginQuota);
+
+        if(LOGV_ENABLED) {
+            Log.v(LOGTAG, "onExceededDatabaseQuota set new quota to "
+                    + newOriginQuota);
+        }
+    }
+
+    /**
+     * The Application Cache has exceeded its max size.
+     * @param spaceNeeded is the amount of disk space that would be needed
+     * in order for the last appcache operation to succeed.
+     * @param totalUsedQuota is the sum of all origins' quota.
+     * @param quotaUpdater A callback to inform the WebCore thread that a new
+     * app cache size is available. This callback must always be executed at
+     * some point to ensure that the sleeping WebCore thread is woken up.
+     */
+    public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
+            WebStorage.QuotaUpdater quotaUpdater) {
+        if(LOGV_ENABLED) {
+            Log.v(LOGTAG, "Received onReachedMaxAppCacheSize with spaceNeeded "
+                  + spaceNeeded + " bytes.");
+        }
+
+        long totalUnusedQuota = mGlobalLimit - totalUsedQuota - mAppCacheMaxSize;
+
+        if (totalUnusedQuota < spaceNeeded + APPCACHE_MAXSIZE_PADDING) {
+            // There definitely isn't any more space. Fire notifications
+            // if needed and exit.
+            if (totalUsedQuota > 0) {
+                // We only fire the notification if there are some other websites
+                // using some of the quota. This avoids the degenerate case where
+                // the first ever website to use Web storage tries to use more
+                // data than it is actually available. In such a case, showing
+                // the notification would not help at all since there is nothing
+                // the user can do.
+                scheduleOutOfSpaceNotification();
+            }
+            quotaUpdater.updateQuota(0);
+            if(LOGV_ENABLED) {
+                Log.v(LOGTAG, "onReachedMaxAppCacheSize: out of space.");
+            }
+            return;
+        }
+        // There is enough space to accommodate spaceNeeded bytes.
+        mAppCacheMaxSize += spaceNeeded + APPCACHE_MAXSIZE_PADDING;
+        quotaUpdater.updateQuota(mAppCacheMaxSize);
+
+        if(LOGV_ENABLED) {
+            Log.v(LOGTAG, "onReachedMaxAppCacheSize set new max size to "
+                    + mAppCacheMaxSize);
+        }
+    }
+
+    // Reset the notification time; we use this iff the user
+    // use clear all; we reset it to some time in the future instead
+    // of just setting it to -1, as the clear all method is asynchronous
+    static void resetLastOutOfSpaceNotificationTime() {
+        mLastOutOfSpaceNotificationTime = System.currentTimeMillis() -
+            NOTIFICATION_INTERVAL + RESET_NOTIFICATION_INTERVAL;
+    }
+
+    // Computes the global limit as a function of the size of the data
+    // partition and the amount of free space on that partition.
+    private long getGlobalLimit() {
+        long freeSpace = mDiskInfo.getFreeSpaceSizeBytes();
+        long fileSystemSize = mDiskInfo.getTotalSizeBytes();
+        return calculateGlobalLimit(fileSystemSize, freeSpace);
+    }
+
+    /*package*/ static long calculateGlobalLimit(long fileSystemSizeBytes,
+            long freeSpaceBytes) {
+        if (fileSystemSizeBytes <= 0
+                || freeSpaceBytes <= 0
+                || freeSpaceBytes > fileSystemSizeBytes) {
+            return 0;
+        }
+
+        long fileSystemSizeRatio =
+            2 << ((int) Math.floor(Math.log10(
+                    fileSystemSizeBytes / (1024 * 1024))));
+        long maxSizeBytes = (long) Math.min(Math.floor(
+                fileSystemSizeBytes / fileSystemSizeRatio),
+                Math.floor(freeSpaceBytes / 2));
+        // Round maxSizeBytes up to a multiple of 1024KB (but only if
+        // maxSizeBytes > 1MB).
+        long maxSizeStepBytes = 1024 * 1024;
+        if (maxSizeBytes < maxSizeStepBytes) {
+            return 0;
+        }
+        long roundingExtra = maxSizeBytes % maxSizeStepBytes == 0 ? 0 : 1;
+        return (maxSizeStepBytes
+                * ((maxSizeBytes / maxSizeStepBytes) + roundingExtra));
+    }
+
+    // Schedules a system notification that takes the user to the WebSettings
+    // activity when clicked.
+    private void scheduleOutOfSpaceNotification() {
+        if(LOGV_ENABLED) {
+            Log.v(LOGTAG, "scheduleOutOfSpaceNotification called.");
+        }
+        if (mContext == null) {
+            // mContext can be null if we're running unit tests.
+            return;
+        }
+        if ((mLastOutOfSpaceNotificationTime == -1) ||
+            (System.currentTimeMillis() - mLastOutOfSpaceNotificationTime > NOTIFICATION_INTERVAL)) {
+            // setup the notification boilerplate.
+            int icon = android.R.drawable.stat_sys_warning;
+            CharSequence title = mContext.getString(
+                    R.string.webstorage_outofspace_notification_title);
+            CharSequence text = mContext.getString(
+                    R.string.webstorage_outofspace_notification_text);
+            long when = System.currentTimeMillis();
+            Intent intent = new Intent(mContext, WebsiteSettingsActivity.class);
+            PendingIntent contentIntent =
+                PendingIntent.getActivity(mContext, 0, intent, 0);
+            Notification notification = new Notification(icon, title, when);
+            notification.setLatestEventInfo(mContext, title, text, contentIntent);
+            notification.flags |= Notification.FLAG_AUTO_CANCEL;
+            // Fire away.
+            String ns = Context.NOTIFICATION_SERVICE;
+            NotificationManager mgr =
+                (NotificationManager) mContext.getSystemService(ns);
+            if (mgr != null) {
+                mLastOutOfSpaceNotificationTime = System.currentTimeMillis();
+                mgr.notify(OUT_OF_SPACE_ID, notification);
+            }
+        }
+    }
+}
diff --git a/src/com/android/browser/WebsiteSettingsActivity.java b/src/com/android/browser/WebsiteSettingsActivity.java
new file mode 100644
index 0000000..645e084
--- /dev/null
+++ b/src/com/android/browser/WebsiteSettingsActivity.java
@@ -0,0 +1,579 @@
+/*
+ * 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.AlertDialog;
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Browser;
+import android.util.Log;
+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.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
+import android.webkit.WebIconDatabase;
+import android.webkit.WebStorage;
+import android.widget.ArrayAdapter;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ * Manage the settings for an origin.
+ * We use it to keep track of the 'HTML5' settings, i.e. database (webstorage)
+ * and Geolocation.
+ */
+public class WebsiteSettingsActivity extends ListActivity {
+
+    private String LOGTAG = "WebsiteSettingsActivity";
+    private static String sMBStored = null;
+    private SiteAdapter mAdapter = null;
+
+    class Site {
+        private String mOrigin;
+        private String mTitle;
+        private Bitmap mIcon;
+        private int mFeatures;
+
+        // These constants provide the set of features that a site may support
+        // They must be consecutive. To add a new feature, add a new FEATURE_XXX
+        // variable with value equal to the current value of FEATURE_COUNT, then
+        // increment FEATURE_COUNT.
+        private final static int FEATURE_WEB_STORAGE = 0;
+        private final static int FEATURE_GEOLOCATION = 1;
+        // The number of features available.
+        private final static int FEATURE_COUNT = 2;
+
+        public Site(String origin) {
+            mOrigin = origin;
+            mTitle = null;
+            mIcon = null;
+            mFeatures = 0;
+        }
+
+        public void addFeature(int feature) {
+            mFeatures |= (1 << feature);
+        }
+
+        public boolean hasFeature(int feature) {
+            return (mFeatures & (1 << feature)) != 0;
+        }
+
+        /**
+         * Gets the number of features supported by this site.
+         */
+        public int getFeatureCount() {
+            int count = 0;
+            for (int i = 0; i < FEATURE_COUNT; ++i) {
+                count += hasFeature(i) ? 1 : 0;
+            }
+            return count;
+        }
+
+        /**
+         * Gets the ID of the nth (zero-based) feature supported by this site.
+         * The return value is a feature ID - one of the FEATURE_XXX values.
+         * This is required to determine which feature is displayed at a given
+         * position in the list of features for this site. This is used both
+         * when populating the view and when responding to clicks on the list.
+         */
+        public int getFeatureByIndex(int n) {
+            int j = -1;
+            for (int i = 0; i < FEATURE_COUNT; ++i) {
+                j += hasFeature(i) ? 1 : 0;
+                if (j == n) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        public String getOrigin() {
+            return mOrigin;
+        }
+
+        public void setTitle(String title) {
+            mTitle = title;
+        }
+
+        public void setIcon(Bitmap icon) {
+            mIcon = icon;
+        }
+
+        public Bitmap getIcon() {
+            return mIcon;
+        }
+
+        public String getPrettyOrigin() {
+            return mTitle == null ? null : hideHttp(mOrigin);
+        }
+
+        public String getPrettyTitle() {
+            return mTitle == null ? hideHttp(mOrigin) : mTitle;
+        }
+
+        private String hideHttp(String str) {
+            Uri uri = Uri.parse(str);
+            return "http".equals(uri.getScheme()) ?  str.substring(7) : str;
+        }
+    }
+
+    class SiteAdapter extends ArrayAdapter<Site>
+            implements AdapterView.OnItemClickListener {
+        private int mResource;
+        private LayoutInflater mInflater;
+        private Bitmap mDefaultIcon;
+        private Bitmap mUsageEmptyIcon;
+        private Bitmap mUsageLowIcon;
+        private Bitmap mUsageHighIcon;
+        private Bitmap mLocationAllowedIcon;
+        private Bitmap mLocationDisallowedIcon;
+        private Site mCurrentSite;
+
+        public SiteAdapter(Context context, int rsc) {
+            super(context, rsc);
+            mResource = rsc;
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            mDefaultIcon = BitmapFactory.decodeResource(getResources(),
+                    R.drawable.ic_launcher_shortcut_browser_bookmark);
+            mUsageEmptyIcon = BitmapFactory.decodeResource(getResources(),
+                    R.drawable.ic_list_data_off);
+            mUsageLowIcon = BitmapFactory.decodeResource(getResources(),
+                    R.drawable.ic_list_data_small);
+            mUsageHighIcon = BitmapFactory.decodeResource(getResources(),
+                    R.drawable.ic_list_data_large);
+            mLocationAllowedIcon = BitmapFactory.decodeResource(getResources(),
+                    R.drawable.ic_list_gps_on);
+            mLocationDisallowedIcon = BitmapFactory.decodeResource(getResources(),
+                    R.drawable.ic_list_gps_denied);
+            askForOrigins();
+        }
+
+        /**
+         * Adds the specified feature to the site corresponding to supplied
+         * origin in the map. Creates the site if it does not already exist.
+         */
+        private void addFeatureToSite(Map sites, String origin, int feature) {
+            Site site = null;
+            if (sites.containsKey(origin)) {
+                site = (Site) sites.get(origin);
+            } else {
+                site = new Site(origin);
+                sites.put(origin, site);
+            }
+            site.addFeature(feature);
+        }
+
+        public void askForOrigins() {
+            // Get the list of origins we want to display.
+            // All 'HTML 5 modules' (Database, Geolocation etc) form these
+            // origin strings using WebCore::SecurityOrigin::toString(), so it's
+            // safe to group origins here. Note that WebCore::SecurityOrigin
+            // uses 0 (which is not printed) for the port if the port is the
+            // default for the protocol. Eg http://www.google.com and
+            // http://www.google.com:80 both record a port of 0 and hence
+            // toString() == 'http://www.google.com' for both.
+
+            WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
+                public void onReceiveValue(Map origins) {
+                    Map sites = new HashMap<String, Site>();
+                    if (origins != null) {
+                        Iterator<String> iter = origins.keySet().iterator();
+                        while (iter.hasNext()) {
+                            addFeatureToSite(sites, iter.next(), Site.FEATURE_WEB_STORAGE);
+                        }
+                    }
+                    askForGeolocation(sites);
+                }
+            });
+        }
+
+        public void askForGeolocation(final Map sites) {
+            GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set>() {
+                public void onReceiveValue(Set origins) {
+                    if (origins != null) {
+                        Iterator<String> iter = origins.iterator();
+                        while (iter.hasNext()) {
+                            addFeatureToSite(sites, iter.next(), Site.FEATURE_GEOLOCATION);
+                        }
+                    }
+                    populateIcons(sites);
+                    populateOrigins(sites);
+                }
+            });
+        }
+
+        public void populateIcons(Map sites) {
+            // Create a map from host to origin. This is used to add metadata
+            // (title, icon) for this origin from the bookmarks DB.
+            HashMap hosts = new HashMap<String, Set<Site> >();
+            Set keys = sites.keySet();
+            Iterator<String> originIter = keys.iterator();
+            while (originIter.hasNext()) {
+                String origin = originIter.next();
+                Site site = (Site) sites.get(origin);
+                String host = Uri.parse(origin).getHost();
+                Set hostSites = null;
+                if (hosts.containsKey(host)) {
+                    hostSites = (Set) 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 = getContext().getContentResolver().query(Browser.BOOKMARKS_URI,
+                    new String[] { Browser.BookmarkColumns.URL, Browser.BookmarkColumns.TITLE,
+                    Browser.BookmarkColumns.FAVICON }, "bookmark = 1", null, null);
+
+            if ((c != null) && 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();
+                            site.setTitle(title);
+                            if (bmp != null) {
+                                site.setIcon(bmp);
+                            }
+                        }
+                    }
+                } while (c.moveToNext());
+            }
+
+            c.close();
+        }
+
+
+        public void populateOrigins(Map sites) {
+            clear();
+
+            // We can now simply populate our array with Site instances
+            Set keys = sites.keySet();
+            Iterator<String> originIter = keys.iterator();
+            while (originIter.hasNext()) {
+                String origin = originIter.next();
+                Site site = (Site) sites.get(origin);
+                add(site);
+            }
+
+            notifyDataSetChanged();
+
+            if (getCount() == 0) {
+                finish(); // we close the screen
+            }
+        }
+
+        public int getCount() {
+            if (mCurrentSite == null) {
+                return super.getCount();
+            }
+            return mCurrentSite.getFeatureCount();
+        }
+
+        public String sizeValueToString(long bytes) {
+            // We display the size in MB, to 1dp, rounding up to the next 0.1MB.
+            // bytes should always be greater than zero.
+            if (bytes <= 0) {
+                Log.e(LOGTAG, "sizeValueToString called with non-positive value");
+                return "0";
+            }
+            float megabytes = (float) bytes / (1024.0F * 1024.0F);
+            int truncated = (int) Math.ceil(megabytes * 10.0F);
+            float result = (float) (truncated / 10.0F);
+            return String.valueOf(result);
+        }
+
+        /*
+         * If we receive the back event and are displaying
+         * site's settings, we want to go back to the main
+         * list view. If not, we just do nothing (see
+         * dispatchKeyEvent() below).
+         */
+        public boolean backKeyPressed() {
+            if (mCurrentSite != null) {
+                mCurrentSite = null;
+                askForOrigins();
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * @hide
+         * Utility function
+         * Set the icon according to the usage
+         */
+        public void setIconForUsage(ImageView usageIcon, long usageInBytes) {
+            float usageInMegabytes = (float) usageInBytes / (1024.0F * 1024.0F);
+            usageIcon.setVisibility(View.VISIBLE);
+
+            // We set the correct icon:
+            // 0 < empty < 0.1MB
+            // 0.1MB < low < 5MB
+            // 5MB < high
+            if (usageInMegabytes <= 0.1) {
+                usageIcon.setImageBitmap(mUsageEmptyIcon);
+            } else if (usageInMegabytes > 0.1 && usageInMegabytes <= 5) {
+                usageIcon.setImageBitmap(mUsageLowIcon);
+            } else if (usageInMegabytes > 5) {
+                usageIcon.setImageBitmap(mUsageHighIcon);
+            }
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view;
+            final TextView title;
+            final TextView subtitle;
+            ImageView icon;
+            final ImageView usageIcon;
+            final ImageView locationIcon;
+
+            if (convertView == null) {
+                view = mInflater.inflate(mResource, parent, false);
+            } else {
+                view = convertView;
+            }
+
+            title = (TextView) view.findViewById(R.id.title);
+            subtitle = (TextView) view.findViewById(R.id.subtitle);
+            icon = (ImageView) view.findViewById(R.id.icon);
+            usageIcon = (ImageView) view.findViewById(R.id.usage_icon);
+            locationIcon = (ImageView) view.findViewById(R.id.location_icon);
+            usageIcon.setVisibility(View.GONE);
+            locationIcon.setVisibility(View.GONE);
+
+            if (mCurrentSite == null) {
+                setTitle(getString(R.string.pref_extras_website_settings));
+
+                Site site = getItem(position);
+                title.setText(site.getPrettyTitle());
+                subtitle.setText(site.getPrettyOrigin());
+                icon.setVisibility(View.VISIBLE);
+                usageIcon.setVisibility(View.INVISIBLE);
+                locationIcon.setVisibility(View.INVISIBLE);
+                Bitmap bmp = site.getIcon();
+                if (bmp == null) {
+                    bmp = mDefaultIcon;
+                }
+                icon.setImageBitmap(bmp);
+                // We set the site as the view's tag,
+                // so that we can get it in onItemClick()
+                view.setTag(site);
+
+                String origin = site.getOrigin();
+                if (site.hasFeature(Site.FEATURE_WEB_STORAGE)) {
+                    WebStorage.getInstance().getUsageForOrigin(origin, new ValueCallback<Long>() {
+                        public void onReceiveValue(Long value) {
+                            if (value != null) {
+                                setIconForUsage(usageIcon, value.longValue());
+                            }
+                        }
+                    });
+                }
+
+                if (site.hasFeature(Site.FEATURE_GEOLOCATION)) {
+                    locationIcon.setVisibility(View.VISIBLE);
+                    GeolocationPermissions.getInstance().getAllowed(origin, new ValueCallback<Boolean>() {
+                        public void onReceiveValue(Boolean allowed) {
+                            if (allowed != null) {
+                                if (allowed.booleanValue()) {
+                                    locationIcon.setImageBitmap(mLocationAllowedIcon);
+                                } else {
+                                    locationIcon.setImageBitmap(mLocationDisallowedIcon);
+                                }
+                            }
+                        }
+                    });
+                }
+            } else {
+                setTitle(mCurrentSite.getPrettyTitle());
+                icon.setVisibility(View.GONE);
+                String origin = mCurrentSite.getOrigin();
+                switch (mCurrentSite.getFeatureByIndex(position)) {
+                    case Site.FEATURE_WEB_STORAGE:
+                        WebStorage.getInstance().getUsageForOrigin(origin, new ValueCallback<Long>() {
+                            public void onReceiveValue(Long value) {
+                                if (value != null) {
+                                    String usage = sizeValueToString(value.longValue()) + " " + sMBStored;
+                                    title.setText(R.string.webstorage_clear_data_title);
+                                    subtitle.setText(usage);
+                                }
+                            }
+                        });
+                        break;
+                    case Site.FEATURE_GEOLOCATION:
+                        title.setText(R.string.geolocation_settings_page_title);
+                        GeolocationPermissions.getInstance().getAllowed(origin, new ValueCallback<Boolean>() {
+                            public void onReceiveValue(Boolean allowed) {
+                                if (allowed != null) {
+                                    if (allowed.booleanValue()) {
+                                        subtitle.setText(R.string.geolocation_settings_page_summary_allowed);
+                                    } else {
+                                        subtitle.setText(R.string.geolocation_settings_page_summary_not_allowed);
+                                    }
+                                }
+                            }
+                        });
+                        break;
+                }
+            }
+
+            return view;
+        }
+
+        public void onItemClick(AdapterView<?> parent,
+                                View view,
+                                int position,
+                                long id) {
+            if (mCurrentSite != null) {
+                switch (mCurrentSite.getFeatureByIndex(position)) {
+                    case Site.FEATURE_WEB_STORAGE:
+                        new AlertDialog.Builder(getContext())
+                            .setTitle(R.string.webstorage_clear_data_dialog_title)
+                            .setMessage(R.string.webstorage_clear_data_dialog_message)
+                            .setPositiveButton(R.string.webstorage_clear_data_dialog_ok_button,
+                                               new AlertDialog.OnClickListener() {
+                                public void onClick(DialogInterface dlg, int which) {
+                                    WebStorage.getInstance().deleteOrigin(mCurrentSite.getOrigin());
+                                    mCurrentSite = null;
+                                    askForOrigins();
+                                }})
+                            .setNegativeButton(R.string.webstorage_clear_data_dialog_cancel_button, null)
+                            .setIcon(android.R.drawable.ic_dialog_alert)
+                            .show();
+                        break;
+                    case Site.FEATURE_GEOLOCATION:
+                        new AlertDialog.Builder(getContext())
+                            .setTitle(R.string.geolocation_settings_page_dialog_title)
+                            .setMessage(R.string.geolocation_settings_page_dialog_message)
+                            .setPositiveButton(R.string.geolocation_settings_page_dialog_ok_button,
+                                               new AlertDialog.OnClickListener() {
+                                public void onClick(DialogInterface dlg, int which) {
+                                    GeolocationPermissions.getInstance().clear(mCurrentSite.getOrigin());
+                                    mCurrentSite = null;
+                                    askForOrigins();
+                                }})
+                            .setNegativeButton(R.string.geolocation_settings_page_dialog_cancel_button, null)
+                            .setIcon(android.R.drawable.ic_dialog_alert)
+                            .show();
+                        break;
+                }
+            } else {
+                mCurrentSite = (Site) view.getTag();
+                notifyDataSetChanged();
+            }
+        }
+    }
+
+    /**
+     * Intercepts the back key to immediately notify
+     * NativeDialog that we are done.
+     */
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK)
+            && (event.getAction() == KeyEvent.ACTION_DOWN)) {
+            if ((mAdapter != null) && (mAdapter.backKeyPressed())){
+                return true; // event consumed
+            }
+        }
+        return super.dispatchKeyEvent(event);
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        if (sMBStored == null) {
+            sMBStored = getString(R.string.webstorage_origin_summary_mb_stored);
+        }
+        mAdapter = new SiteAdapter(this, R.layout.website_settings_row);
+        setListAdapter(mAdapter);
+        getListView().setOnItemClickListener(mAdapter);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.websitesettings, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        // If we aren't listing any sites hide the clear all button (and hence the menu).
+        return mAdapter.getCount() > 0;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.website_settings_menu_clear_all:
+                // Show the prompt to clear all origins of their data and geolocation permissions.
+                new AlertDialog.Builder(this)
+                        .setTitle(R.string.website_settings_clear_all_dialog_title)
+                        .setMessage(R.string.website_settings_clear_all_dialog_message)
+                        .setPositiveButton(R.string.website_settings_clear_all_dialog_ok_button,
+                                new AlertDialog.OnClickListener() {
+                                    public void onClick(DialogInterface dlg, int which) {
+                                        WebStorage.getInstance().deleteAllData();
+                                        GeolocationPermissions.getInstance().clearAll();
+                                        WebStorageSizeManager.resetLastOutOfSpaceNotificationTime();
+                                        mAdapter.askForOrigins();
+                                        finish();
+                                    }})
+                        .setNegativeButton(R.string.website_settings_clear_all_dialog_cancel_button, null)
+                        .setIcon(android.R.drawable.ic_dialog_alert)
+                        .show();
+                return true;
+        }
+        return false;
+    }
+}