Merge "Import revised translations."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c76cb92..0f11f8c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,6 +20,10 @@
 
     <original-package android:name="com.android.browser" />
 
+    <permission android:name="com.android.browser.permission.PRELOAD"
+        android:label="@string/permission_preload_label"
+        android:protectionLevel="signatureOrSystem" />
+
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
@@ -242,6 +246,18 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name=".PreloadRequestReceiver"
+             android:permission="com.android.browser.permission.PRELOAD" >
+             <intent-filter>
+                 <action android:name="android.intent.action.PRELOAD"/>
+                 <data android:scheme="http" />
+             </intent-filter>
+         </receiver>
+
+        <provider android:name=".provider.SnapshotProvider"
+                  android:authorities="com.android.browser.snapshots"
+                  android:exported="false" />
+
     </application>
 
 </manifest>
diff --git a/res/drawable-hdpi/geolocation_permissions_prompt_background.9.png b/res/drawable-hdpi/geolocation_permissions_prompt_background.9.png
new file mode 100644
index 0000000..44a2e2f
--- /dev/null
+++ b/res/drawable-hdpi/geolocation_permissions_prompt_background.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_chevron.png b/res/drawable-hdpi/ic_chevron.png
deleted file mode 100644
index 6dc842f..0000000
--- a/res/drawable-hdpi/ic_chevron.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_chevron.png b/res/drawable-mdpi/ic_chevron.png
deleted file mode 100644
index 786899a..0000000
--- a/res/drawable-mdpi/ic_chevron.png
+++ /dev/null
Binary files differ
diff --git a/res/layout/custom_screen.xml b/res/layout/custom_screen.xml
index 2105501..7a22530 100644
--- a/res/layout/custom_screen.xml
+++ b/res/layout/custom_screen.xml
@@ -14,7 +14,8 @@
      limitations under the License.
 -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android">
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android">
     <FrameLayout android:id="@+id/fullscreen_custom_content"
         android:visibility="gone"
         android:background="@color/black"
@@ -36,5 +37,5 @@
             android:layout_height="match_parent"
         />
     </LinearLayout>
-</FrameLayout>
+</merge>
 
diff --git a/res/layout/geolocation_permissions_prompt.xml b/res/layout/geolocation_permissions_prompt.xml
index babde3a..1920c05 100755
--- a/res/layout/geolocation_permissions_prompt.xml
+++ b/res/layout/geolocation_permissions_prompt.xml
@@ -18,74 +18,67 @@
 
 <com.android.browser.GeolocationPermissionsPrompt
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fitsSystemWindows="true"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content">
+    android:layout_height="wrap_content"
+    android:background="@drawable/geolocation_permissions_prompt_background"
+    android:visibility="gone">
 
-    <!-- Use an inner element as we can't show a hidden outermost element -->
-    <LinearLayout android:id="@+id/inner"
-        android:orientation="vertical"
+    <!-- 'google.com wants to know your location' -->
+    <TextView android:id="@+id/message"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@color/black"
-        android:paddingTop="1px"
-        android:visibility="gone">
+        android:singleLine="true"
+        android:scrollHorizontally="true"
+        android:padding="6dip"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
 
-        <!-- White line -->
-        <View
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="1px"
-            android:background="@color/white" />
+    <CheckBox android:id="@+id/remember"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/message"
+        android:layout_alignLeft="@id/message" />
+    <TextView
+        android:paddingLeft="4dip"
+        android:text="@string/geolocation_permissions_prompt_remember"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_alignBaseline="@id/remember"
+        android:layout_toRightOf="@id/remember" />
 
-        <!-- Container for content -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/remember"
+        android:orientation="vertical"
+        android:divider="?android:attr/dividerHorizontal"
+        android:showDividers="beginning"
+        android:dividerPadding="16dip"
+        android:background="@null">
         <LinearLayout
-            android:orientation="vertical"
+            style="?android:attr/buttonBarStyle"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="@color/geolocation_permissions_prompt_background"
-            android:padding="6dip">
-
-            <!-- 'google.com wants to know your location' -->
-            <TextView android:id="@+id/message"
-                android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingLeft="2dip"
+            android:paddingRight="2dip"
+            android:measureWithLargestChild="true"
+            android:background="@null">
+            <Button
+                android:id="@+id/dont_share_button"
+                style="?android:attr/buttonBarButtonStyle"
+                android:layout_weight="1"
+                android:layout_width="0dip"
                 android:layout_height="wrap_content"
-                android:textSize="14dip"
-                android:textColor="@color/black" />
-
-            <!-- Checkbox -->
-            <LinearLayout
-                android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <CheckBox android:id="@+id/remember"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content" />
-                <TextView
-                    android:paddingLeft="4dip"
-                    android:text="@string/geolocation_permissions_prompt_remember"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textSize="14dip"
-                    android:textColor="@color/black" />
-            </LinearLayout>
-
-            <!-- Buttons -->
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <Button android:id="@+id/share_button"
-                    android:text="@string/geolocation_permissions_prompt_share"
-                    android:layout_weight="1"
-                    android:layout_width="0dip"
-                    android:layout_height="wrap_content" />
-                <Button android:id="@+id/dont_share_button"
-                    android:text="@string/geolocation_permissions_prompt_dont_share"
-                    android:layout_weight="1"
-                    android:layout_width="0dip"
-                    android:layout_height="wrap_content" />
-            </LinearLayout>
-
+                android:text="@string/geolocation_permissions_prompt_dont_share" />
+            <Button
+                android:id="@+id/share_button"
+                style="?android:attr/buttonBarButtonStyle"
+                android:layout_weight="1"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:text="@string/geolocation_permissions_prompt_share" />
         </LinearLayout>
     </LinearLayout>
+
 </com.android.browser.GeolocationPermissionsPrompt>
diff --git a/res/layout/pref_homepage_buttons.xml b/res/layout/pref_homepage_buttons.xml
deleted file mode 100644
index fe21beb..0000000
--- a/res/layout/pref_homepage_buttons.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
-
-
-     This layout provides the structure for a browser tab. A tab contains the
-     WebView and any number of other UI elements specific to that tab.
-     Currently, the only such element is the Geolocation permissions prompt.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="horizontal"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center_horizontal">
-
-    <Button android:id="@+id/use_current"
-        android:text="@string/pref_use_current"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginRight="16dp" />
-
-    <Button android:id="@+id/use_default"
-        android:text="@string/pref_use_default"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
-
-</LinearLayout>
diff --git a/res/layout/snapshot_item.xml b/res/layout/snapshot_item.xml
new file mode 100644
index 0000000..2fc6ca8
--- /dev/null
+++ b/res/layout/snapshot_item.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/combo_horizontalSpacing">
+    <ImageView
+        android:id="@+id/thumb"
+        android:src="@drawable/thumbnail_bookmarks_widget_no_bookmark_holo"
+        android:layout_width="@dimen/bookmarkThumbnailWidth"
+        android:layout_height="@dimen/bookmarkThumbnailHeight"
+        android:scaleType="centerCrop"
+        android:cropToPadding="true"
+        android:background="@drawable/border_thumb_bookmarks_widget_holo" />
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBottom="@id/thumb"
+        android:layout_alignLeft="@id/thumb"
+        android:layout_alignRight="@id/thumb"
+        android:background="@drawable/overlay_url_bookmark_widget_holo"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:textSize="12sp"
+        android:typeface="sans"
+        android:textColor="@android:color/white"
+        android:paddingLeft="6dip"
+        android:paddingRight="2dip"
+        android:gravity="center_vertical" />
+    <ImageView
+        android:id="@+id/divider"
+        android:src="?android:attr/dividerVertical"
+        android:layout_width="wrap_content"
+        android:layout_height="24dip"
+        android:layout_below="@+id/thumb"
+        android:layout_alignLeft="@+id/thumb"
+        android:scaleType="fitXY"
+        android:layout_marginTop="12dip" />
+    <TextView android:id="@+id/date"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/divider"
+        android:layout_alignTop="@id/divider"
+        android:layout_alignBottom="@id/divider"
+        android:paddingLeft="8dip"
+        android:gravity="center_vertical"
+        android:typeface="sans"
+        android:textSize="14sp"
+        android:textColor="#AAAAAA" />
+    <TextView android:id="@+id/size"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignTop="@id/divider"
+        android:layout_alignBottom="@id/divider"
+        android:layout_alignRight="@+id/thumb"
+        android:paddingRight="2dip"
+        android:gravity="center_vertical"
+        android:typeface="sans"
+        android:textSize="14sp"
+        android:textColor="#AAAAAA" />
+</RelativeLayout>
diff --git a/res/layout/snapshots.xml b/res/layout/snapshots.xml
new file mode 100644
index 0000000..48d2883
--- /dev/null
+++ b/res/layout/snapshots.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="@dimen/combo_paddingTop">
+
+    <GridView
+        android:id="@+id/grid"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_horizontal"
+        android:numColumns="auto_fit"
+        android:stretchMode="none"
+        android:gravity="center" />
+    <TextView
+        android:id="@android:id/empty"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="@string/empty_bookmarks_folder"
+        android:visibility="gone" />
+
+</FrameLayout>
diff --git a/res/layout/tab_title.xml b/res/layout/tab_title.xml
index fcae2bc..7ac2ba0 100644
--- a/res/layout/tab_title.xml
+++ b/res/layout/tab_title.xml
@@ -17,13 +17,6 @@
     android:gravity="center_vertical"
     android:orientation="horizontal">
     <ImageView
-        android:id="@+id/chevron"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center_vertical"
-        android:src="@drawable/ic_chevron"
-        android:visibility="gone" />
-    <ImageView
         android:id="@+id/incognito"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
diff --git a/res/layout/title_bar.xml b/res/layout/title_bar.xml
index 649a4a0..9096418 100644
--- a/res/layout/title_bar.xml
+++ b/res/layout/title_bar.xml
@@ -24,11 +24,7 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/toolbar_height"
         android:orientation="horizontal"
-        android:background="@drawable/bg_urlbar"
-        android:paddingLeft="4dip"
-        android:paddingRight="4dip"
-        android:paddingTop="2dip"
-        android:paddingBottom="2dip">
+        android:background="@drawable/bg_urlbar">
         <LinearLayout
             android:id="@+id/title_bg"
             android:layout_width="0dip"
@@ -36,53 +32,63 @@
             android:layout_height="match_parent"
             android:gravity="center_vertical"
             android:orientation="horizontal">
+            <FrameLayout
+                android:id="@+id/iconcombo"
+                android:layout_width="52dip"
+                android:layout_height="match_parent"
+                style="@style/HoloButton">
+                <ImageView
+                    android:id="@+id/favicon"
+                    android:layout_width="36dip"
+                    android:layout_height="36dip"
+                    android:paddingLeft="8dip"
+                    android:paddingRight="8dip"
+                    android:layout_gravity="center_vertical" />
+                <ImageView
+                    android:id="@+id/lock"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom|right"
+                    android:visibility="gone" />
+            </FrameLayout>
             <ImageView
-                android:id="@+id/favicon"
-                android:layout_width="20dip"
-                android:layout_height="20dip" />
-            <ImageView
-                android:id="@+id/lock"
+                android:id="@+id/stop"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginLeft="2dip"
-                android:visibility="gone" />
+                android:layout_gravity="center_vertical"
+                android:src="@drawable/ic_stop_holo_dark"
+                style="@style/HoloButton" />
             <com.android.browser.UrlInputView
                 android:id="@+id/url"
                 android:focusable="true"
                 android:layout_width="0dip"
                 android:layout_weight="1.0"
                 android:layout_height="match_parent"
-                android:layout_marginLeft="2dip"
-                android:paddingLeft="4dip"
-                android:paddingRight="4dip"
-                android:background="@*android:drawable/edit_text_holo_dark"
+                android:fadingEdge="horizontal"
+                android:fadingEdgeLength="24dip"
                 android:textAppearance="?android:attr/textAppearanceMedium"
                 android:hint="@string/search_hint"
                 android:singleLine="true"
                 android:ellipsize="end"
                 android:lines="1"
                 android:scrollHorizontally="true"
-                android:inputType="textUri"
+                android:inputType="text"
                 android:imeOptions="actionGo"
-                style="@style/Suggestions" />
+                style="@style/Suggestions"
+                android:background="@null" />
+            <ImageView
+                android:id="@+id/voice"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:src="@drawable/ic_voice_search_holo_dark"
+                style="@style/HoloButton"
+                android:visibility="gone" />
         </LinearLayout>
-        <ImageView
-            android:id="@+id/voice"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:src="@drawable/ic_voice_search_holo_dark"
-            android:visibility="gone" />
-        <ImageView
-            android:id="@+id/stop"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:src="@drawable/ic_stop_holo_dark" />
         <ImageButton
-            android:id="@+id/forward"
+            android:id="@+id/tab_switcher"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:src="@drawable/ic_forward_holo_dark"
+            android:src="@drawable/ic_windows_holo_dark"
             style="@style/HoloButton" />
     </LinearLayout>
     <LinearLayout
diff --git a/res/menu-sw600dp/browser.xml b/res/menu-sw600dp/browser.xml
index 23366e7..29b6117 100644
--- a/res/menu-sw600dp/browser.xml
+++ b/res/menu-sw600dp/browser.xml
@@ -32,11 +32,8 @@
             android:title="@string/share_page"
             android:icon="@drawable/ic_share_holo_dark"
             android:alphabeticShortcut="s" />
-        <item android:id="@+id/save_webarchive_menu_id"
-            android:title="@string/menu_save_webarchive" />
-        <item
-            android:id="@+id/freeze_tab_menu_id"
-            android:title="@string/menu_freeze_tab" />
+        <item android:id="@+id/save_snapshot_menu_id"
+            android:title="@string/menu_save_snapshot" />
         <item android:id="@+id/page_info_menu_id"
             android:title="@string/page_info"
             android:icon="@drawable/ic_pageinfo_holo_dark"
diff --git a/res/menu/browser.xml b/res/menu/browser.xml
index 6eadcba..630bb87 100644
--- a/res/menu/browser.xml
+++ b/res/menu/browser.xml
@@ -62,11 +62,8 @@
             android:icon="@drawable/ic_share_holo_dark"
             android:alphabeticShortcut="s" />
         <item
-            android:id="@+id/save_webarchive_menu_id"
-            android:title="@string/menu_save_webarchive" />
-        <item
-            android:id="@+id/freeze_tab_menu_id"
-            android:title="@string/menu_freeze_tab" />
+            android:id="@+id/save_snapshot_menu_id"
+            android:title="@string/menu_save_snapshot" />
         <item
             android:id="@+id/page_info_menu_id"
             android:title="@string/page_info"
diff --git a/res/menu/snapshots_context.xml b/res/menu/snapshots_context.xml
new file mode 100644
index 0000000..01880dd
--- /dev/null
+++ b/res/menu/snapshots_context.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:id="@+id/CONTEXT_MENU">
+        <item
+            android:id="@+id/delete_context_menu_id"
+            android:title="@string/remove_bookmark"/>
+    </group>
+</menu>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 322a80a..4ab971f 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -23,8 +23,6 @@
     <color name="white">#ffffffff</color>
     <color name="black">#ff000000</color>
     
-    <color name="geolocation_permissions_prompt_background">#ffdddddd</color>
-
     <color name="bookmarkWidgetHeader">#383847</color>
     <color name="bookmarkWidgetDivider">#383847</color>
     <color name="bookmarkWidgetItemBackground">#2b2b3c</color>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e1fe0c5..d0e81ac 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -34,6 +34,8 @@
     <string name="tab_most_visited">Most visited</string>
     <!-- Name of tab containing the user's complete history, organized by time of last visit -->
     <string name="tab_history">History</string>
+    <!-- Name of tab containing the user's saved pages, organized by time created [CHAR LIMIT=20] -->
+    <string name="tab_snapshots">Saved Pages</string>
     <!-- Toast shown when a history item's star is clicked, converting it to a bookmark -->
     <string name="added_to_bookmarks">Added to bookmarks</string>
     <!-- Toast shown when a history item's star is clicked off, removing its bookmark -->
@@ -213,15 +215,10 @@
     <string name="copy_page_url">Copy page url</string>
     <!-- Menu item -->
     <string name="share_page">Share page</string>
-    <!-- Menu item to freeze a tab. This will make a view-only
-            snapshot of the page in a new tab. [CHAR LIMIT=50] -->
-    <string name="menu_freeze_tab">Freeze tab</string>
-    <!-- Menu item for saving a page. [CHAR LIMIT=30] -->
-    <string name="menu_save_webarchive">Save page</string>
-    <!-- Toast informing the user that the page has been saved. [CHAR LIMIT=50] -->
-    <string name="webarchive_saved">Page saved.</string>
-    <!-- Toast informing the user that saving the page has failed. [CHAR LIMIT=50] -->
-    <string name="webarchive_failed">Failed to save page.</string>
+    <!-- Menu item for saving a page for offline reading. This is a view-only snapshot of the page. [CHAR LIMIT=50] -->
+    <string name="menu_save_snapshot">Save for offline reading</string>
+    <!-- Toast informing the user that saving the page for offline reading has failed. [CHAR LIMIT=50] -->
+    <string name="snapshot_failed">Failed to save for offline reading.</string>
     <!-- The number of bookmarks in a folder [CHAR LIMT=50] -->
     <string name="contextheader_folder_bookmarkcount"><xliff:g id="bookmark_count">%d</xliff:g> bookmarks</string>
     <!-- No bookmarks in the folder [CHAR LIMIT=50] -->
@@ -645,6 +642,12 @@
     <!-- Summary for the fullscreen lab feature [CHAR LIMIT=120] -->
     <string name="pref_lab_fullscreen_summary">
       Use fullscreen mode to hide the status bar.</string>
+    <!-- Title for bandwidth management preference [CHAR LIMIT=25] -->
+    <string name="pref_data_title">Bandwidth Management</string>
+    <!-- Title for search preloading [CHAR LIMIT=40] -->
+    <string name="pref_data_preload_title">Search result preloading</string>
+    <!-- Summary for search preloading [CHAR LIMIT=80] -->
+    <string name="pref_data_preload_summary">Allow the browser to preload high confidence search results in the background</string>
     <!-- Title for a dialog displayed when the browser has a data connectivity
             problem -->
     <string name="browserFrameNetworkErrorLabel">Data connectivity problem</string>
@@ -822,7 +825,7 @@
         http://www.google.com/webhp?client={CID}&amp;source=android-home</string>
     <!-- The default url for the instant_base_page. -->
     <string name="instant_base" translatable="false">
-        http://www.google.com/webhp?client={CID}&amp;source=android-omnibox-instant&amp;ion=1</string>
+        http://www.google.com/webhp?client={CID}&amp;source=android-instant&amp;ion=1</string>
 
     <!-- Bookmarks -->
     <string-array name="bookmarks" translatable="false">
@@ -995,4 +998,6 @@
     <string name="ua_switcher_mobile">Mobile</string>
     <!-- Popup menu option that allows the user to select the desktop version of a webpage [CHAR LIMIT=50] -->
     <string name="ua_switcher_desktop">Desktop</string>
+    <!-- Preload permission label [CHAR LIMIT=40] -->
+    <string name="permission_preload_label">Preload results</string>
 </resources>
diff --git a/res/xml/bandwidth_preferences.xml b/res/xml/bandwidth_preferences.xml
new file mode 100644
index 0000000..0eb4c21
--- /dev/null
+++ b/res/xml/bandwidth_preferences.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <CheckBoxPreference
+        android:key="preload_enabled"
+        android:title="@string/pref_data_preload_title"
+        android:summary="@string/pref_data_preload_summary"
+        android:defaultValue="false" />
+
+</PreferenceScreen>
diff --git a/res/xml/preference_headers.xml b/res/xml/preference_headers.xml
index e58b90a..2c80835 100644
--- a/res/xml/preference_headers.xml
+++ b/res/xml/preference_headers.xml
@@ -32,6 +32,10 @@
         android:title="@string/pref_extras_title"
     />
 
+    <header android:fragment="com.android.browser.preferences.BandwidthPreferencesFragment"
+        android:title="@string/pref_data_title"
+    />
+
     <header android:fragment="com.android.browser.preferences.LabPreferencesFragment"
         android:title="@string/pref_lab_title"
     />
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index bcb18f1..0777efd 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -16,11 +16,6 @@
 
 package com.android.browser;
 
-import com.android.browser.BrowserWebView.ScrollListener;
-import com.android.browser.Tab.LockIcon;
-import com.android.internal.view.menu.MenuBuilder;
-
-import android.animation.ObjectAnimator;
 import android.app.Activity;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
@@ -40,9 +35,11 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.Menu;
+import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.WindowManager;
@@ -54,12 +51,15 @@
 import android.widget.LinearLayout;
 import android.widget.Toast;
 
+import com.android.browser.Tab.LockIcon;
+import com.android.internal.view.menu.MenuBuilder;
+
 import java.util.List;
 
 /**
  * UI interface definitions
  */
-public abstract class BaseUi implements UI, WebViewFactory, ScrollListener {
+public abstract class BaseUi implements UI, OnTouchListener {
 
     private static final String LOGTAG = "BaseUi";
 
@@ -87,7 +87,6 @@
     private Drawable mMixLockIcon;
     protected Drawable mGenericFavicon;
 
-    private FrameLayout mBrowserFrameLayout;
     protected FrameLayout mContentView;
     protected FrameLayout mCustomViewContainer;
 
@@ -101,7 +100,7 @@
 
     private Toast mStopToast;
 
-    private int mLastScrollY;
+    private float mInitialY;
     private int mTitlebarScrollTriggerSlop;
 
     // the default <video> poster
@@ -110,6 +109,7 @@
     private View mVideoProgressView;
 
     private boolean mActivityPaused;
+    protected boolean mUseQuickControls;
 
     public BaseUi(Activity browser, UiController controller) {
         mActivity = browser;
@@ -123,15 +123,14 @@
 
         FrameLayout frameLayout = (FrameLayout) mActivity.getWindow()
                 .getDecorView().findViewById(android.R.id.content);
-        mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(mActivity)
-                .inflate(R.layout.custom_screen, null);
-        mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(
+        LayoutInflater.from(mActivity)
+                .inflate(R.layout.custom_screen, frameLayout);
+        mContentView = (FrameLayout) frameLayout.findViewById(
                 R.id.main_content);
-        mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout
+        mErrorConsoleContainer = (LinearLayout) frameLayout
                 .findViewById(R.id.error_console);
-        mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
+        mCustomViewContainer = (FrameLayout) frameLayout
                 .findViewById(R.id.fullscreen_custom_content);
-        frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
         setFullscreen(BrowserSettings.getInstance().useFullscreen());
         mGenericFavicon = res.getDrawable(
                 R.drawable.app_web_browser_sm);
@@ -143,41 +142,6 @@
                 config.getScaledTouchSlop());
     }
 
-    @Override
-    public WebView createWebView(boolean privateBrowsing) {
-        // Create a new WebView
-        BrowserWebView w = new BrowserWebView(mActivity, null,
-                android.R.attr.webViewStyle, privateBrowsing);
-        initWebViewSettings(w);
-        return w;
-    }
-
-    @Override
-    public WebView createSubWebView(boolean privateBrowsing) {
-        return createWebView(privateBrowsing);
-    }
-
-    /**
-     * common webview initialization
-     * @param w the webview to initialize
-     */
-    protected void initWebViewSettings(WebView w) {
-        w.setScrollbarFadingEnabled(true);
-        w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
-        w.setMapTrackballToArrowKeys(false); // use trackball directly
-        // Enable the built-in zoom
-        w.getSettings().setBuiltInZoomControls(true);
-        boolean supportsMultiTouch = mActivity.getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
-        w.getSettings().setDisplayZoomControls(!supportsMultiTouch);
-        w.setExpandedTileBounds(true);  // smoother scrolling
-
-        // Add this WebView to the settings observer list and update the
-        // settings
-        final BrowserSettings s = BrowserSettings.getInstance();
-        s.startManagingSettings(w.getSettings());
-    }
-
     private void cancelStopToast() {
         if (mStopToast != null) {
             mStopToast.cancel();
@@ -269,8 +233,16 @@
         mHandler.removeMessages(MSG_HIDE_TITLEBAR);
         if ((tab != mActiveTab) && (mActiveTab != null)) {
             removeTabFromContentView(mActiveTab);
+            WebView web = mActiveTab.getWebView();
+            if (web != null) {
+                web.setOnTouchListener(null);
+            }
         }
         mActiveTab = tab;
+        WebView web = mActiveTab.getWebView();
+        if (web != null && !mUseQuickControls) {
+            web.setOnTouchListener(this);
+        }
         attachTabToContentView(tab);
         setShouldShowErrorConsole(tab, mUiController.shouldShowErrorConsole());
         onTabDataChanged(tab);
@@ -278,6 +250,10 @@
         boolean incognito = mActiveTab.getWebView().isPrivateBrowsingEnabled();
         getTitleBar().setIncognitoMode(incognito);
         updateAutoLogin(tab, false);
+        if (web != null && web.getVisibleTitleHeight()
+                != getTitleBar().getEmbeddedHeight()) {
+            showTitleBarForDuration();
+        }
     }
 
     Tab getActiveTab() {
@@ -374,7 +350,7 @@
             // The tab consists of a container view, which contains the main
             // WebView, as well as any other UI elements associated with the tab.
             container = mActivity.getLayoutInflater().inflate(R.layout.tab,
-                    null);
+                    mContentView, false);
             tab.setViewContainer(container);
         }
         if (tab.getWebView() != webView) {
@@ -504,15 +480,13 @@
     }
 
     @Override
-    public void showComboView(boolean startWithHistory, Bundle extras) {
+    public void showComboView(ComboViews startingView, Bundle extras) {
         if (mComboView != null) {
             return;
         }
         mComboView = new CombinedBookmarkHistoryView(mActivity,
                 mUiController,
-                startWithHistory ?
-                        CombinedBookmarkHistoryView.FRAGMENT_ID_HISTORY
-                        : CombinedBookmarkHistoryView.FRAGMENT_ID_BOOKMARKS,
+                startingView,
                 extras);
         FrameLayout wrapper =
             (FrameLayout) mContentView.findViewById(R.id.webview_wrapper);
@@ -523,11 +497,6 @@
         if (mActiveTab != null) {
             mActiveTab.putInBackground();
         }
-        mComboView.setAlpha(0f);
-        ObjectAnimator anim = ObjectAnimator.ofFloat(mComboView, "alpha", 0f, 1f);
-        Resources res = mActivity.getResources();
-        anim.setDuration(res.getInteger(R.integer.comboViewFadeInDuration));
-        anim.start();
         mContentView.addView(mComboView, COVER_SCREEN_PARAMS);
     }
 
@@ -854,29 +823,39 @@
         }
     }
 
+    private void showTitleBarForDuration() {
+        mHandler.removeMessages(MSG_HIDE_TITLEBAR);
+        showTitleBar();
+        Message msg = Message.obtain(mHandler, MSG_HIDE_TITLEBAR);
+        mHandler.sendMessageDelayed(msg, HIDE_TITLEBAR_DELAY);
+    }
+
     @Override
-    public void onScroll(int visibleTitleHeight, boolean userInitiated) {
-        WebView view = mActiveTab != null ? mActiveTab.getWebView() : null;
-        if (view == null) {
-            return;
-        }
-        int scrollY = view.getScrollY();
-        if (isTitleBarShowing()
-                || scrollY < (mLastScrollY - mTitlebarScrollTriggerSlop)) {
-            mLastScrollY = scrollY;
-            if (visibleTitleHeight == 0 && userInitiated) {
+    public boolean onTouch(View v, MotionEvent event) {
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_DOWN:
+            mInitialY = event.getY();
+            break;
+        case MotionEvent.ACTION_MOVE:
+            WebView web = (WebView) v;
+            if (!isTitleBarShowing()
+                    && web.getVisibleTitleHeight() == 0
+                    && event.getY() > (mInitialY + mTitlebarScrollTriggerSlop)) {
                 mHandler.removeMessages(MSG_HIDE_TITLEBAR);
                 showTitleBar();
+            } else if (event.getY() < mInitialY) {
+                mInitialY = event.getY();
+            }
+            break;
+        case MotionEvent.ACTION_CANCEL:
+        case MotionEvent.ACTION_UP:
+            if (isTitleBarShowing()) {
                 Message msg = Message.obtain(mHandler, MSG_HIDE_TITLEBAR);
                 mHandler.sendMessageDelayed(msg, HIDE_TITLEBAR_DELAY);
-            } else if (visibleTitleHeight == getTitleBar().getEmbeddedHeight()
-                    && mHandler.hasMessages(MSG_HIDE_TITLEBAR)) {
-                mHandler.removeMessages(MSG_HIDE_TITLEBAR);
-                suggestHideTitleBar();
             }
-        } else if (scrollY > mLastScrollY) {
-            mLastScrollY = scrollY;
+            break;
         }
+        return false;
     }
 
     private Handler mHandler = new Handler() {
diff --git a/src/com/android/browser/Browser.java b/src/com/android/browser/Browser.java
index 65eb0ce..c4412e2 100644
--- a/src/com/android/browser/Browser.java
+++ b/src/com/android/browser/Browser.java
@@ -18,6 +18,7 @@
 
 import android.app.Application;
 import android.content.Intent;
+import android.os.AsyncTask;
 import android.util.Log;
 import android.webkit.CookieSyncManager;
 
@@ -52,12 +53,15 @@
         if (LOGV_ENABLED)
             Log.v(LOGTAG, "Browser.onCreate: this=" + this);
 
+        // Fix AsyncTask to use multiple threads
+        AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
         // Fix heap utilization for better heap size characteristics.
         VMRuntime.getRuntime().setTargetHeapUtilization(
                 TARGET_HEAP_UTILIZATION);
         // create CookieSyncManager with current Context
         CookieSyncManager.createInstance(this);
         BrowserSettings.initialize(getApplicationContext());
+        Preloader.initialize(getApplicationContext());
     }
 
     static Intent createBrowserViewIntent() {
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index dbcae2e..13b8b06 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -92,7 +92,6 @@
             mUi = new PhoneUi(this, mController);
         }
         mController.setUi(mUi);
-        mController.setWebViewFactory((BaseUi) mUi);
 
         Bundle state = getIntent().getBundleExtra(EXTRA_STATE);
         if (state != null && icicle == null) {
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 4928e61..3a6349a 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -689,4 +689,11 @@
         return mPrefs.getBoolean(PREF_REMEMBER_PASSWORDS, true);
     }
 
+    // -----------------------------
+    // getter/setters for bandwidth_preferences.xml
+    // -----------------------------
+
+    public boolean isPreloadEnabled() {
+        return mPrefs.getBoolean(PREF_DATA_PRELOAD, false);
+    }
 }
diff --git a/src/com/android/browser/BrowserSnapshotPage.java b/src/com/android/browser/BrowserSnapshotPage.java
new file mode 100644
index 0000000..72da15b
--- /dev/null
+++ b/src/com/android/browser/BrowserSnapshotPage.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.browser;
+
+import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import com.android.browser.provider.SnapshotProvider.Snapshots;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+public class BrowserSnapshotPage extends Fragment implements
+        LoaderCallbacks<Cursor>, OnItemClickListener {
+
+    public static final String EXTRA_ANIMATE_ID = "animate_id";
+
+    private static final int LOADER_SNAPSHOTS = 1;
+    private static final String[] PROJECTION = new String[] {
+        Snapshots._ID,
+        Snapshots.TITLE,
+        "length(" + Snapshots.VIEWSTATE + ")",
+        Snapshots.THUMBNAIL,
+        Snapshots.FAVICON,
+        Snapshots.URL,
+        Snapshots.DATE_CREATED,
+    };
+    private static final int SNAPSHOT_TITLE = 1;
+    private static final int SNAPSHOT_VIEWSTATE_LENGTH = 2;
+    private static final int SNAPSHOT_THUMBNAIL = 3;
+    private static final int SNAPSHOT_FAVICON = 4;
+    private static final int SNAPSHOT_URL = 5;
+    private static final int SNAPSHOT_DATE_CREATED = 6;
+
+    GridView mGrid;
+    View mEmpty;
+    SnapshotAdapter mAdapter;
+    UiController mUiController;
+
+    public static BrowserSnapshotPage newInstance(UiController uiController,
+            Bundle extras) {
+        BrowserSnapshotPage instance = new BrowserSnapshotPage();
+        instance.mUiController = uiController;
+        instance.setArguments(extras);
+        return instance;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.snapshots, container, false);
+        mEmpty = view.findViewById(android.R.id.empty);
+        mGrid = (GridView) view.findViewById(R.id.grid);
+        setupGrid(inflater);
+        getLoaderManager().initLoader(LOADER_SNAPSHOTS, null, this);
+        return view;
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        getLoaderManager().destroyLoader(LOADER_SNAPSHOTS);
+        mAdapter.changeCursor(null);
+        mAdapter = null;
+    }
+
+    void setupGrid(LayoutInflater inflater) {
+        View item = inflater.inflate(R.layout.snapshot_item, mGrid, false);
+        int mspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        item.measure(mspec, mspec);
+        int width = item.getMeasuredWidth();
+        mGrid.setColumnWidth(width);
+        mGrid.setOnItemClickListener(this);
+        mGrid.setOnCreateContextMenuListener(this);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        if (id == LOADER_SNAPSHOTS) {
+            return new CursorLoader(getActivity(),
+                    Snapshots.CONTENT_URI, PROJECTION,
+                    null, null, Snapshots.DATE_CREATED + " DESC");
+        }
+        return null;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        if (loader.getId() == LOADER_SNAPSHOTS) {
+            if (mAdapter == null) {
+                mAdapter = new SnapshotAdapter(getActivity(), data);
+                mGrid.setAdapter(mAdapter);
+            } else {
+                mAdapter.changeCursor(data);
+            }
+            boolean empty = mAdapter.isEmpty();
+            mGrid.setVisibility(empty ? View.GONE : View.VISIBLE);
+            mEmpty.setVisibility(empty ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v,
+            ContextMenuInfo menuInfo) {
+        MenuInflater inflater = getActivity().getMenuInflater();
+        inflater.inflate(R.menu.snapshots_context, menu);
+        // Create the header, re-use BookmarkItem (has the layout we want)
+        BookmarkItem header = new BookmarkItem(getActivity());
+        AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
+        populateBookmarkItem(mAdapter.getItem(info.position), header);
+        menu.setHeaderView(header);
+    }
+
+    private void populateBookmarkItem(Cursor cursor, BookmarkItem item) {
+        item.setName(cursor.getString(SNAPSHOT_TITLE));
+        item.setUrl(cursor.getString(SNAPSHOT_URL));
+        item.setFavicon(getBitmap(cursor, SNAPSHOT_FAVICON));
+    }
+
+    static Bitmap getBitmap(Cursor cursor, int columnIndex) {
+        byte[] data = cursor.getBlob(columnIndex);
+        if (data == null) {
+            return null;
+        }
+        return BitmapFactory.decodeByteArray(data, 0, data.length);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.delete_context_menu_id) {
+            AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+            deleteSnapshot(info.id);
+            return true;
+        }
+        return super.onContextItemSelected(item);
+    }
+
+    void deleteSnapshot(long id) {
+        final Uri uri = ContentUris.withAppendedId(Snapshots.CONTENT_URI, id);
+        final ContentResolver cr = getActivity().getContentResolver();
+        new Thread() {
+            @Override
+            public void run() {
+                cr.delete(uri, null, null);
+            }
+        }.start();
+
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position,
+            long id) {
+        mUiController.removeComboView();
+        mUiController.createNewSnapshotTab(id, true);
+    }
+
+    private static class SnapshotAdapter extends ResourceCursorAdapter {
+
+        public SnapshotAdapter(Context context, Cursor c) {
+            super(context, R.layout.snapshot_item, c, 0);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            ImageView thumbnail = (ImageView) view.findViewById(R.id.thumb);
+            byte[] thumbBlob = cursor.getBlob(SNAPSHOT_THUMBNAIL);
+            if (thumbBlob == null) {
+                thumbnail.setImageResource(R.drawable.browser_thumbnail);
+            } else {
+                Bitmap thumbBitmap = BitmapFactory.decodeByteArray(
+                        thumbBlob, 0, thumbBlob.length);
+                thumbnail.setImageBitmap(thumbBitmap);
+            }
+            TextView title = (TextView) view.findViewById(R.id.title);
+            title.setText(cursor.getString(SNAPSHOT_TITLE));
+            TextView size = (TextView) view.findViewById(R.id.size);
+            int stateLen = cursor.getInt(SNAPSHOT_VIEWSTATE_LENGTH);
+            size.setText(String.format("%.2fMB", stateLen / 1024f / 1024f));
+            long timestamp = cursor.getLong(SNAPSHOT_DATE_CREATED);
+            TextView date = (TextView) view.findViewById(R.id.date);
+            DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
+            date.setText(dateFormat.format(new Date(timestamp)));
+        }
+
+        @Override
+        public Cursor getItem(int position) {
+            return (Cursor) super.getItem(position);
+        }
+    }
+
+}
diff --git a/src/com/android/browser/BrowserWebView.java b/src/com/android/browser/BrowserWebView.java
index 55dd24a..80f4a53 100644
--- a/src/com/android/browser/BrowserWebView.java
+++ b/src/com/android/browser/BrowserWebView.java
@@ -20,7 +20,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.view.View;
 import android.webkit.WebView;
 
@@ -31,12 +30,9 @@
 /**
  * Manage WebView scroll events
  */
-public class BrowserWebView extends WebView implements Runnable {
+public class BrowserWebView extends WebView {
 
-    private ScrollListener mScrollListener;
-    private boolean mIsCancelled;
     private boolean mBackgroundRemoved = false;
-    private boolean mUserInitiated = false;
     private TitleBarBase mTitleBar;
     private int mCaptureSize;
     private Bitmap mCapture;
@@ -104,13 +100,6 @@
         return (mTitleBar != null) ? mTitleBar.getEmbeddedHeight() : 0;
     }
 
-    // scroll runnable implementation
-    public void run() {
-        if (!mIsCancelled && (mScrollListener != null)) {
-            mScrollListener.onScroll(getVisibleTitleHeight(), mUserInitiated);
-        }
-    }
-
     void hideEmbeddedTitleBar() {
         scrollBy(0, getVisibleTitleHeight());
     }
@@ -119,53 +108,12 @@
     public void setEmbeddedTitleBar(final View title) {
         super.setEmbeddedTitleBar(title);
         mTitleBar = (TitleBarBase) title;
-        if (title != null && mScrollListener != null) {
-            // allow the scroll listener to initialize its state
-            post(this);
-        }
     }
 
     public boolean hasTitleBar() {
         return (mTitleBar != null);
     }
 
-    @Override
-    public boolean onTouchEvent(MotionEvent evt) {
-        if (MotionEvent.ACTION_DOWN == evt.getActionMasked()) {
-            mUserInitiated = true;
-        } else if (MotionEvent.ACTION_UP == evt.getActionMasked()
-                || (MotionEvent.ACTION_CANCEL == evt.getActionMasked())) {
-            mUserInitiated = false;
-        }
-        return super.onTouchEvent(evt);
-    }
-
-    @Override
-    public void stopScroll() {
-        mIsCancelled = true;
-        super.stopScroll();
-    }
-
-    @Override
-    protected void onScrollChanged(int l, final int t, int ol, int ot) {
-        super.onScrollChanged(l, t, ol, ot);
-        if (!mIsCancelled) {
-            post(this);
-        } else {
-            mIsCancelled = false;
-        }
-    }
-
-    void setScrollListener(ScrollListener l) {
-        mScrollListener = l;
-    }
-
-    // callback for scroll events
-
-    interface ScrollListener {
-        public void onScroll(int visibleTitleHeight, boolean userInitiated);
-    }
-
     protected Bitmap capture() {
         if (mCapture == null) return null;
         Canvas c = new Canvas(mCapture);
diff --git a/src/com/android/browser/BrowserWebViewFactory.java b/src/com/android/browser/BrowserWebViewFactory.java
new file mode 100644
index 0000000..fbd26a9
--- /dev/null
+++ b/src/com/android/browser/BrowserWebViewFactory.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2011 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.pm.PackageManager;
+import android.util.AttributeSet;
+import android.view.View;
+import android.webkit.WebView;
+
+/**
+ * Web view factory class for creating {@link BrowserWebView}'s.
+ */
+public class BrowserWebViewFactory implements WebViewFactory {
+
+    private final Context mContext;
+
+    public BrowserWebViewFactory(Context context) {
+        mContext = context;
+    }
+
+    protected WebView instantiateWebView(AttributeSet attrs, int defStyle,
+            boolean privateBrowsing) {
+        return new BrowserWebView(mContext, attrs, defStyle, privateBrowsing);
+    }
+
+    @Override
+    public WebView createSubWebView(boolean privateBrowsing) {
+        return createWebView(privateBrowsing);
+    }
+
+    @Override
+    public WebView createWebView(boolean privateBrowsing) {
+        WebView w = instantiateWebView(null, android.R.attr.webViewStyle, privateBrowsing);
+        initWebViewSettings(w);
+        return w;
+    }
+
+    protected void initWebViewSettings(WebView w) {
+        w.setScrollbarFadingEnabled(true);
+        w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
+        w.setMapTrackballToArrowKeys(false); // use trackball directly
+        // Enable the built-in zoom
+        w.getSettings().setBuiltInZoomControls(true);
+        boolean supportsMultiTouch = mContext.getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
+        w.getSettings().setDisplayZoomControls(!supportsMultiTouch);
+        w.setExpandedTileBounds(true);  // smoother scrolling
+
+        // Add this WebView to the settings observer list and update the
+        // settings
+        final BrowserSettings s = BrowserSettings.getInstance();
+        s.startManagingSettings(w.getSettings());
+    }
+
+}
diff --git a/src/com/android/browser/CombinedBookmarkHistoryView.java b/src/com/android/browser/CombinedBookmarkHistoryView.java
index 785b2cd..184314e 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryView.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryView.java
@@ -17,6 +17,8 @@
 package com.android.browser;
 
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.app.ActionBar;
 import android.app.ActionBar.Tab;
 import android.app.ActionBar.TabListener;
@@ -28,7 +30,6 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.Browser;
@@ -45,6 +46,8 @@
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
+import com.android.browser.UI.ComboViews;
+
 import java.util.HashMap;
 import java.util.Vector;
 
@@ -61,6 +64,7 @@
     final static int INVALID_ID = 0;
     final static int FRAGMENT_ID_BOOKMARKS = 1;
     final static int FRAGMENT_ID_HISTORY = 2;
+    final static int FRAGMENT_ID_SNAPSHOTS = 3;
 
     private UiController mUiController;
     private Activity mActivity;
@@ -72,10 +76,13 @@
 
     ActionBar.Tab mTabBookmarks;
     ActionBar.Tab mTabHistory;
+    ActionBar.Tab mTabSnapshots;
     ViewGroup mBookmarksHeader;
 
     BrowserBookmarksPage mBookmarks;
     BrowserHistoryPage mHistory;
+    BrowserSnapshotPage mSnapshots;
+    boolean mIsAnimating;
 
     static class IconListenerSet implements IconListener {
         // Used to store favicons as we get them from the database
@@ -115,7 +122,7 @@
     }
 
     public CombinedBookmarkHistoryView(Activity activity, UiController controller,
-            int startingFragment, Bundle extras) {
+            ComboViews startingView, Bundle extras) {
         super(activity);
         mUiController = controller;
         mActivity = activity;
@@ -124,7 +131,6 @@
 
         View v = LayoutInflater.from(activity).inflate(R.layout.bookmarks_history, this);
         v.setOnTouchListener(this);
-        Resources res = activity.getResources();
 
 //        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
@@ -152,11 +158,28 @@
             }
         }).execute();
 
-        setupActionBar(startingFragment);
+        mIsAnimating = true;
+        setAlpha(0f);
+        Resources res = mActivity.getResources();
+        animate().alpha(1f)
+                .setDuration(res.getInteger(R.integer.comboViewFadeInDuration))
+                .setListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mIsAnimating = false;
+                FragmentManager fm = mActivity.getFragmentManager();
+                FragmentTransaction ft = fm.beginTransaction();
+                onTabSelected(mActionBar.getSelectedTab(), ft);
+                ft.commit();
+            }
+        });
+
+        setupActionBar(startingView);
         mUiController.registerOptionsMenuHandler(this);
     }
 
-    void setupActionBar(int startingFragment) {
+    void setupActionBar(ComboViews startingView) {
         if (BrowserActivity.isTablet(mContext)) {
             mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
                     | ActionBar.DISPLAY_USE_LOGO);
@@ -168,15 +191,32 @@
         mTabBookmarks = mActionBar.newTab();
         mTabBookmarks.setText(R.string.tab_bookmarks);
         mTabBookmarks.setTabListener(this);
-        mActionBar.addTab(mTabBookmarks, FRAGMENT_ID_BOOKMARKS == startingFragment);
+        mActionBar.addTab(mTabBookmarks, ComboViews.Bookmarks == startingView);
         mTabHistory = mActionBar.newTab();
         mTabHistory.setText(R.string.tab_history);
         mTabHistory.setTabListener(this);
-        mActionBar.addTab(mTabHistory, FRAGMENT_ID_HISTORY == startingFragment);
+        mActionBar.addTab(mTabHistory, ComboViews.History == startingView);
+        mTabSnapshots = mActionBar.newTab();
+        mTabSnapshots.setText(R.string.tab_snapshots);
+        mTabSnapshots.setTabListener(this);
+        mActionBar.addTab(mTabSnapshots, ComboViews.Snapshots == startingView);
         mActionBar.setCustomView(mBookmarksHeader);
         mActionBar.show();
     }
 
+    void tearDownActionBar() {
+        if (mActionBar != null) {
+            mActionBar.removeAllTabs();
+            mTabBookmarks.setTabListener(null);
+            mTabHistory.setTabListener(null);
+            mTabSnapshots.setTabListener(null);
+            mTabBookmarks = null;
+            mTabHistory = null;
+            mTabSnapshots = null;
+            mActionBar = null;
+        }
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -210,6 +250,7 @@
         mBookmarks = BrowserBookmarksPage.newInstance(mBookmarkCallbackWrapper,
                 extras, mBookmarksHeader);
         mHistory = BrowserHistoryPage.newInstance(mUiController, extras);
+        mSnapshots = BrowserSnapshotPage.newInstance(mUiController, extras);
     }
 
     private void loadFragment(int id, FragmentTransaction ft) {
@@ -222,6 +263,9 @@
             case FRAGMENT_ID_HISTORY:
                 ft.replace(R.id.fragment, mHistory);
                 break;
+            case FRAGMENT_ID_SNAPSHOTS:
+                ft.replace(R.id.fragment, mSnapshots);
+                break;
             default:
                 throw new IllegalArgumentException();
         }
@@ -231,6 +275,7 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
+        tearDownActionBar();
         if (mCurrentFragment != INVALID_ID) {
             try {
                 FragmentManager fm = mActivity.getFragmentManager();
@@ -239,6 +284,8 @@
                     transaction.remove(mBookmarks);
                 } else if (mCurrentFragment == FRAGMENT_ID_HISTORY) {
                     transaction.remove(mHistory);
+                } else if (mCurrentFragment == FRAGMENT_ID_SNAPSHOTS) {
+                    transaction.remove(mSnapshots);
                 }
                 transaction.commit();
             } catch (IllegalStateException ex) {
@@ -256,6 +303,9 @@
      * callback for back key presses
      */
     boolean onBackPressed() {
+        if (mIsAnimating) {
+            return true;
+        }
         if (mCurrentFragment == FRAGMENT_ID_BOOKMARKS) {
             return mBookmarks.onBackPressed();
         }
@@ -278,10 +328,19 @@
 
     @Override
     public void onTabSelected(Tab tab, FragmentTransaction ft) {
+        if (mIsAnimating) {
+            // We delay set while animating (smooth animations)
+            // TODO: Signal to the fragment in advance so that it can start
+            // loading its data asynchronously
+            return;
+        }
+
         if (tab == mTabBookmarks) {
             loadFragment(FRAGMENT_ID_BOOKMARKS, ft);
         } else if (tab == mTabHistory) {
             loadFragment(FRAGMENT_ID_HISTORY, ft);
+        } else if (tab == mTabSnapshots) {
+            loadFragment(FRAGMENT_ID_SNAPSHOTS, ft);
         }
     }
 
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 447e61b..003d2d8 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -40,7 +40,6 @@
 import android.net.http.SslError;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
@@ -68,6 +67,7 @@
 import android.webkit.CookieManager;
 import android.webkit.CookieSyncManager;
 import android.webkit.HttpAuthHandler;
+import android.webkit.SearchBox;
 import android.webkit.SslErrorHandler;
 import android.webkit.ValueCallback;
 import android.webkit.WebChromeClient;
@@ -77,9 +77,10 @@
 import android.widget.Toast;
 
 import com.android.browser.IntentHandler.UrlData;
+import com.android.browser.UI.ComboViews;
 import com.android.browser.UI.DropdownChangeListener;
 import com.android.browser.provider.BrowserProvider;
-import com.android.browser.provider.BrowserProvider2.Snapshots;
+import com.android.browser.provider.SnapshotProvider.Snapshots;
 import com.android.browser.search.SearchEngine;
 import com.android.common.Search;
 
@@ -229,6 +230,7 @@
         mTabControl = new TabControl(this);
         mSettings.setController(this);
         mCrashRecoveryHandler = CrashRecoveryHandler.initialize(this);
+        mFactory = new BrowserWebViewFactory(browser);
 
         mUrlHandler = new UrlHandler(this);
         mIntentHandler = new IntentHandler(mActivity, this);
@@ -312,7 +314,7 @@
             // If the intent is ACTION_VIEW and data is not null, the Browser is
             // invoked to view the content by another application. In this case,
             // the tab will be close when exit.
-            UrlData urlData = mIntentHandler.getUrlDataFromIntent(intent);
+            UrlData urlData = IntentHandler.getUrlDataFromIntent(intent);
             Tab t = null;
             if (urlData.isEmpty()) {
                 t = openTabToHomePage();
@@ -329,7 +331,6 @@
                     webView.setInitialScale(scale);
                 }
             }
-            mTabControl.loadSnapshotTabs();
             mUi.updateTabs(mTabControl.getTabs());
         } else {
             mTabControl.restoreState(icicle, currentTabId, restoreIncognitoTabs,
@@ -356,10 +357,6 @@
         }
     }
 
-    void setWebViewFactory(WebViewFactory factory) {
-        mFactory = factory;
-    }
-
     @Override
     public WebViewFactory getWebViewFactory() {
         return mFactory;
@@ -381,6 +378,11 @@
     }
 
     @Override
+    public Context getContext() {
+        return mActivity;
+    }
+
+    @Override
     public Activity getActivity() {
         return mActivity;
     }
@@ -1177,7 +1179,8 @@
         // Disable opening in a new window if we have maxed out the windows
         extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW,
                 !mTabControl.canCreateNewTab());
-        mUi.showComboView(startWithHistory, extras);
+        mUi.showComboView(startWithHistory
+                ? ComboViews.History : ComboViews.Bookmarks, extras);
     }
 
     // combo view callbacks
@@ -1518,11 +1521,9 @@
                 final MenuItem newtab = menu.findItem(R.id.new_tab_menu_id);
                 newtab.setEnabled(getTabControl().canCreateNewTab());
 
-                MenuItem archive = menu.findItem(R.id.save_webarchive_menu_id);
-                Tab tab = getTabControl().getCurrentTab();
-                String url = tab != null ? tab.getUrl() : null;
-                archive.setVisible(!TextUtils.isEmpty(url)
-                        && !url.endsWith(".webarchivexml"));
+                MenuItem saveSnapshot = menu.findItem(R.id.save_snapshot_menu_id);
+                Tab tab = getCurrentTab();
+                saveSnapshot.setVisible(tab != null && !tab.isSnapshot());
                 break;
         }
         mCurrentMenuState = mMenuState;
@@ -1617,82 +1618,32 @@
                 getCurrentTopWebView().showFindDialog(null, true);
                 break;
 
-            case R.id.freeze_tab_menu_id:
-                // TODO: Show error messages
-                Tab source = getTabControl().getCurrentTab();
+            case R.id.save_snapshot_menu_id:
+                final Tab source = getTabControl().getCurrentTab();
                 if (source == null) break;
                 final ContentResolver cr = mActivity.getContentResolver();
                 final ContentValues values = source.createSnapshotValues();
-                new AsyncTask<Tab, Void, Long>() {
-                    @Override
-                    protected Long doInBackground(Tab... params) {
-                        Tab t = params[0];
-                        if (values == null) {
-                            return t.isSnapshot()
-                                    ? ((SnapshotTab)t).getSnapshotId()
-                                    : -1;
-                        }
-                        Uri result = cr.insert(Snapshots.CONTENT_URI, values);
-                        long id = ContentUris.parseId(result);
-                        return id;
-                    }
+                if (values != null) {
+                    new AsyncTask<Tab, Void, Long>() {
 
-                    protected void onPostExecute(Long id) {
-                        if (id > 0) {
-                            createNewSnapshotTab(id, true);
+                        @Override
+                        protected Long doInBackground(Tab... params) {
+                            Uri result = cr.insert(Snapshots.CONTENT_URI, values);
+                            long id = ContentUris.parseId(result);
+                            return id;
                         }
-                    };
-                }.execute(source);
-                break;
 
-            case R.id.save_webarchive_menu_id:
-                String state = Environment.getExternalStorageState();
-                if (!Environment.MEDIA_MOUNTED.equals(state)) {
-                    Log.e(LOGTAG, "External storage not mounted");
-                    Toast.makeText(mActivity, R.string.webarchive_failed,
+                        @Override
+                        protected void onPostExecute(Long id) {
+                            Bundle b = new Bundle();
+                            b.putLong(BrowserSnapshotPage.EXTRA_ANIMATE_ID, id);
+                            mUi.showComboView(ComboViews.Snapshots, b);
+                        };
+                    }.execute(source);
+                } else {
+                    Toast.makeText(mActivity, R.string.snapshot_failed,
                             Toast.LENGTH_SHORT).show();
-                    break;
                 }
-                final String directory = Environment.getExternalStoragePublicDirectory(
-                        Environment.DIRECTORY_DOWNLOADS) + File.separator;
-                File dir = new File(directory);
-                if (!dir.exists() && !dir.mkdirs()) {
-                    Log.e(LOGTAG, "Save as Web Archive: mkdirs for " + directory + " failed!");
-                    Toast.makeText(mActivity, R.string.webarchive_failed,
-                            Toast.LENGTH_SHORT).show();
-                    break;
-                }
-                final WebView topWebView = getCurrentTopWebView();
-                final String title = topWebView.getTitle();
-                final String url = topWebView.getUrl();
-                topWebView.saveWebArchive(directory, true,
-                        new ValueCallback<String>() {
-                    @Override
-                    public void onReceiveValue(final String value) {
-                        if (value != null) {
-                            File file = new File(value);
-                            final long length = file.length();
-                            if (file.exists() && length > 0) {
-                                Toast.makeText(mActivity, R.string.webarchive_saved,
-                                        Toast.LENGTH_SHORT).show();
-                                final DownloadManager manager = (DownloadManager) mActivity
-                                        .getSystemService(Context.DOWNLOAD_SERVICE);
-                                new Thread("Add WebArchive to download manager") {
-                                    @Override
-                                    public void run() {
-                                        manager.addCompletedDownload(
-                                                null == title ? value : title,
-                                                value, true, "application/x-webarchive-xml",
-                                                value, length, true);
-                                    }
-                                }.start();
-                                return;
-                            }
-                        }
-                        DownloadHandler.onDownloadStartNoStream(mActivity,
-                                url, null, null, null, topWebView.isPrivateBrowsingEnabled());
-                    }
-                });
                 break;
 
             case R.id.page_info_menu_id:
@@ -1995,7 +1946,7 @@
         return null;
     }
 
-    private static Bitmap createScreenshot(WebView view, int width, int height) {
+    static Bitmap createScreenshot(WebView view, int width, int height) {
         // We render to a bitmap 2x the desired size so that we can then
         // re-scale it with filtering since canvas.scale doesn't filter
         // This helps reduce aliasing at the cost of being slightly blurry
@@ -2163,18 +2114,6 @@
         mUi.removeTab(tab);
         mTabControl.removeTab(tab);
         mCrashRecoveryHandler.backupState();
-        if (tab.isSnapshot()) {
-            SnapshotTab st = (SnapshotTab) tab;
-            final Uri uri = ContentUris.withAppendedId(
-                    Snapshots.CONTENT_URI, st.getSnapshotId());
-            final ContentResolver cr = mActivity.getContentResolver();
-            new Thread() {
-                @Override
-                public void run() {
-                    cr.delete(uri, null, null);
-                }
-            }.start();
-        }
     }
 
     @Override
@@ -2244,12 +2183,35 @@
         }
     }
 
+    private Tab showPreloadedTab(final UrlData urlData) {
+        if (!urlData.isPreloaded()) {
+            return null;
+        }
+        final PreloadedTabControl tabControl = urlData.getPreloadedTab();
+        final String sbQuery = urlData.getSearchBoxQueryToSubmit();
+        if (sbQuery != null) {
+            if (!tabControl.searchBoxSubmit(sbQuery, urlData.mUrl, urlData.mHeaders)) {
+                // Could not submit query. Fallback to regular tab creation
+                tabControl.destroy();
+                return null;
+            }
+        }
+        Tab t = tabControl.getTab();
+        mTabControl.addPreloadedTab(t);
+        addTab(t);
+        setActiveTab(t);
+        return t;
+    }
+
     // open a non inconito tab with the given url data
     // and set as active tab
     public Tab openTab(UrlData urlData) {
-        Tab tab = createNewTab(false, true, true);
-        if ((tab != null) && !urlData.isEmpty()) {
-            loadUrlDataIn(tab, urlData);
+        Tab tab = showPreloadedTab(urlData);
+        if (tab == null) {
+            tab = createNewTab(false, true, true);
+            if ((tab != null) && !urlData.isEmpty()) {
+                loadUrlDataIn(tab, urlData);
+            }
         }
         return tab;
     }
@@ -2315,11 +2277,17 @@
         return tab;
     }
 
-    private SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive) {
-        SnapshotTab tab = mTabControl.createSnapshotTab(snapshotId);
-        addTab(tab);
-        if (setActive) {
-            setActiveTab(tab);
+    @Override
+    public SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive) {
+        SnapshotTab tab = null;
+        if (mTabControl.canCreateNewTab()) {
+            tab = mTabControl.createSnapshotTab(snapshotId);
+            addTab(tab);
+            if (setActive) {
+                setActiveTab(tab);
+            }
+        } else {
+            mUi.showMaxTabsWarning();
         }
         return tab;
     }
@@ -2396,7 +2364,8 @@
      * @param view The WebView used to load url.
      * @param url The URL to load.
      */
-    protected void loadUrl(Tab tab, String url) {
+    @Override
+    public void loadUrl(Tab tab, String url) {
         loadUrl(tab, url, null);
     }
 
@@ -2417,6 +2386,8 @@
         if (data != null) {
             if (data.mVoiceIntent != null) {
                 t.activateVoiceSearchMode(data.mVoiceIntent);
+            } else if (data.isPreloaded()) {
+                // this isn't called for preloaded tabs
             } else {
                 loadUrl(t, data.mUrl, data.mHeaders);
             }
diff --git a/src/com/android/browser/CrashRecoveryHandler.java b/src/com/android/browser/CrashRecoveryHandler.java
index 5140952..ca538bd 100644
--- a/src/com/android/browser/CrashRecoveryHandler.java
+++ b/src/com/android/browser/CrashRecoveryHandler.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -120,8 +121,8 @@
             Parcel p = Parcel.obtain();
             try {
                 mState.writeToParcel(p, 0);
-                FileOutputStream fout = mContext.openFileOutput(STATE_FILE,
-                        Context.MODE_PRIVATE);
+                File state = new File(mContext.getCacheDir(), STATE_FILE);
+                FileOutputStream fout = new FileOutputStream(state);
                 fout.write(p.marshall());
                 fout.close();
             } catch (Throwable e) {
@@ -134,7 +135,10 @@
     }
 
     private static void clearState(Context context) {
-        context.deleteFile(STATE_FILE);
+        File state = new File(context.getCacheDir(), STATE_FILE);
+        if (state.exists()) {
+            state.delete();
+        }
     }
 
     public void promptToRecover(final Bundle state, final Intent intent) {
@@ -166,6 +170,8 @@
     }
 
     private boolean shouldPrompt() {
+        // STOPSHIP TODO: Remove once b/4971724 is fixed
+        if (true) return false;
         Context context = mController.getActivity();
         SharedPreferences prefs = context.getSharedPreferences(
                 RECOVERY_PREFERENCES, Context.MODE_PRIVATE);
@@ -192,7 +198,8 @@
         Parcel parcel = Parcel.obtain();
         try {
             Context context = mController.getActivity();
-            FileInputStream fin = context.openFileInput(STATE_FILE);
+            File stateFile = new File(context.getCacheDir(), STATE_FILE);
+            FileInputStream fin = new FileInputStream(stateFile);
             ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
             byte[] buffer = new byte[BUFFER_SIZE];
             int read;
diff --git a/src/com/android/browser/GeolocationPermissionsPrompt.java b/src/com/android/browser/GeolocationPermissionsPrompt.java
index 95c5415..afbf39f 100755
--- a/src/com/android/browser/GeolocationPermissionsPrompt.java
+++ b/src/com/android/browser/GeolocationPermissionsPrompt.java
@@ -17,22 +17,18 @@
 package com.android.browser;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.util.AttributeSet;
 import android.view.Gravity;
-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.RelativeLayout;
 import android.widget.TextView;
 import android.widget.Toast;
 
-public class GeolocationPermissionsPrompt extends LinearLayout {
-    private LinearLayout mInner;
+public class GeolocationPermissionsPrompt extends RelativeLayout {
     private TextView mMessage;
     private Button mShareButton;
     private Button mDontShareButton;
@@ -48,22 +44,26 @@
         super(context, attrs);
     }
 
-    void init() {
-        mInner = (LinearLayout) findViewById(R.id.inner);
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        init();
+    }
+
+    private void init() {
         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);
 
-        final GeolocationPermissionsPrompt me = this;
         mShareButton.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
-                me.handleButtonClick(true);
+                handleButtonClick(true);
             }
         });
         mDontShareButton.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
-                me.handleButtonClick(false);
+                handleButtonClick(false);
             }
         });
     }
@@ -79,21 +79,21 @@
         setMessage("http".equals(uri.getScheme()) ?  mOrigin.substring(7) : mOrigin);
         // The checkbox should always be intially checked.
         mRemember.setChecked(true);
-        showDialog(true);
+        setVisibility(View.VISIBLE);
     }
 
     /**
      * Hides the prompt.
      */
     public void hide() {
-        showDialog(false);
+        setVisibility(View.GONE);
     }
 
     /**
      * Handles a click on one the buttons by invoking the callback.
      */
     private void handleButtonClick(boolean allow) {
-        showDialog(false);
+        hide();
 
         boolean remember = mRemember.isChecked();
         if (remember) {
@@ -117,11 +117,4 @@
             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/IntentHandler.java b/src/com/android/browser/IntentHandler.java
index 088a788..1a72a23 100644
--- a/src/com/android/browser/IntentHandler.java
+++ b/src/com/android/browser/IntentHandler.java
@@ -135,8 +135,9 @@
                 urlData = new UrlData(mSettings.getHomePage());
             }
 
-            if (intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false)) {
-                mController.openTab(urlData);
+            if (intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false)
+                  || urlData.isPreloaded()) {
+                Tab t = mController.openTab(urlData);
                 return;
             }
             /*
@@ -220,9 +221,11 @@
         }
     }
 
-    protected UrlData getUrlDataFromIntent(Intent intent) {
+    protected static UrlData getUrlDataFromIntent(Intent intent) {
         String url = "";
         Map<String, String> headers = null;
+        PreloadedTabControl preloaded = null;
+        String preloadedSearchBoxQuery = null;
         if (intent != null
                 && (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
             final String action = intent.getAction();
@@ -241,6 +244,12 @@
                         }
                     }
                 }
+                if (intent.hasExtra(PreloadRequestReceiver.EXTRA_PRELOAD_ID)) {
+                    String id = intent.getStringExtra(PreloadRequestReceiver.EXTRA_PRELOAD_ID);
+                    preloadedSearchBoxQuery = intent.getStringExtra(
+                            PreloadRequestReceiver.EXTRA_SEARCHBOX_SETQUERY);
+                    preloaded = Preloader.getInstance().getPreloadedTab(id);
+                }
             } else if (Intent.ACTION_SEARCH.equals(action)
                     || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
                     || Intent.ACTION_WEB_SEARCH.equals(action)) {
@@ -265,7 +274,7 @@
                 }
             }
         }
-        return new UrlData(url, headers, intent);
+        return new UrlData(url, headers, intent, preloaded, preloadedSearchBoxQuery);
     }
 
     /**
@@ -348,14 +357,23 @@
         final String mUrl;
         final Map<String, String> mHeaders;
         final Intent mVoiceIntent;
+        final PreloadedTabControl mPreloadedTab;
+        final String mSearchBoxQueryToSubmit;
 
         UrlData(String url) {
             this.mUrl = url;
             this.mHeaders = null;
             this.mVoiceIntent = null;
+            this.mPreloadedTab = null;
+            this.mSearchBoxQueryToSubmit = null;
         }
 
         UrlData(String url, Map<String, String> headers, Intent intent) {
+            this(url, headers, intent, null, null);
+        }
+
+        UrlData(String url, Map<String, String> headers, Intent intent,
+                PreloadedTabControl preloaded, String searchBoxQueryToSubmit) {
             this.mUrl = url;
             this.mHeaders = headers;
             if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
@@ -364,11 +382,25 @@
             } else {
                 this.mVoiceIntent = null;
             }
+            this.mPreloadedTab = preloaded;
+            this.mSearchBoxQueryToSubmit = searchBoxQueryToSubmit;
         }
 
         boolean isEmpty() {
             return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0);
         }
+
+        boolean isPreloaded() {
+            return mPreloadedTab != null;
+        }
+
+        PreloadedTabControl getPreloadedTab() {
+            return mPreloadedTab;
+        }
+
+        String getSearchBoxQueryToSubmit() {
+            return mSearchBoxQueryToSubmit;
+        }
     }
 
 }
diff --git a/src/com/android/browser/NfcHandler.java b/src/com/android/browser/NfcHandler.java
index bdfe25e..bc19950 100644
--- a/src/com/android/browser/NfcHandler.java
+++ b/src/com/android/browser/NfcHandler.java
@@ -20,6 +20,7 @@
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
 import android.nfc.NfcAdapter;
+import android.os.AsyncTask;
 
 /** This class implements sharing the URL of the currently
   * shown browser page over NFC. Sharing is only active
@@ -31,6 +32,37 @@
     private Activity mActivity;
     private Controller mController;
 
+    /** We need an async task to check whether the tab is private
+      * on the UI thread.
+      */
+    private class CreateMessageTask extends AsyncTask<Void, Void, NdefMessage> {
+        private boolean mIsPrivate = false;
+        private Tab mCurrentTab;
+
+        @Override
+        protected void onPreExecute() {
+            mCurrentTab = mController.getCurrentTab();
+            if ((mCurrentTab != null) && (mCurrentTab.getWebView() != null)) {
+                mIsPrivate = mCurrentTab.getWebView().isPrivateBrowsingEnabled();
+            }
+        }
+
+        @Override
+        protected NdefMessage doInBackground(Void... params) {
+            if ((mCurrentTab == null) || mIsPrivate) {
+                return null;
+            }
+            String currentUrl = mCurrentTab.getUrl();
+            if (currentUrl != null) {
+                NdefRecord record = NdefRecord.createUri(currentUrl);
+                NdefMessage msg = new NdefMessage(new NdefRecord[] { record });
+                return msg;
+            } else {
+                return null;
+            }
+        }
+    }
+
     public NfcHandler(Activity browser, Controller controller) {
         mActivity = browser;
         mController = controller;
@@ -51,18 +83,11 @@
 
     @Override
     public NdefMessage createMessage() {
-        Tab currentTab = mController.getCurrentTab();
-        if (currentTab == null) {
-            return null;
-        }
-        String currentUrl = currentTab.getUrl();
-        if (currentUrl != null && currentTab.getWebView() != null &&
-                    !currentTab.getWebView().isPrivateBrowsingEnabled()) {
-            NdefRecord record = new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI,
-                    NdefRecord.RTD_URI, new byte[] {}, currentUrl.getBytes());
-            NdefMessage msg = new NdefMessage(new NdefRecord[] { record });
-            return msg;
-        } else {
+        CreateMessageTask task = new CreateMessageTask();
+        task.execute();
+        try {
+            return task.get();
+        } catch (Exception e) {
             return null;
         }
     }
diff --git a/src/com/android/browser/PageDialogsHandler.java b/src/com/android/browser/PageDialogsHandler.java
index b39d863..015a9bb 100644
--- a/src/com/android/browser/PageDialogsHandler.java
+++ b/src/com/android/browser/PageDialogsHandler.java
@@ -249,7 +249,7 @@
         LayoutInflater factory = LayoutInflater.from(mContext);
 
         final LinearLayout placeholder =
-                (LinearLayout)certificateView.findViewById(R.id.placeholder);
+                (LinearLayout)certificateView.findViewById(com.android.internal.R.id.placeholder);
 
         LinearLayout ll = (LinearLayout) factory.inflate(
             R.layout.ssl_success, placeholder);
@@ -304,7 +304,7 @@
         LayoutInflater factory = LayoutInflater.from(mContext);
 
         final LinearLayout placeholder =
-                (LinearLayout)certificateView.findViewById(R.id.placeholder);
+                (LinearLayout)certificateView.findViewById(com.android.internal.R.id.placeholder);
 
         if (error.hasError(SslError.SSL_UNTRUSTED)) {
             LinearLayout ll = (LinearLayout)factory
diff --git a/src/com/android/browser/PhoneUi.java b/src/com/android/browser/PhoneUi.java
index d0b0f77..0c236af 100644
--- a/src/com/android/browser/PhoneUi.java
+++ b/src/com/android/browser/PhoneUi.java
@@ -36,7 +36,6 @@
 
     private TitleBarPhone mTitleBar;
     private ActiveTabsPage mActiveTabsPage;
-    private boolean mUseQuickControls;
     private PieControl mPieControl;
     private NavScreen mNavScreen;
 
@@ -164,13 +163,11 @@
         // Request focus on the top window.
         if (mUseQuickControls) {
             mPieControl.forceToTop(mContentView);
-            view.setScrollListener(null);
         } else {
             // check if title bar is already attached by animation
             if (mTitleBar.getParent() == null) {
                 view.setEmbeddedTitleBar(mTitleBar);
             }
-            view.setScrollListener(this);
         }
         if (tab.isInVoiceSearchMode()) {
             showVoiceTitleBar(tab.getVoiceDisplayTitle(), tab.getVoiceSearchResults());
@@ -209,11 +206,11 @@
     }
 
     @Override
-    public void showComboView(boolean startWithHistory, Bundle extras) {
+    public void showComboView(ComboViews startWith, Bundle extras) {
         if (mNavScreen != null) {
             hideNavScreen(false);
         }
-        super.showComboView(startWithHistory, extras);
+        super.showComboView(startWith, extras);
     }
 
     @Override
diff --git a/src/com/android/browser/PreferenceKeys.java b/src/com/android/browser/PreferenceKeys.java
index bc8d38f..144d505 100644
--- a/src/com/android/browser/PreferenceKeys.java
+++ b/src/com/android/browser/PreferenceKeys.java
@@ -94,4 +94,9 @@
     static final String PREF_SAVE_FORMDATA = "save_formdata";
     static final String PREF_SHOW_SECURITY_WARNINGS = "show_security_warnings";
 
+    // ----------------------
+    // Keys for bandwidth_preferences.xml
+    // ----------------------
+    static final String PREF_DATA_PRELOAD = "preload_enabled";
+
 }
diff --git a/src/com/android/browser/PreloadController.java b/src/com/android/browser/PreloadController.java
new file mode 100644
index 0000000..6528410
--- /dev/null
+++ b/src/com/android/browser/PreloadController.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.browser;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.net.http.SslError;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.view.View;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient.CustomViewCallback;
+import android.webkit.WebView;
+
+import java.util.List;
+
+public class PreloadController implements WebViewController {
+
+    private Context mContext;
+
+    public PreloadController(Context ctx) {
+        mContext = ctx;
+
+    }
+
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public Activity getActivity() {
+        return null;
+    }
+
+    @Override
+    public TabControl getTabControl() {
+        return null;
+    }
+
+    @Override
+    public WebViewFactory getWebViewFactory() {
+        return null;
+    }
+
+    @Override
+    public void onSetWebView(Tab tab, WebView view) {
+    }
+
+    @Override
+    public void createSubWindow(Tab tab) {
+    }
+
+    @Override
+    public void onPageStarted(Tab tab, WebView view, Bitmap favicon) {
+    }
+
+    @Override
+    public void onPageFinished(Tab tab) {
+    }
+
+    @Override
+    public void onProgressChanged(Tab tab) {
+    }
+
+    @Override
+    public void onReceivedTitle(Tab tab, String title) {
+    }
+
+    @Override
+    public void onFavicon(Tab tab, WebView view, Bitmap icon) {
+    }
+
+    @Override
+    public boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
+        return false;
+    }
+
+    @Override
+    public boolean shouldOverrideKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public void onUnhandledKeyEvent(KeyEvent event) {
+    }
+
+    @Override
+    public void doUpdateVisitedHistory(Tab tab, boolean isReload) {
+    }
+
+    @Override
+    public void getVisitedHistory(ValueCallback<String[]> callback) {
+    }
+
+    @Override
+    public void onReceivedHttpAuthRequest(Tab tab, WebView view,
+                                    HttpAuthHandler handler, String host,
+                                    String realm) {
+    }
+
+    @Override
+    public void onDownloadStart(Tab tab, String url, String useragent,
+                                    String contentDisposition, String mimeType,
+                                    long contentLength) {
+    }
+
+    @Override
+    public void showCustomView(Tab tab, View view, int requestedOrientation,
+                                    CustomViewCallback callback) {
+    }
+
+    @Override
+    public void hideCustomView() {
+    }
+
+    @Override
+    public Bitmap getDefaultVideoPoster() {
+        return null;
+    }
+
+    @Override
+    public View getVideoLoadingProgressView() {
+        return null;
+    }
+
+    @Override
+    public void showSslCertificateOnError(WebView view,
+                                    SslErrorHandler handler, SslError error) {
+    }
+
+    @Override
+    public void onUserCanceledSsl(Tab tab) {
+    }
+
+    @Override
+    public void activateVoiceSearchMode(String title, List<String> results) {
+    }
+
+    @Override
+    public void revertVoiceSearchMode(Tab tab) {
+    }
+
+    @Override
+    public boolean shouldShowErrorConsole() {
+        return false;
+    }
+
+    @Override
+    public void onUpdatedLockIcon(Tab tab) {
+    }
+
+    @Override
+    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
+    }
+
+    @Override
+    public void endActionMode() {
+    }
+
+    @Override
+    public void attachSubWindow(Tab tab) {
+    }
+
+    @Override
+    public void dismissSubWindow(Tab tab) {
+    }
+
+    @Override
+    public Tab openTab(String url, boolean incognito, boolean setActive,
+                                    boolean useCurrent) {
+        return null;
+    }
+
+    @Override
+    public Tab openTab(String url, Tab parent, boolean setActive,
+                                    boolean useCurrent) {
+        return null;
+    }
+
+    @Override
+    public boolean switchToTab(Tab tab) {
+        return false;
+    }
+
+    @Override
+    public void closeTab(Tab tab) {
+    }
+
+    @Override
+    public void setupAutoFill(Message message) {
+    }
+
+    @Override
+    public void bookmarkedStatusHasChanged(Tab tab) {
+    }
+
+    @Override
+    public void showAutoLogin(Tab tab) {
+    }
+
+    @Override
+    public void hideAutoLogin(Tab tab) {
+    }
+
+}
diff --git a/src/com/android/browser/PreloadRequestReceiver.java b/src/com/android/browser/PreloadRequestReceiver.java
new file mode 100644
index 0000000..5176176
--- /dev/null
+++ b/src/com/android/browser/PreloadRequestReceiver.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2011 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.Browser;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Broadcast receiver for receiving browser preload requests
+ */
+public class PreloadRequestReceiver extends BroadcastReceiver {
+
+    private final static String LOGTAG = "browser.preloader";
+    private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
+
+    private static final String ACTION_PRELOAD = "android.intent.action.PRELOAD";
+    static final String EXTRA_PRELOAD_ID = "preload_id";
+    static final String EXTRA_PRELOAD_DISCARD = "preload_discard";
+    static final String EXTRA_SEARCHBOX_SETQUERY = "searchbox_query";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "received intent " + intent);
+        if (BrowserSettings.getInstance().isPreloadEnabled()
+                && intent.getAction().equals(ACTION_PRELOAD)) {
+            handlePreload(context, intent);
+        }
+    }
+
+    private void handlePreload(Context context, Intent i) {
+        String url = UrlUtils.smartUrlFilter(i.getData());
+        String id = i.getStringExtra(EXTRA_PRELOAD_ID);
+        Map<String, String> headers = null;
+        if (id == null) {
+            if (LOGD_ENABLED) Log.d(LOGTAG, "Preload request has no " + EXTRA_PRELOAD_ID);
+            return;
+        }
+        if (i.getBooleanExtra(EXTRA_PRELOAD_DISCARD, false)) {
+            if (LOGD_ENABLED) Log.d(LOGTAG, "Got " + id + " preload discard request");
+            Preloader.getInstance().discardPreload(id);
+        } else {
+            if (LOGD_ENABLED) Log.d(LOGTAG, "Got " + id + " preload request for " + url);
+            if (url != null && url.startsWith("http")) {
+                final Bundle pairs = i.getBundleExtra(Browser.EXTRA_HEADERS);
+                if (pairs != null && !pairs.isEmpty()) {
+                    Iterator<String> iter = pairs.keySet().iterator();
+                    headers = new HashMap<String, String>();
+                    while (iter.hasNext()) {
+                        String key = iter.next();
+                        headers.put(key, pairs.getString(key));
+                    }
+                }
+            }
+            String sbQuery = i.getStringExtra(EXTRA_SEARCHBOX_SETQUERY);
+            if (url != null) {
+                if (LOGD_ENABLED){
+                    Log.d(LOGTAG, "Preload request(" + id + ", " + url + ", " +
+                            headers + ", " + sbQuery + ")");
+                }
+                Preloader.getInstance().handlePreloadRequest(id, url, headers, sbQuery);
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/browser/PreloadedTabControl.java b/src/com/android/browser/PreloadedTabControl.java
new file mode 100644
index 0000000..99592fb
--- /dev/null
+++ b/src/com/android/browser/PreloadedTabControl.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 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.text.TextUtils;
+import android.util.Log;
+import android.webkit.SearchBox;
+
+import java.util.Map;
+
+/**
+ * Class to manage the controlling of preloaded tab.
+ */
+public class PreloadedTabControl {
+    private static final boolean LOGD_ENABLED = true;//com.android.browser.Browser.LOGD_ENABLED;
+    private static final String LOGTAG = "PreloadedTabControl";
+
+    final Tab mTab;
+    private String mLastQuery;
+    private boolean mDestroyed;
+
+    public PreloadedTabControl(Tab t) {
+        mTab = t;
+    }
+
+    private void maybeSetQuery(String query, SearchBox sb) {
+        if (!TextUtils.equals(mLastQuery, query)) {
+            if (sb != null) {
+                if (LOGD_ENABLED) Log.d(LOGTAG, "Changing searchbox query to " + query);
+                sb.setVerbatim(true);
+                sb.setQuery(query);
+                sb.onchange();
+                mLastQuery = query;
+            } else {
+                if (LOGD_ENABLED) Log.d(LOGTAG, "Cannot set query: no searchbox interface");
+            }
+        }
+    }
+
+    public void setQuery(String query) {
+        maybeSetQuery(query, mTab.getWebView().getSearchBox());
+    }
+
+    public boolean searchBoxSubmit(final String query,
+            final String fallbackUrl, final Map<String, String> fallbackHeaders) {
+        final SearchBox sb = mTab.getWebView().getSearchBox();
+        if (sb == null) {
+            // no searchbox, cannot submit. Fallback to regular tab creation
+            if (LOGD_ENABLED) Log.d(LOGTAG, "No searchbox, cannot submit query");
+            return false;
+        }
+        sb.isSupported(new SearchBox.IsSupportedCallback() {
+            @Override
+            public void searchBoxIsSupported(boolean supported) {
+                if (LOGD_ENABLED) Log.d(LOGTAG, "SearchBox supported: " + supported);
+                if (mDestroyed) {
+                    if (LOGD_ENABLED) Log.d(LOGTAG, "tab has been destroyed");
+                    return;
+                }
+                if (supported) {
+                    maybeSetQuery(query, sb);
+                    if (LOGD_ENABLED) Log.d(LOGTAG, "Submitting query " + query);
+                    sb.onsubmit();
+                } else {
+                    if (LOGD_ENABLED) Log.d(LOGTAG, "SearchBox not supported; falling back");
+                    loadUrl(fallbackUrl, fallbackHeaders);
+                }
+                mTab.getWebView().clearHistory();
+            }
+        });
+        return true;
+    }
+
+    public void loadUrlIfChanged(String url, Map<String, String> headers) {
+        if (!TextUtils.equals(url, mTab.getUrl())) {
+            loadUrl(url, headers);
+        }
+    }
+
+    public void loadUrl(String url, Map<String, String> headers) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "Preloading " + url);
+        mTab.loadUrl(url, headers);
+    }
+
+    public void destroy() {
+        mDestroyed = true;
+        mTab.destroy();
+    }
+
+    public Tab getTab() {
+        return mTab;
+    }
+
+}
diff --git a/src/com/android/browser/Preloader.java b/src/com/android/browser/Preloader.java
new file mode 100644
index 0000000..336b77a
--- /dev/null
+++ b/src/com/android/browser/Preloader.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2011 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.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Singleton class for handling preload requests.
+ */
+public class Preloader {
+
+    private final static String LOGTAG = "browser.preloader";
+    private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
+
+    private static final int PRERENDER_TIMEOUT_MILLIS = 30 * 1000; // 30s
+
+    private static Preloader sInstance;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final BrowserWebViewFactory mFactory;
+    private final HashMap<String, PreloaderSession> mSessions;
+
+    public static void initialize(Context context) {
+        sInstance = new Preloader(context);
+    }
+
+    public static Preloader getInstance() {
+        return sInstance;
+    }
+
+    private Preloader(Context context) {
+        mContext = context;
+        mHandler = new Handler(Looper.getMainLooper());
+        mSessions = new HashMap<String, PreloaderSession>();
+        mFactory = new BrowserWebViewFactory(context);
+
+    }
+
+    private PreloaderSession getSession(String id) {
+        PreloaderSession s = mSessions.get(id);
+        if (s == null) {
+            if (LOGD_ENABLED) Log.d(LOGTAG, "Create new preload session " + id);
+            s = new PreloaderSession(id);
+            mSessions.put(id, s);
+        }
+        return s;
+    }
+
+    private PreloaderSession takeSession(String id) {
+        PreloaderSession s = mSessions.remove(id);
+        if (s != null) {
+            s.cancelTimeout();
+        }
+        return s;
+    }
+
+    public void handlePreloadRequest(String id, String url, Map<String, String> headers,
+            String searchBoxQuery) {
+        PreloaderSession s = getSession(id);
+        s.touch(); // reset timer
+        PreloadedTabControl tab = s.getTabControl();
+        if (searchBoxQuery != null) {
+            tab.loadUrlIfChanged(url, headers);
+            tab.setQuery(searchBoxQuery);
+        } else {
+            tab.loadUrl(url, headers);
+        }
+    }
+
+    public void discardPreload(String id) {
+        PreloaderSession s = takeSession(id);
+        if (s != null) {
+            if (LOGD_ENABLED) Log.d(LOGTAG, "Discard preload session " + id);
+            PreloadedTabControl t = s.getTabControl();
+            t.destroy();
+        } else {
+            if (LOGD_ENABLED) Log.d(LOGTAG, "Ignored discard request " + id);
+        }
+    }
+
+    /**
+     * Return a preloaded tab, and remove it from the preloader. This is used when the
+     * view is about to be displayed.
+     */
+    public PreloadedTabControl getPreloadedTab(String id) {
+        PreloaderSession s = takeSession(id);
+        if (LOGD_ENABLED) Log.d(LOGTAG, "Showing preload session " + id + "=" + s);
+        return s == null ? null : s.getTabControl();
+    }
+
+    private class PreloaderSession {
+        private final String mId;
+        private final PreloadedTabControl mTabControl;
+
+        private final Runnable mTimeoutTask = new Runnable(){
+            @Override
+            public void run() {
+                if (LOGD_ENABLED) Log.d(LOGTAG, "Preload session timeout " + mId);
+                discardPreload(mId);
+            }};
+
+        public PreloaderSession(String id) {
+            mId = id;
+            mTabControl = new PreloadedTabControl(
+                    new Tab(new PreloadController(mContext), mFactory.createWebView(false)));
+            touch();
+        }
+
+        public void cancelTimeout() {
+            mHandler.removeCallbacks(mTimeoutTask);
+        }
+
+        public void touch() {
+            cancelTimeout();
+            mHandler.postDelayed(mTimeoutTask, PRERENDER_TIMEOUT_MILLIS);
+        }
+
+        public PreloadedTabControl getTabControl() {
+            return mTabControl;
+        }
+
+    }
+
+}
diff --git a/src/com/android/browser/SnapshotTab.java b/src/com/android/browser/SnapshotTab.java
index 52a5c5f..f0abf58 100644
--- a/src/com/android/browser/SnapshotTab.java
+++ b/src/com/android/browser/SnapshotTab.java
@@ -20,19 +20,21 @@
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.graphics.BitmapFactory;
-import android.graphics.Color;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Bundle;
+import android.util.Log;
 import android.webkit.WebView;
 
-import com.android.browser.provider.BrowserProvider2.Snapshots;
+import com.android.browser.provider.SnapshotProvider.Snapshots;
 
 import java.io.ByteArrayInputStream;
+import java.util.zip.GZIPInputStream;
 
 
 public class SnapshotTab extends Tab {
 
+    private static final String LOGTAG = "SnapshotTab";
+
     private long mSnapshotId;
     private LoadData mLoadTask;
     private WebViewFactory mWebViewFactory;
@@ -76,7 +78,7 @@
 
     void loadData() {
         if (mLoadTask == null) {
-            mLoadTask = new LoadData(this, mActivity.getContentResolver());
+            mLoadTask = new LoadData(this, mContext.getContentResolver());
             mLoadTask.execute();
         }
     }
@@ -145,8 +147,13 @@
                     WebView web = mTab.getWebView();
                     if (web != null) {
                         byte[] data = result.getBlob(4);
-                        ByteArrayInputStream stream = new ByteArrayInputStream(data);
-                        web.loadViewState(stream);
+                        ByteArrayInputStream bis = new ByteArrayInputStream(data);
+                        try {
+                            GZIPInputStream stream = new GZIPInputStream(bis);
+                            web.loadViewState(stream);
+                        } catch (Exception e) {
+                            Log.w(LOGTAG, "Failed to load view state", e);
+                        }
                     }
                     mTab.mBackgroundColor = result.getInt(5);
                     mTab.mWebViewController.onPageFinished(mTab);
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index e672e2b..911726c 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Bitmap.CompressFormat;
 import android.net.Uri;
 import android.net.http.SslError;
 import android.os.Bundle;
@@ -63,7 +64,7 @@
 import android.widget.Toast;
 
 import com.android.browser.homepages.HomeProvider;
-import com.android.browser.provider.BrowserProvider2.Snapshots;
+import com.android.browser.provider.SnapshotProvider.Snapshots;
 import com.android.common.speech.LoggingEvents;
 
 import java.io.ByteArrayOutputStream;
@@ -73,6 +74,7 @@
 import java.util.LinkedList;
 import java.util.Map;
 import java.util.Vector;
+import java.util.zip.GZIPOutputStream;
 
 /**
  * Class for maintaining Tabs with a main WebView and a subwindow.
@@ -92,7 +94,7 @@
         LOCK_ICON_MIXED,
     }
 
-    Activity mActivity;
+    Context mContext;
     protected WebViewController mWebViewController;
 
     // The tab ID
@@ -304,7 +306,7 @@
                 logIntent.putExtra(
                         LoggingEvents.VoiceSearch.EXTRA_N_BEST_CHOOSE_INDEX,
                         index);
-                mActivity.sendBroadcast(logIntent);
+                mContext.sendBroadcast(logIntent);
             }
             if (mVoiceSearchData.mVoiceSearchIntent != null) {
                 // Copy the Intent, so that each history item will have its own
@@ -487,7 +489,7 @@
 
     private void showError(ErrorDialog errDialog) {
         if (mInForeground) {
-            AlertDialog d = new AlertDialog.Builder(mActivity)
+            AlertDialog d = new AlertDialog.Builder(mContext)
                     .setTitle(errDialog.mTitle)
                     .setMessage(errDialog.mDescription)
                     .setPositiveButton(R.string.ok, null)
@@ -508,7 +510,7 @@
         public void onPageStarted(WebView view, String url, Bitmap favicon) {
             mInPageLoad = true;
             mPageLoadProgress = 0;
-            mCurrentState = new PageState(mActivity,
+            mCurrentState = new PageState(mContext,
                     view.isPrivateBrowsingEnabled(), url, favicon);
             mLoadStartTime = SystemClock.uptimeMillis();
             if (mVoiceSearchData != null
@@ -516,7 +518,7 @@
                 if (mVoiceSearchData.mSourceIsGoogle) {
                     Intent i = new Intent(LoggingEvents.ACTION_LOG_EVENT);
                     i.putExtra(LoggingEvents.EXTRA_FLUSH, true);
-                    mActivity.sendBroadcast(i);
+                    mContext.sendBroadcast(i);
                 }
                 revertVoiceSearchMode();
             }
@@ -587,7 +589,7 @@
                 Intent logIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT);
                 logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
                         LoggingEvents.VoiceSearch.RESULT_CLICKED);
-                mActivity.sendBroadcast(logIntent);
+                mContext.sendBroadcast(logIntent);
             }
             if (mInForeground) {
                 return mWebViewController.shouldOverrideUrlLoading(Tab.this,
@@ -659,7 +661,7 @@
             }
             mDontResend = dontResend;
             mResend = resend;
-            new AlertDialog.Builder(mActivity).setTitle(
+            new AlertDialog.Builder(mContext).setTitle(
                     R.string.browserFrameFormResubmitLabel).setMessage(
                     R.string.browserFrameFormResubmitMessage)
                     .setPositiveButton(R.string.ok,
@@ -718,7 +720,7 @@
             }
             if (mSettings.showSecurityWarnings()) {
                 final LayoutInflater factory =
-                    LayoutInflater.from(mActivity);
+                    LayoutInflater.from(mContext);
                 final View warningsView =
                     factory.inflate(R.layout.ssl_warnings, null);
                 final LinearLayout placeholder =
@@ -756,7 +758,7 @@
                     placeholder.addView(ll);
                 }
 
-                new AlertDialog.Builder(mActivity).setTitle(
+                new AlertDialog.Builder(mContext).setTitle(
                         R.string.security_warning).setIcon(
                         android.R.drawable.ic_dialog_alert).setView(
                         warningsView).setPositiveButton(R.string.ssl_continue,
@@ -817,13 +819,14 @@
                     port = -1;
                 }
             }
-            KeyChain.choosePrivateKeyAlias(mActivity, new KeyChainAliasCallback() {
+            KeyChain.choosePrivateKeyAlias(
+                    mWebViewController.getActivity(), new KeyChainAliasCallback() {
                 @Override public void alias(String alias) {
                     if (alias == null) {
                         handler.cancel();
                         return;
                     }
-                    new KeyChainLookup(mActivity, handler, alias).execute();
+                    new KeyChainLookup(mContext, handler, alias).execute();
                 }
             }, null, null, host, port, null);
         }
@@ -846,7 +849,7 @@
         public WebResourceResponse shouldInterceptRequest(WebView view,
                 String url) {
             WebResourceResponse res = HomeProvider.shouldInterceptRequest(
-                    mActivity, url);
+                    mContext, url);
             return res;
         }
 
@@ -869,7 +872,7 @@
         @Override
         public void onReceivedLoginRequest(WebView view, String realm,
                 String account, String args) {
-            new DeviceAccountLogin(mActivity, view, Tab.this, mWebViewController)
+            new DeviceAccountLogin(mWebViewController.getActivity(), view, Tab.this, mWebViewController)
                     .handleLogin(realm, account, args);
         }
 
@@ -916,7 +919,7 @@
             }
             // Short-circuit if we can't create any more tabs or sub windows.
             if (dialog && mSubView != null) {
-                new AlertDialog.Builder(mActivity)
+                new AlertDialog.Builder(mContext)
                         .setTitle(R.string.too_many_subwindows_dialog_title)
                         .setIcon(android.R.drawable.ic_dialog_alert)
                         .setMessage(R.string.too_many_subwindows_dialog_message)
@@ -924,7 +927,7 @@
                         .show();
                 return false;
             } else if (!mWebViewController.getTabControl().canCreateNewTab()) {
-                new AlertDialog.Builder(mActivity)
+                new AlertDialog.Builder(mContext)
                         .setTitle(R.string.too_many_windows_dialog_title)
                         .setIcon(android.R.drawable.ic_dialog_alert)
                         .setMessage(R.string.too_many_windows_dialog_message)
@@ -958,7 +961,7 @@
 
             // Build a confirmation dialog to display to the user.
             final AlertDialog d =
-                    new AlertDialog.Builder(mActivity)
+                    new AlertDialog.Builder(mContext)
                     .setTitle(R.string.attention)
                     .setIcon(android.R.drawable.ic_dialog_alert)
                     .setMessage(R.string.popup_window_attempt)
@@ -1011,7 +1014,7 @@
         @Override
         public void onReceivedTouchIconUrl(WebView view, String url,
                 boolean precomposed) {
-            final ContentResolver cr = mActivity.getContentResolver();
+            final ContentResolver cr = mContext.getContentResolver();
             // Let precomposed icons take precedence over non-composed
             // icons.
             if (precomposed && mTouchIconLoader != null) {
@@ -1021,7 +1024,7 @@
             // Have only one async task at a time.
             if (mTouchIconLoader == null) {
                 mTouchIconLoader = new DownloadTouchIcon(Tab.this,
-                        mActivity, cr, view);
+                        mContext, cr, view);
                 mTouchIconLoader.execute(url);
             }
         }
@@ -1029,7 +1032,10 @@
         @Override
         public void onShowCustomView(View view,
                 WebChromeClient.CustomViewCallback callback) {
-            onShowCustomView(view, mActivity.getRequestedOrientation(), callback);
+            Activity activity = mWebViewController.getActivity();
+            if (activity != null) {
+                onShowCustomView(view, activity.getRequestedOrientation(), callback);
+            }
         }
 
         @Override
@@ -1202,8 +1208,8 @@
         public void setupAutoFill(Message message) {
             // Prompt the user to set up their profile.
             final Message msg = message;
-            AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
-            LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+            AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                     Context.LAYOUT_INFLATER_SERVICE);
             final View layout = inflater.inflate(R.layout.setup_autofill_dialog, null);
 
@@ -1217,7 +1223,7 @@
                         if (disableAutoFill.isChecked()) {
                             // Disable autofill and show a toast with how to turn it on again.
                             mSettings.setAutofillEnabled(false);
-                            Toast.makeText(mActivity,
+                            Toast.makeText(mContext,
                                     R.string.autofill_setup_dialog_negative_toast,
                                     Toast.LENGTH_LONG).show();
                         } else {
@@ -1338,10 +1344,10 @@
     // Construct a new tab
     Tab(WebViewController wvcontroller, WebView w) {
         mWebViewController = wvcontroller;
-        mActivity = mWebViewController.getActivity();
+        mContext = mWebViewController.getContext();
         mSettings = BrowserSettings.getInstance();
-        mDataController = DataController.getInstance(mActivity);
-        mCurrentState = new PageState(mActivity, w != null
+        mDataController = DataController.getInstance(mContext);
+        mCurrentState = new PageState(mContext, w != null
                 ? w.isPrivateBrowsingEnabled() : false);
         mInPageLoad = false;
         mInForeground = false;
@@ -1373,6 +1379,10 @@
         setWebView(w);
     }
 
+    public void setController(WebViewController ctl) {
+        mWebViewController = ctl;
+    }
+
     public void setId(long id) {
         mId = id;
     }
@@ -1468,7 +1478,7 @@
                     }
                 }
             });
-            mSubView.setOnCreateContextMenuListener(mActivity);
+            mSubView.setOnCreateContextMenuListener(mWebViewController.getActivity());
             return true;
         }
         return false;
@@ -1558,9 +1568,10 @@
     void putInForeground() {
         mInForeground = true;
         resume();
-        mMainView.setOnCreateContextMenuListener(mActivity);
+        Activity activity = mWebViewController.getActivity();
+        mMainView.setOnCreateContextMenuListener(activity);
         if (mSubView != null) {
-            mSubView.setOnCreateContextMenuListener(mActivity);
+            mSubView.setOnCreateContextMenuListener(activity);
         }
         // Show the pending error dialog if the queue is not empty
         if (mQueuedErrors != null && mQueuedErrors.size() >  0) {
@@ -1654,7 +1665,6 @@
                     .findViewById(R.id.geolocation_permissions_prompt);
             mGeolocationPermissionsPrompt = (GeolocationPermissionsPrompt) stub
                     .inflate();
-            mGeolocationPermissionsPrompt.init();
         }
         return mGeolocationPermissionsPrompt;
     }
@@ -1690,7 +1700,7 @@
      */
     String getTitle() {
         if (mCurrentState.mTitle == null && mInPageLoad) {
-            return mActivity.getString(R.string.title_bar_loading);
+            return mContext.getString(R.string.title_bar_loading);
         }
         return mCurrentState.mTitle;
     }
@@ -1716,7 +1726,7 @@
      */
     ErrorConsoleView getErrorConsole(boolean createIfNecessary) {
         if (createIfNecessary && mErrorConsole == null) {
-            mErrorConsole = new ErrorConsoleView(mActivity);
+            mErrorConsole = new ErrorConsoleView(mContext);
             mErrorConsole.setWebView(mMainView);
         }
         return mErrorConsole;
@@ -1867,22 +1877,45 @@
 
     public ContentValues createSnapshotValues() {
         if (mMainView == null) return null;
-        ByteArrayOutputStream stream = new ByteArrayOutputStream();
-        if (!mMainView.saveViewState(stream)) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try {
+            GZIPOutputStream stream = new GZIPOutputStream(bos);
+            if (!mMainView.saveViewState(stream)) {
+                return null;
+            }
+            stream.flush();
+            stream.close();
+        } catch (Exception e) {
+            Log.w(LOGTAG, "Failed to save view state", e);
             return null;
         }
-        byte[] data = stream.toByteArray();
+        byte[] data = bos.toByteArray();
         ContentValues values = new ContentValues();
         values.put(Snapshots.TITLE, mCurrentState.mTitle);
         values.put(Snapshots.URL, mCurrentState.mUrl);
         values.put(Snapshots.VIEWSTATE, data);
         values.put(Snapshots.BACKGROUND, mMainView.getPageBackgroundColor());
+        values.put(Snapshots.DATE_CREATED, System.currentTimeMillis());
+        values.put(Snapshots.FAVICON, compressBitmap(getFavicon()));
+        Bitmap screenshot = Controller.createScreenshot(mMainView,
+                Controller.getDesiredThumbnailWidth(mContext),
+                Controller.getDesiredThumbnailHeight(mContext));
+        values.put(Snapshots.THUMBNAIL, compressBitmap(screenshot));
         return values;
     }
 
+    public byte[] compressBitmap(Bitmap bitmap) {
+        if (bitmap == null) {
+            return null;
+        }
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        bitmap.compress(CompressFormat.PNG, 100, stream);
+        return stream.toByteArray();
+    }
+
     public void loadUrl(String url, Map<String, String> headers) {
         if (mMainView != null) {
-            mCurrentState = new PageState(mActivity, false, url, null);
+            mCurrentState = new PageState(mContext, false, url, null);
             mWebViewController.onPageStarted(this, mMainView, null);
             mMainView.loadUrl(url, headers);
         }
diff --git a/src/com/android/browser/TabBar.java b/src/com/android/browser/TabBar.java
index 8584afa..b2c2af8 100644
--- a/src/com/android/browser/TabBar.java
+++ b/src/com/android/browser/TabBar.java
@@ -16,8 +16,6 @@
 
 package com.android.browser;
 
-import com.android.browser.BrowserWebView.ScrollListener;
-
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorSet;
@@ -43,7 +41,6 @@
 import android.view.MenuInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.webkit.WebView;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -56,8 +53,7 @@
 /**
  * tabbed title bar for xlarge screen browser
  */
-public class TabBar extends LinearLayout
-        implements ScrollListener, OnClickListener {
+public class TabBar extends LinearLayout implements OnClickListener {
 
     private static final int PROGRESS_MAX = 100;
 
@@ -235,60 +231,6 @@
         mUi.showTitleBar();
     }
 
-    void showTitleBarIndicator(boolean show) {
-        Tab tab = mTabControl.getCurrentTab();
-        if (tab != null && !tab.isSnapshot()) {
-            TabView tv = mTabMap.get(tab);
-            if (tv != null) {
-                tv.showIndicator(show);
-            }
-        }
-    }
-
-    boolean showsTitleBarIndicator() {
-        Tab tab = mTabControl.getCurrentTab();
-        if (tab != null) {
-            TabView tv = mTabMap.get(tab);
-            if (tv != null) {
-                return tv.showsIndicator();
-            }
-        }
-        return false;
-    }
-
-    // callback after fake titlebar is shown
-    void onShowTitleBar() {
-        showTitleBarIndicator(false);
-    }
-
-    // callback after fake titlebar is hidden
-    void onHideTitleBar() {
-        Tab tab = mTabControl.getCurrentTab();
-        WebView w = tab.getWebView();
-        if (w != null) {
-            showTitleBarIndicator(w.getVisibleTitleHeight() == 0);
-        }
-    }
-
-    // webview scroll listener
-
-    @Override
-    public void onScroll(int visibleTitleHeight, boolean userInitiated) {
-        if (mUseQuickControls) return;
-        // isLoading is using the current tab, which initially might not be set yet
-        if (mTabControl.getCurrentTab() != null) {
-            if (visibleTitleHeight == 0 && !mUi.isTitleBarShowing()) {
-                if (!showsTitleBarIndicator()) {
-                    showTitleBarIndicator(true);
-                }
-            } else {
-                if (showsTitleBarIndicator()) {
-                    showTitleBarIndicator(false);
-                }
-            }
-        }
-    }
-
     @Override
     public void createContextMenu(ContextMenu menu) {
         MenuInflater inflater = mActivity.getMenuInflater();
@@ -319,7 +261,6 @@
         Tab mTab;
         View mTabContent;
         TextView mTitle;
-        View mIndicator;
         View mIncognito;
         View mSnapshot;
         ImageView mIconView;
@@ -353,33 +294,12 @@
             mClose.setOnClickListener(this);
             mIncognito = mTabContent.findViewById(R.id.incognito);
             mSnapshot = mTabContent.findViewById(R.id.snapshot);
-            mIndicator = mTabContent.findViewById(R.id.chevron);
             mSelected = false;
             mInLoad = false;
             // update the status
             updateFromTab();
         }
 
-        void showIndicator(boolean show) {
-            if (mSelected) {
-                mIndicator.setVisibility(show ? View.VISIBLE : View.GONE);
-                LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
-                if (show) {
-                    lp.width = mTabWidthSelected + mIndicator.getWidth();
-                } else {
-                    lp.width = mTabWidthSelected;
-                }
-                lp.height =  LayoutParams.MATCH_PARENT;
-                setLayoutParams(lp);
-            } else {
-                mIndicator.setVisibility(View.GONE);
-            }
-        }
-
-        boolean showsIndicator() {
-            return (mIndicator.getVisibility() == View.VISIBLE);
-        }
-
         @Override
         public void onClick(View v) {
             if (v == mClose) {
@@ -412,7 +332,6 @@
         public void setActivated(boolean selected) {
             mSelected = selected;
             mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE);
-            mIndicator.setVisibility(View.GONE);
             mTitle.setTextAppearance(mActivity, mSelected ?
                     R.style.TabTitleSelected : R.style.TabTitleUnselected);
             setHorizontalFadingEdgeEnabled(!mSelected);
@@ -623,12 +542,6 @@
         TabView tv = mTabMap.get(tab);
         if (tv != null) {
             tv.setProgress(tv.mTab.getLoadProgress());
-            // update the scroll state
-            WebView webview = tab.getWebView();
-            if (webview != null) {
-                int h = webview.getVisibleTitleHeight();
-                onScroll(h, true);
-            }
         }
     }
 
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 2eb24e9..5f3995f 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -16,14 +16,10 @@
 
 package com.android.browser;
 
-import android.content.ContentResolver;
-import android.database.Cursor;
 import android.os.Bundle;
 import android.util.Log;
 import android.webkit.WebView;
 
-import com.android.browser.provider.BrowserProvider2.Snapshots;
-
 import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -176,6 +172,14 @@
         return false;
     }
 
+    void addPreloadedTab(Tab tab) {
+        tab.setId(getNextId());
+        mTabs.add(tab);
+        tab.setController(mController);
+        mController.onSetWebView(tab, tab.getWebView());
+        tab.putInBackground();
+    }
+
     /**
      * Create a new tab.
      * @return The newly createTab or null if we have reached the maximum
@@ -207,7 +211,6 @@
     }
 
     SnapshotTab createSnapshotTab(long snapshotId) {
-        // TODO: Don't count this against the limit
         SnapshotTab t = new SnapshotTab(mController, snapshotId);
         t.setId(getNextId());
         mTabs.add(t);
@@ -427,21 +430,6 @@
                 }
             }
         }
-        loadSnapshotTabs();
-
-    }
-
-    void loadSnapshotTabs() {
-        ContentResolver cr = mController.getActivity().getContentResolver();
-        Cursor c = cr.query(Snapshots.CONTENT_URI, new String[] { "_id" },
-                null, null, null);
-        try {
-            while (c.moveToNext()) {
-                createSnapshotTab(c.getLong(0));
-            }
-        } finally {
-            c.close();
-        }
     }
 
     /**
diff --git a/src/com/android/browser/TitleBarBase.java b/src/com/android/browser/TitleBarBase.java
index c7fb9c6..de44306 100644
--- a/src/com/android/browser/TitleBarBase.java
+++ b/src/com/android/browser/TitleBarBase.java
@@ -493,13 +493,6 @@
     }
 
     protected void setFocusState(boolean focus) {
-        if (focus) {
-            updateSearchMode(false);
-        }
-    }
-
-    protected void updateSearchMode(boolean userEdited) {
-        setSearchMode(!userEdited || TextUtils.isEmpty(mUrlInput.getUserText()));
     }
 
     protected void setSearchMode(boolean voiceSearchEnabled) {}
@@ -523,8 +516,6 @@
     @Override
     public void onTextChanged(String newText) {
         if (mUrlInput.hasFocus()) {
-            // check if input field is empty and adjust voice search state
-            updateSearchMode(true);
             // clear voice mode when user types
             setInVoiceMode(false, null);
         }
@@ -554,7 +545,15 @@
     @Override
     public void onAction(String text, String extra, String source) {
         mUiController.getCurrentTopWebView().requestFocus();
-        mBaseUi.hideTitleBar();
+        if (UrlInputView.TYPED.equals(source)) {
+            String url = UrlUtils.smartUrlFilter(text, false);
+            Tab t = mBaseUi.getActiveTab();
+            if (url != null && t != null) {
+                mUiController.loadUrl(t, url);
+                setDisplayTitle(text);
+                return;
+            }
+        }
         Intent i = new Intent();
         String action = null;
         if (UrlInputView.VOICE.equals(source)) {
diff --git a/src/com/android/browser/TitleBarPhone.java b/src/com/android/browser/TitleBarPhone.java
index 1fcaf4d..f41eca7 100644
--- a/src/com/android/browser/TitleBarPhone.java
+++ b/src/com/android/browser/TitleBarPhone.java
@@ -16,8 +16,6 @@
 
 package com.android.browser;
 
-import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
-
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Resources;
@@ -29,9 +27,11 @@
 import android.view.View.OnFocusChangeListener;
 import android.webkit.WebView;
 import android.widget.FrameLayout;
-import android.widget.ImageButton;
 import android.widget.ImageView;
 
+import com.android.browser.UrlInputView.StateListener;
+import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
+
 import java.util.List;
 
 /**
@@ -39,15 +39,17 @@
  * browser.
  */
 public class TitleBarPhone extends TitleBarBase implements OnFocusChangeListener,
-        OnClickListener, TextChangeWatcher {
+        OnClickListener, TextChangeWatcher, StateListener {
 
     private Activity mActivity;
     private ImageView mStopButton;
     private ImageView mVoiceButton;
-    private boolean mHasLockIcon;
-    private ImageButton mForward;
     private Drawable mStopDrawable;
     private Drawable mRefreshDrawable;
+    private View mTabSwitcher;
+    private View mComboIcon;
+    private View mTitleContainer;
+    private Drawable mTextfieldBgDrawable;
 
     public TitleBarPhone(Activity activity, UiController controller, PhoneUi ui,
             FrameLayout parent) {
@@ -65,14 +67,18 @@
         mStopButton.setOnClickListener(this);
         mVoiceButton = (ImageView) findViewById(R.id.voice);
         mVoiceButton.setOnClickListener(this);
-        mForward = (ImageButton) findViewById(R.id.forward);
-        mForward.setOnClickListener(this);
+        mTabSwitcher = findViewById(R.id.tab_switcher);
+        mTabSwitcher.setOnClickListener(this);
+        mComboIcon = findViewById(R.id.iconcombo);
+        mTitleContainer = findViewById(R.id.title_bg);
         setFocusState(false);
         Resources res = context.getResources();
         mStopDrawable = res.getDrawable(R.drawable.ic_stop_holo_dark);
         mRefreshDrawable = res.getDrawable(R.drawable.ic_refresh_holo_dark);
-        setUaSwitcher(mFavicon);
+        mTextfieldBgDrawable = res.getDrawable(R.drawable.textfield_active_holo_dark);
+        setUaSwitcher(mComboIcon);
         mUrlInput.setContainer(this);
+        mUrlInput.setStateListener(this);
     }
 
     @Override
@@ -96,29 +102,23 @@
     }
 
     @Override
-    protected void setFocusState(boolean focus) {
-        super.setFocusState(focus);
-        if (focus) {
-            mHasLockIcon = (mLockIcon.getVisibility() == View.VISIBLE);
-            mLockIcon.setVisibility(View.GONE);
-            mStopButton.setVisibility(View.GONE);
-            mVoiceButton.setVisibility(View.VISIBLE);
-        } else {
-            mLockIcon.setVisibility(mHasLockIcon ? View.VISIBLE : View.GONE);
-            mStopButton.setVisibility(View.VISIBLE);
-            mVoiceButton.setVisibility(View.GONE);
-        }
-    }
-
-    @Override
     void setProgress(int progress) {
         super.setProgress(progress);
         if (progress == 100) {
+            mStopButton.setVisibility(View.GONE);
             mStopButton.setImageDrawable(mRefreshDrawable);
-        } else if (mStopButton.getDrawable() != mStopDrawable) {
-            mStopButton.setImageDrawable(mStopDrawable);
+            if (!isEditingUrl()) {
+                mComboIcon.setVisibility(View.VISIBLE);
+            }
+        } else {
+            if (mStopButton.getDrawable() != mStopDrawable) {
+                mStopButton.setImageDrawable(mStopDrawable);
+                if (mStopButton.getVisibility() != View.VISIBLE) {
+                    mComboIcon.setVisibility(View.GONE);
+                    mStopButton.setVisibility(View.VISIBLE);
+                }
+            }
         }
-        updateNavigationState();
     }
 
     /**
@@ -135,7 +135,6 @@
                 mUrlInput.setText(title);
             }
             mUrlInput.setSelection(0);
-            updateNavigationState();
         }
     }
 
@@ -157,25 +156,43 @@
             } else {
                 WebView web = mBaseUi.getWebView();
                 if (web != null) {
+                    stopEditingUrl();
                     web.reload();
                 }
             }
         } else if (v == mVoiceButton) {
             mUiController.startVoiceSearch();
-        } else if (v == mForward) {
-            WebView web = mBaseUi.getWebView();
-            if (web != null) {
-                web.goForward();
-            }
+        } else if (v == mTabSwitcher) {
+            mBaseUi.onMenuKey();
         } else {
             super.onClick(v);
         }
     }
 
-    private void updateNavigationState() {
-        WebView web = mBaseUi.getWebView();
-        if (web != null) {
-          mForward.setVisibility(web.canGoForward() ? View.VISIBLE : View.GONE);
+    @Override
+    public void onStateChanged(int state) {
+        switch(state) {
+        case StateListener.STATE_NORMAL:
+            mComboIcon.setVisibility(View.VISIBLE);
+            mStopButton.setVisibility(View.GONE);
+            setSearchMode(false);
+            mTabSwitcher.setVisibility(View.VISIBLE);
+            mTitleContainer.setBackgroundDrawable(null);
+            break;
+        case StateListener.STATE_HIGHLIGHTED:
+            mComboIcon.setVisibility(View.GONE);
+            mStopButton.setVisibility(View.VISIBLE);
+            setSearchMode(true);
+            mTabSwitcher.setVisibility(View.GONE);
+            mTitleContainer.setBackgroundDrawable(mTextfieldBgDrawable);
+            break;
+        case StateListener.STATE_EDITED:
+            mComboIcon.setVisibility(View.GONE);
+            mStopButton.setVisibility(View.GONE);
+            setSearchMode(false);
+            mTabSwitcher.setVisibility(View.GONE);
+            mTitleContainer.setBackgroundDrawable(mTextfieldBgDrawable);
+            break;
         }
     }
 
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
index 3d4d2ec..6aed86c 100644
--- a/src/com/android/browser/TitleBarXLarge.java
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -218,7 +218,6 @@
         mStopButton.setImageDrawable(mReloadDrawable);
     }
 
-    @Override
     protected void updateSearchMode(boolean userEdited) {
         setSearchMode(!userEdited || TextUtils.isEmpty(mUrlInput.getUserText()));
     }
diff --git a/src/com/android/browser/UI.java b/src/com/android/browser/UI.java
index a6356a3..93f6e63 100644
--- a/src/com/android/browser/UI.java
+++ b/src/com/android/browser/UI.java
@@ -33,6 +33,12 @@
  */
 public interface UI {
 
+    public static enum ComboViews {
+        History,
+        Bookmarks,
+        Snapshots,
+    }
+
     public void onPause();
 
     public void onResume();
@@ -77,7 +83,7 @@
 
     public void removeActiveTabsPage();
 
-    public void showComboView(boolean startWithHistory, Bundle extra);
+    public void showComboView(ComboViews startingView, Bundle extra);
 
     public void hideComboView();
 
diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java
index 05eaf8e..6045d86 100644
--- a/src/com/android/browser/UiController.java
+++ b/src/com/android/browser/UiController.java
@@ -97,4 +97,8 @@
 
     boolean onOptionsItemSelected(MenuItem item);
 
+    SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive);
+
+    void loadUrl(Tab tab, String url);
+
 }
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
index 7545e6a..8b5c292 100644
--- a/src/com/android/browser/UrlInputView.java
+++ b/src/com/android/browser/UrlInputView.java
@@ -16,14 +16,6 @@
 
 package com.android.browser;
 
-import com.android.browser.SuggestionsAdapter.CompletionListener;
-import com.android.browser.SuggestionsAdapter.SuggestItem;
-import com.android.browser.UI.DropdownChangeListener;
-import com.android.browser.autocomplete.SuggestiveAutoCompleteTextView;
-import com.android.browser.search.SearchEngine;
-import com.android.browser.search.SearchEngineInfo;
-import com.android.browser.search.SearchEngines;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.DataSetObserver;
@@ -32,6 +24,7 @@
 import android.util.AttributeSet;
 import android.util.Patterns;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -40,6 +33,15 @@
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
+import com.android.browser.SuggestionsAdapter.CompletionListener;
+import com.android.browser.SuggestionsAdapter.SuggestItem;
+import com.android.browser.UI.DropdownChangeListener;
+import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
+import com.android.browser.autocomplete.SuggestiveAutoCompleteTextView;
+import com.android.browser.search.SearchEngine;
+import com.android.browser.search.SearchEngineInfo;
+import com.android.browser.search.SearchEngines;
+
 import java.util.List;
 
 /**
@@ -48,13 +50,20 @@
  */
 public class UrlInputView extends SuggestiveAutoCompleteTextView
         implements OnEditorActionListener,
-        CompletionListener, OnItemClickListener {
-
+        CompletionListener, OnItemClickListener, TextChangeWatcher {
 
     static final String TYPED = "browser-type";
     static final String SUGGESTED = "browser-suggest";
     static final String VOICE = "voice-search";
 
+    static interface StateListener {
+        static final int STATE_NORMAL = 0;
+        static final int STATE_HIGHLIGHTED = 1;
+        static final int STATE_EDITED = 2;
+
+        public void onStateChanged(int state);
+    }
+
     private UrlInputListener   mListener;
     private InputMethodManager mInputManager;
     private SuggestionsAdapter mAdapter;
@@ -64,6 +73,9 @@
     private boolean mNeedsUpdate;
     private DropdownChangeListener mDropdownListener;
 
+    private int mState;
+    private StateListener mStateListener;
+
     public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         init(context);
@@ -90,6 +102,7 @@
         setOnItemClickListener(this);
         mNeedsUpdate = false;
         mDropdownListener = null;
+        addQueryTextWatcher(this);
 
         mAdapter.registerDataSetObserver(new DataSetObserver() {
             @Override
@@ -105,6 +118,32 @@
                 dispatchChange();
             }
         });
+        mState = StateListener.STATE_NORMAL;
+    }
+
+    protected void onFocusChanged(boolean focused, int direction, Rect prevRect) {
+        super.onFocusChanged(focused, direction, prevRect);
+        if (focused) {
+            if (hasSelection()) {
+                changeState(StateListener.STATE_HIGHLIGHTED);
+            } else {
+                changeState(StateListener.STATE_EDITED);
+            }
+        } else {
+            // reset the selection state
+            changeState(StateListener.STATE_NORMAL);
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent evt) {
+        boolean hasSelection = hasSelection();
+        boolean res = super.onTouchEvent(evt);
+        if ((MotionEvent.ACTION_DOWN == evt.getActionMasked())
+              && hasSelection) {
+            changeState(StateListener.STATE_EDITED);
+        }
+        return res;
     }
 
     /**
@@ -135,6 +174,19 @@
         mListener = listener;
     }
 
+    public void setStateListener(StateListener listener) {
+        mStateListener = listener;
+        // update listener
+        changeState(mState);
+    }
+
+    private void changeState(int newState) {
+        mState = newState;
+        if (mStateListener != null) {
+            mStateListener.onStateChanged(mState);
+        }
+    }
+
     void setVoiceResults(List<String> voiceResults) {
         mAdapter.setVoiceResults(voiceResults);
     }
@@ -305,4 +357,12 @@
     public boolean requestRectangleOnScreen(Rect rect, boolean immediate) {
         return false;
     }
+
+    @Override
+    public void onTextChanged(String newText) {
+        if (StateListener.STATE_HIGHLIGHTED == mState) {
+            changeState(StateListener.STATE_EDITED);
+        }
+    }
+
 }
diff --git a/src/com/android/browser/UrlUtils.java b/src/com/android/browser/UrlUtils.java
index 26f8e0e..c922e55 100644
--- a/src/com/android/browser/UrlUtils.java
+++ b/src/com/android/browser/UrlUtils.java
@@ -85,7 +85,22 @@
      *
      */
     public static String smartUrlFilter(String url) {
+        return smartUrlFilter(url, true);
+    }
 
+    /**
+     * Attempts to determine whether user input is a URL or search
+     * terms.  Anything with a space is passed to search if canBeSearch is true.
+     *
+     * Converts to lowercase any mistakenly uppercased schema (i.e.,
+     * "Http://" converts to "http://"
+     *
+     * @param canBeSearch If true, will return a search url if it isn't a valid
+     *                    URL. If false, invalid URLs will return null
+     * @return Original or modified URL
+     *
+     */
+    public static String smartUrlFilter(String url, boolean canBeSearch) {
         String inUrl = url.trim();
         boolean hasSpace = inUrl.indexOf(' ') != -1;
 
@@ -97,7 +112,7 @@
             if (!lcScheme.equals(scheme)) {
                 inUrl = lcScheme + matcher.group(2);
             }
-            if (hasSpace) {
+            if (hasSpace && Patterns.WEB_URL.matcher(inUrl).matches()) {
                 inUrl = inUrl.replace(" ", "%20");
             }
             return inUrl;
@@ -107,12 +122,11 @@
                 return URLUtil.guessUrl(inUrl);
             }
         }
-
-        // FIXME: Is this the correct place to add to searches?
-        // what if someone else calls this function?
-
-//        Browser.addSearchUrl(mBrowser.getContentResolver(), inUrl);
-        return URLUtil.composeSearchUrl(inUrl, QUICKSEARCH_G, QUERY_PLACE_HOLDER);
+        if (canBeSearch) {
+            return URLUtil.composeSearchUrl(inUrl,
+                    QUICKSEARCH_G, QUERY_PLACE_HOLDER);
+        }
+        return null;
     }
 
     /* package */ static String fixUrl(String inUrl) {
diff --git a/src/com/android/browser/WebViewController.java b/src/com/android/browser/WebViewController.java
index 018af99..175cbf8 100644
--- a/src/com/android/browser/WebViewController.java
+++ b/src/com/android/browser/WebViewController.java
@@ -17,6 +17,7 @@
 package com.android.browser;
 
 import android.app.Activity;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.net.http.SslError;
@@ -36,6 +37,8 @@
  */
 public interface WebViewController {
 
+    Context getContext();
+
     Activity getActivity();
 
     TabControl getTabControl();
@@ -72,7 +75,7 @@
     void onDownloadStart(Tab tab, String url, String useragent, String contentDisposition,
             String mimeType, long contentLength);
 
-    void showCustomView(Tab tab, View view, int requestedOrientation, 
+    void showCustomView(Tab tab, View view, int requestedOrientation,
             WebChromeClient.CustomViewCallback callback);
 
     void hideCustomView();
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index 84ad6ea..6fcfab7 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -16,8 +16,6 @@
 
 package com.android.browser;
 
-import com.android.browser.BrowserWebView.ScrollListener;
-
 import android.app.ActionBar;
 import android.app.Activity;
 import android.os.Bundle;
@@ -44,7 +42,6 @@
 
     private TitleBarXLarge mTitleBar;
 
-    private boolean mUseQuickControls;
     private PieControl mPieControl;
     private Handler mHandler;
 
@@ -70,9 +67,8 @@
         mActionBar.setCustomView(mTabBar);
     }
 
-    @Override
-    public void showComboView(boolean startWithHistory, Bundle extras) {
-        super.showComboView(startWithHistory, extras);
+    public void showComboView(ComboViews startWith, Bundle extras) {
+        super.showComboView(startWith, extras);
         if (mUseQuickControls) {
             mActionBar.show();
         }
@@ -136,27 +132,6 @@
         hideTitleBar();
     }
 
-    // webview factory
-
-    @Override
-    public WebView createWebView(boolean privateBrowsing) {
-        // Create a new WebView
-        BrowserWebView w = (BrowserWebView) super.createWebView(privateBrowsing);
-        w.setScrollListener(this);
-        return w;
-    }
-
-    @Override
-    public WebView createSubWebView(boolean privateBrowsing) {
-        return super.createWebView(privateBrowsing);
-    }
-
-    @Override
-    public void onScroll(int visibleTitleHeight, boolean userInitiated) {
-        super.onScroll(visibleTitleHeight, userInitiated);
-        mTabBar.onScroll(visibleTitleHeight, userInitiated);
-    }
-
     void stopWebViewScrolling() {
         BrowserWebView web = (BrowserWebView) mUiController.getCurrentWebView();
         if (web != null) {
@@ -204,14 +179,11 @@
         // Request focus on the top window.
         if (mUseQuickControls) {
             mPieControl.forceToTop(mContentView);
-            view.setScrollListener(null);
-            mTabBar.showTitleBarIndicator(false);
         } else {
             // check if title bar is already attached by animation
             if (mTitleBar.getParent() == null && !tab.isSnapshot()) {
                 view.setEmbeddedTitleBar(mTitleBar);
             }
-            view.setScrollListener(this);
         }
         mTabBar.onSetActiveTab(tab);
         if (tab.isInVoiceSearchMode()) {
@@ -266,14 +238,12 @@
     protected void showTitleBar() {
         if (canShowTitleBar()) {
             mTitleBar.show();
-            mTabBar.onShowTitleBar();
         }
     }
 
     @Override
     protected void hideTitleBar() {
         if (isTitleBarShowing()) {
-            mTabBar.onHideTitleBar();
             mTitleBar.hide();
         }
     }
diff --git a/src/com/android/browser/preferences/BandwidthPreferencesFragment.java b/src/com/android/browser/preferences/BandwidthPreferencesFragment.java
new file mode 100644
index 0000000..18b9fa4
--- /dev/null
+++ b/src/com/android/browser/preferences/BandwidthPreferencesFragment.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.browser.preferences;
+
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.util.Log;
+
+import com.android.browser.PreferenceKeys;
+import com.android.browser.R;
+
+public class BandwidthPreferencesFragment extends PreferenceFragment {
+
+    static final String TAG = "BandwidthPreferencesFragment";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Load the XML preferences file
+        addPreferencesFromResource(R.xml.bandwidth_preferences);
+    }
+
+}
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index 06e4e4a..b974c0e 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -68,18 +68,6 @@
 
 public class BrowserProvider2 extends SQLiteContentProvider {
 
-    public static interface Snapshots {
-
-        public static final Uri CONTENT_URI = Uri.withAppendedPath(
-                BrowserContract.AUTHORITY_URI, "snapshots");
-        public static final String _ID = "_id";
-        public static final String VIEWSTATE = "view_state";
-        public static final String BACKGROUND = "background";
-        public static final String TITLE = History.TITLE;
-        public static final String URL = History.URL;
-        public static final String FAVICON = History.FAVICON;
-    }
-
     public static final String PARAM_GROUP_BY = "groupBy";
 
     public static final String LEGACY_AUTHORITY = "browser";
@@ -151,9 +139,6 @@
     static final int LEGACY = 9000;
     static final int LEGACY_ID = 9001;
 
-    static final int SNAPSHOTS = 10000;
-    static final int SNAPSHOTS_ID = 10001;
-
     public static final long FIXED_ID_ROOT = 1;
 
     // Default sort order for unsync'd bookmarks
@@ -215,9 +200,6 @@
                 "bookmarks/" + SearchManager.SUGGEST_URI_PATH_QUERY,
                 BOOKMARKS_SUGGESTIONS);
 
-        matcher.addURI(authority, "snapshots", SNAPSHOTS);
-        matcher.addURI(authority, "snapshots/#", SNAPSHOTS_ID);
-
         // Projection maps
         HashMap<String, String> map;
 
@@ -351,7 +333,7 @@
 
     final class DatabaseHelper extends SQLiteOpenHelper {
         static final String DATABASE_NAME = "browser2.db";
-        static final int DATABASE_VERSION = 29;
+        static final int DATABASE_VERSION = 30;
         public DatabaseHelper(Context context) {
             super(context, DATABASE_NAME, null, DATABASE_VERSION);
         }
@@ -422,8 +404,6 @@
             }
 
             enableSync(db);
-
-            createSnapshots(db);
         }
 
         void enableSync(SQLiteDatabase db) {
@@ -520,8 +500,9 @@
 
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            if (oldVersion < 29) {
-                createSnapshots(db);
+            if (oldVersion < 30) {
+                db.execSQL("DROP VIEW IF EXISTS " + VIEW_SNAPSHOTS_COMBINED);
+                db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
             }
             if (oldVersion < 28) {
                 enableSync(db);
@@ -543,23 +524,6 @@
             }
         }
 
-        void createSnapshots(SQLiteDatabase db) {
-            db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
-            db.execSQL("CREATE TABLE " + TABLE_SNAPSHOTS + " (" +
-                    Snapshots._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                    Snapshots.URL + " TEXT NOT NULL," +
-                    Snapshots.TITLE + " TEXT," +
-                    Snapshots.BACKGROUND + " INTEGER," +
-                    Snapshots.VIEWSTATE + " BLOB NOT NULL" +
-                    ");");
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_SNAPSHOTS_COMBINED +
-                    " AS SELECT * FROM " + TABLE_SNAPSHOTS +
-                    " LEFT OUTER JOIN " + TABLE_IMAGES +
-                    " ON " + TABLE_SNAPSHOTS + "." + Snapshots.URL +
-                    " = images.url_key");
-        }
-
-        @Override
         public void onOpen(SQLiteDatabase db) {
             db.enableWriteAheadLogging();
             mSyncHelper.onDatabaseOpened(db);
@@ -1010,17 +974,6 @@
                 break;
             }
 
-            case SNAPSHOTS_ID: {
-                selection = DatabaseUtils.concatenateWhere(selection, "_id=?");
-                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
-                        new String[] { Long.toString(ContentUris.parseId(uri)) });
-                // fall through
-            }
-            case SNAPSHOTS: {
-                qb.setTables(VIEW_SNAPSHOTS_COMBINED);
-                break;
-            }
-
             default: {
                 throw new UnsupportedOperationException("Unknown URL " + uri.toString());
             }
@@ -1220,16 +1173,6 @@
                 }
                 break;
             }
-            case SNAPSHOTS_ID: {
-                selection = DatabaseUtils.concatenateWhere(selection, TABLE_SNAPSHOTS + "._id=?");
-                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
-                        new String[] { Long.toString(ContentUris.parseId(uri)) });
-                // fall through
-            }
-            case SNAPSHOTS: {
-                deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs);
-                break;
-            }
             default: {
                 throw new UnsupportedOperationException("Unknown delete URI " + uri);
             }
@@ -1367,11 +1310,6 @@
                 break;
             }
 
-            case SNAPSHOTS: {
-                id = db.insertOrThrow(TABLE_SNAPSHOTS, Snapshots.TITLE, values);
-                break;
-            }
-
             default: {
                 throw new UnsupportedOperationException("Unknown insert URI " + uri);
             }
diff --git a/src/com/android/browser/provider/SnapshotProvider.java b/src/com/android/browser/provider/SnapshotProvider.java
new file mode 100644
index 0000000..49557f7
--- /dev/null
+++ b/src/com/android/browser/provider/SnapshotProvider.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.browser.provider;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.BrowserContract;
+
+import java.io.File;
+
+/**
+ * This provider is expected to be potentially flaky. It uses a database
+ * stored on external storage, which could be yanked unexpectedly.
+ */
+public class SnapshotProvider extends ContentProvider {
+
+    public static interface Snapshots {
+
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                SnapshotProvider.AUTHORITY_URI, "snapshots");
+        public static final String _ID = "_id";
+        public static final String VIEWSTATE = "view_state";
+        public static final String BACKGROUND = "background";
+        public static final String TITLE = "title";
+        public static final String URL = "url";
+        public static final String FAVICON = "favicon";
+        public static final String THUMBNAIL = "thumbnail";
+        public static final String DATE_CREATED = "date_created";
+    }
+
+    public static final String AUTHORITY = "com.android.browser.snapshots";
+    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+    static final String TABLE_SNAPSHOTS = "snapshots";
+    static final int SNAPSHOTS = 10;
+    static final int SNAPSHOTS_ID = 11;
+    static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+    SnapshotDatabaseHelper mOpenHelper;
+
+    static {
+        URI_MATCHER.addURI(AUTHORITY, "snapshots", SNAPSHOTS);
+        URI_MATCHER.addURI(AUTHORITY, "snapshots/#", SNAPSHOTS_ID);
+    }
+
+    final static class SnapshotDatabaseHelper extends SQLiteOpenHelper {
+
+        static final String DATABASE_NAME = "snapshots.db";
+        static final int DATABASE_VERSION = 1;
+
+        public SnapshotDatabaseHelper(Context context) {
+            super(context, getFullDatabaseName(context), null, DATABASE_VERSION);
+        }
+
+        static String getFullDatabaseName(Context context) {
+            File dir = context.getExternalFilesDir(null);
+            return new File(dir, DATABASE_NAME).getAbsolutePath();
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_SNAPSHOTS + "(" +
+                    Snapshots._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    Snapshots.TITLE + " TEXT," +
+                    Snapshots.URL + " TEXT NOT NULL," +
+                    Snapshots.DATE_CREATED + " INTEGER," +
+                    Snapshots.FAVICON + " BLOB," +
+                    Snapshots.THUMBNAIL + " BLOB," +
+                    Snapshots.BACKGROUND + " INTEGER," +
+                    Snapshots.VIEWSTATE + " BLOB NOT NULL" +
+                    ");");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            // Not needed yet
+        }
+
+    }
+
+    @Override
+    public boolean onCreate() {
+        mOpenHelper = new SnapshotDatabaseHelper(getContext());
+        IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_EJECT);
+        filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        getContext().registerReceiver(mExternalStorageReceiver, filter);
+        return true;
+    }
+
+    final BroadcastReceiver mExternalStorageReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            try {
+                mOpenHelper.close();
+            } catch (Throwable t) {
+                // We failed to close the open helper, which most likely means
+                // another thread is busy attempting to open the database
+                // or use the database. Let that thread try to gracefully
+                // deal with the error
+            }
+        }
+    };
+
+    SQLiteDatabase getWritableDatabase() {
+        String state = Environment.getExternalStorageState();
+        if (Environment.MEDIA_MOUNTED.equals(state)) {
+            try {
+                return mOpenHelper.getWritableDatabase();
+            } catch (Throwable t) {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    SQLiteDatabase getReadableDatabase() {
+        String state = Environment.getExternalStorageState();
+        if (Environment.MEDIA_MOUNTED.equals(state)
+                || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+            try {
+                return mOpenHelper.getReadableDatabase();
+            } catch (Throwable t) {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        SQLiteDatabase db = getReadableDatabase();
+        if (db == null) {
+            return null;
+        }
+        final int match = URI_MATCHER.match(uri);
+        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
+        switch (match) {
+        case SNAPSHOTS_ID:
+            selection = DatabaseUtils.concatenateWhere(selection, "_id=?");
+            selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+                    new String[] { Long.toString(ContentUris.parseId(uri)) });
+            // fall through
+        case SNAPSHOTS:
+            qb.setTables(TABLE_SNAPSHOTS);
+            break;
+
+        default:
+            throw new UnsupportedOperationException("Unknown URL " + uri.toString());
+        }
+        try {
+            Cursor cursor = qb.query(db, projection, selection, selectionArgs,
+                    null, null, sortOrder, limit);
+            cursor.setNotificationUri(getContext().getContentResolver(),
+                    AUTHORITY_URI);
+            return cursor;
+        } catch (Throwable t) {
+            return null;
+        }
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        SQLiteDatabase db = getWritableDatabase();
+        if (db == null) {
+            return null;
+        }
+        int match = URI_MATCHER.match(uri);
+        long id = -1;
+        switch (match) {
+        case SNAPSHOTS:
+            try {
+                id = db.insert(TABLE_SNAPSHOTS, Snapshots.TITLE, values);
+            } catch (Throwable t) {
+                id = -1;
+            }
+            break;
+        default:
+            throw new UnsupportedOperationException("Unknown insert URI " + uri);
+        }
+        if (id < 0) {
+            return null;
+        }
+        Uri inserted = ContentUris.withAppendedId(uri, id);
+        getContext().getContentResolver().notifyChange(inserted, null, false);
+        return inserted;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        SQLiteDatabase db = getWritableDatabase();
+        if (db == null) {
+            return 0;
+        }
+        int match = URI_MATCHER.match(uri);
+        int deleted = 0;
+        switch (match) {
+        case SNAPSHOTS_ID: {
+            selection = DatabaseUtils.concatenateWhere(selection, TABLE_SNAPSHOTS + "._id=?");
+            selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+                    new String[] { Long.toString(ContentUris.parseId(uri)) });
+            // fall through
+        }
+        case SNAPSHOTS:
+            try {
+                deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs);
+            } catch (Throwable t) {
+            }
+            break;
+        default:
+            throw new UnsupportedOperationException("Unknown delete URI " + uri);
+        }
+        if (deleted > 0) {
+            getContext().getContentResolver().notifyChange(uri, null, false);
+        }
+        return deleted;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+}