am 52c6209e: (-s ours) am cd54e35a: (-s ours) am df2a0ab1: Import revised translations.  DO NOT MERGE

* commit '52c6209e393e12f2db9b3438b00c3bb8823a923d':
  Import revised translations.  DO NOT MERGE
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f88dfd9..9ddc7b5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -35,6 +35,7 @@
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
     <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
     <uses-permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS"/>
     <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
@@ -165,7 +166,7 @@
         </activity>
 
         <activity android:name="AddBookmarkPage" android:label="Save bookmark"
-                  android:theme="@style/Dialog"
+                  android:theme="@style/DialogWhenLarge"
                   android:configChanges="orientation|keyboardHidden"
                   android:windowSoftInputMode="stateHidden|adjustPan">
             <intent-filter>
@@ -193,6 +194,9 @@
             android:name=".widget.BookmarkThumbnailWidgetService"
             android:permission="android.permission.BIND_REMOTEVIEWS"
             android:exported="false" />
+        <receiver
+            android:name=".widget.BookmarkWidgetProxy"
+            android:exported="false" />
 
         <!-- Makes .BrowserActivity the search target for any activity in Browser -->
         <meta-data android:name="android.app.default_searchable" android:value=".BrowserActivity" />
diff --git a/res/drawable-hdpi/bookmarks_widget_thumb_selector_focused.9.png b/res/drawable-hdpi/bookmarks_widget_thumb_selector_focused.9.png
new file mode 100644
index 0000000..9983890
--- /dev/null
+++ b/res/drawable-hdpi/bookmarks_widget_thumb_selector_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi/bookmarks_widget_thumb_selector_longpressed.9.png b/res/drawable-hdpi/bookmarks_widget_thumb_selector_longpressed.9.png
new file mode 100644
index 0000000..34704ef
--- /dev/null
+++ b/res/drawable-hdpi/bookmarks_widget_thumb_selector_longpressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/bookmarks_widget_thumb_selector_focused.9.png b/res/drawable-mdpi/bookmarks_widget_thumb_selector_focused.9.png
new file mode 100644
index 0000000..5bcc7c8
--- /dev/null
+++ b/res/drawable-mdpi/bookmarks_widget_thumb_selector_focused.9.png
Binary files differ
diff --git a/res/drawable/bookmark_thumb_selector.xml b/res/drawable/bookmark_thumb_selector.xml
index 59d9405..d4a12a3 100644
--- a/res/drawable/bookmark_thumb_selector.xml
+++ b/res/drawable/bookmark_thumb_selector.xml
@@ -16,6 +16,7 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
         android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+    <item android:state_focused="true" android:state_pressed="false" android:drawable="@drawable/bookmarks_widget_thumb_selector_focused" />
     <item android:state_pressed="true" android:drawable="@drawable/bookmark_thumb_selector_transition" />
     <item android:drawable="@android:color/transparent" />
 </selector>
diff --git a/res/layout-xlarge/browser_add_bookmark.xml b/res/layout-xlarge/browser_add_bookmark.xml
new file mode 100644
index 0000000..14edecf
--- /dev/null
+++ b/res/layout-xlarge/browser_add_bookmark.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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/add_bookmark_width"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    >
+
+    <include layout="@layout/browser_add_bookmark_content" />
+
+</LinearLayout>
diff --git a/res/layout/bookmarkthumbnailwidget_item.xml b/res/layout/bookmarkthumbnailwidget_item.xml
index b67b386..3247806 100644
--- a/res/layout/bookmarkthumbnailwidget_item.xml
+++ b/res/layout/bookmarkthumbnailwidget_item.xml
@@ -21,7 +21,7 @@
     android:layout_height="wrap_content">
     <ImageView
         android:id="@+id/thumb"
-        android:src="@drawable/browser_thumbnail"
+        android:src="@drawable/thumbnail_bookmarks_widget_no_bookmark_holo"
         android:layout_width="match_parent"
         android:layout_height="@dimen/widgetThumbnailHeight"
         android:scaleType="centerCrop"
diff --git a/res/layout/browser_add_bookmark.xml b/res/layout/browser_add_bookmark.xml
index f5c09a9..3720790 100644
--- a/res/layout/browser_add_bookmark.xml
+++ b/res/layout/browser_add_bookmark.xml
@@ -4,9 +4,9 @@
      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.
@@ -15,209 +15,10 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/add_bookmark_width"
-    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
     >
-    <RelativeLayout android:id="@+id/crumb_holder"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeight"
-        android:gravity="center_vertical"
-        android:visibility="gone"
-        android:paddingLeft="5dip"
-        android:paddingRight="5dip"
-        >
-        <com.android.browser.BreadCrumbView android:id="@+id/crumbs"
-            android:layout_width="wrap_content"
-            android:layout_height="?android:attr/listPreferredItemHeight"
-            android:layout_alignParentLeft="true"
-            android:layout_toLeftOf="@+id/add_divider"
-            android:layout_centerVertical="true"
-            />
-        <TextView
-            android:id="@+id/add_new_folder"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentRight="true"
-            android:layout_alignBaseline="@+id/crumbs"
-            android:drawableLeft="@drawable/ic_add_string"
-            android:gravity="center_vertical"
-            android:text="@string/new_folder"
-            android:visibility="gone"
-            android:layout_centerVertical="true"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-        <ImageView android:id="@+id/add_divider"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_toLeftOf="@+id/add_new_folder"
-            android:src="@drawable/crumb_divider"
-            android:layout_centerVertical="true"
-            />
-    </RelativeLayout>
-    <LinearLayout android:id="@+id/title_holder"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:minHeight="?android:attr/listPreferredItemHeight"
-        android:paddingLeft="5dip"
-        android:paddingRight="5dip"
-        >
-        <TextView android:id="@+id/fake_title"
-            android:layout_width="0dip"
-            android:layout_weight="1"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:gravity="center_vertical"
-            android:drawableLeft="@drawable/ic_bookmark_on_holo_dark"
-            android:text="@string/bookmark_this_page"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-        <ImageView android:id="@+id/remove_divider"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:src="@drawable/crumb_divider"
-            android:layout_centerVertical="true"
-            android:visibility="gone"
-            />
-        <TextView android:id="@+id/remove"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:gravity="center_vertical"
-            android:text="@string/remove"
-            android:drawableLeft="@drawable/trashcan"
-            android:visibility="gone"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-    </LinearLayout>
-    <View android:id="@+id/titleDivider"
-        android:layout_width="match_parent"
-        android:layout_height="1dip"
-        android:gravity="fill_horizontal"
-        android:background="?android:attr/colorForeground"
-        />
-
-    <TableLayout android:id="@+id/default_view"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/folder_selector_height"
-        android:layout_weight="1"
-        android:stretchColumns="1"
-        android:shrinkColumns="1"
-        android:paddingTop="20dip"
-        android:paddingLeft="20dip"
-        android:paddingRight="20dip" >
-      <TableRow>
-        <TextView 
-            android:id="@+id/titleText"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_marginBottom="40dip"
-            android:text="@string/name"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-                
-        <EditText
-            android:id="@+id/title"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_marginRight="20dip"
-            android:layout_marginLeft="20dip"
-            android:gravity="fill_horizontal"
-            android:inputType="textCapSentences"
-            android:ellipsize="end"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-      </TableRow>
-
-      <TableRow
-          android:id="@+id/row_address">
-        <TextView
-            android:id="@+id/addressText"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:text="@string/location"
-            android:gravity="left"
-            android:layout_marginBottom="40dip"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-                
-        <EditText
-            android:id="@+id/address"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_marginRight="20dip"
-            android:layout_marginLeft="20dip"
-            android:hint="@string/http"
-            android:gravity="fill_horizontal"
-            android:inputType="textUri"
-            android:ellipsize="end"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-      </TableRow>
-      <TableRow>
-        <TextView
-            android:id="@+id/add_to"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:text="@string/containing_folder"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-
-        <view class="com.android.browser.addbookmark.FolderSpinner"
-            android:id="@+id/folder"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_marginRight="20dip"
-            android:layout_marginLeft="20dip"
-            android:spinnerMode="dropdown"
-            android:gravity="center_vertical"
-            />
-      </TableRow>
-    </TableLayout>
-
-    <LinearLayout android:id="@+id/folder_selector"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/folder_selector_height"
-        android:orientation="vertical"
-        android:visibility="gone"
-        >
-
-        <view class="com.android.browser.AddBookmarkPage$CustomListView"
-            android:id="@+id/list"
-            android:layout_marginLeft="16dip"
-            android:layout_marginRight="16dip"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            />
-        <TextView
-            android:id="@+id/empty"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-            android:layout_marginLeft="16dip"
-            android:layout_marginTop="16dip"
-            android:text="@string/no_subfolders"
-            android:textStyle="italic"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-    </LinearLayout>
-
-    <LinearLayout 
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="54dip"
-        android:orientation="horizontal"
-        android:paddingTop="4dip"
-        android:paddingLeft="2dip"
-        android:paddingRight="2dip" >
-        <Button android:id="@+id/OK" 
-            android:text="@string/save"
-            android:layout_width="0dip"
-            android:layout_gravity="left"
-            android:layout_weight="1"
-            android:maxLines="2"
-            android:layout_height="wrap_content" />
-        <Button android:id="@+id/cancel" 
-            android:text="@string/do_not_save"
-            android:layout_width="0dip"
-            android:layout_gravity="right"
-            android:layout_weight="1"
-            android:maxLines="2"
-            android:layout_height="wrap_content" />
-    </LinearLayout>
+    <include layout="@layout/browser_add_bookmark_content" />
 
 </LinearLayout>
diff --git a/res/layout/browser_add_bookmark_content.xml b/res/layout/browser_add_bookmark_content.xml
new file mode 100644
index 0000000..8b28011
--- /dev/null
+++ b/res/layout/browser_add_bookmark_content.xml
@@ -0,0 +1,222 @@
+<?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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <RelativeLayout android:id="@+id/crumb_holder"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeight"
+        android:gravity="center_vertical"
+        android:visibility="gone"
+        android:paddingLeft="5dip"
+        android:paddingRight="5dip"
+        >
+        <com.android.browser.BreadCrumbView android:id="@+id/crumbs"
+            android:layout_width="wrap_content"
+            android:layout_height="?android:attr/listPreferredItemHeight"
+            android:layout_alignParentLeft="true"
+            android:layout_toLeftOf="@+id/add_divider"
+            android:layout_centerVertical="true"
+            />
+        <TextView
+            android:id="@+id/add_new_folder"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_alignBaseline="@+id/crumbs"
+            android:drawableLeft="@drawable/ic_add_string"
+            android:gravity="center_vertical"
+            android:text="@string/new_folder"
+            android:visibility="gone"
+            android:layout_centerVertical="true"
+            android:layout_alignTop="@+id/crumbs"
+            android:layout_alignBottom="@+id/crumbs"
+            android:focusable="true"
+            android:background="?android:attr/selectableItemBackground"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+        <ImageView android:id="@+id/add_divider"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toLeftOf="@+id/add_new_folder"
+            android:src="@drawable/crumb_divider"
+            android:layout_centerVertical="true"
+            />
+    </RelativeLayout>
+    <LinearLayout android:id="@+id/title_holder"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:minHeight="?android:attr/listPreferredItemHeight"
+        android:paddingLeft="5dip"
+        android:paddingRight="5dip"
+        >
+        <TextView android:id="@+id/fake_title"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:gravity="center_vertical"
+            android:drawableLeft="@drawable/ic_bookmark_on_holo_dark"
+            android:text="@string/bookmark_this_page"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+        <ImageView android:id="@+id/remove_divider"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:src="@drawable/crumb_divider"
+            android:layout_centerVertical="true"
+            android:visibility="gone"
+            />
+        <TextView android:id="@+id/remove"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:gravity="center_vertical"
+            android:text="@string/remove"
+            android:drawableLeft="@drawable/trashcan"
+            android:visibility="gone"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+    </LinearLayout>
+    <View android:id="@+id/titleDivider"
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:gravity="fill_horizontal"
+        android:background="?android:attr/colorForeground"
+        />
+
+    <TableLayout android:id="@+id/default_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:stretchColumns="1"
+        android:shrinkColumns="1"
+        android:paddingTop="20dip"
+        android:paddingLeft="20dip"
+        android:paddingRight="20dip" >
+      <TableRow>
+        <TextView
+            android:id="@+id/titleText"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginBottom="40dip"
+            android:text="@string/name"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        <EditText
+            android:id="@+id/title"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginRight="20dip"
+            android:layout_marginLeft="20dip"
+            android:gravity="fill_horizontal"
+            android:inputType="textCapSentences"
+            android:ellipsize="end"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+      </TableRow>
+
+      <TableRow
+          android:id="@+id/row_address">
+        <TextView
+            android:id="@+id/addressText"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:text="@string/location"
+            android:gravity="left"
+            android:layout_marginBottom="40dip"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        <EditText
+            android:id="@+id/address"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginRight="20dip"
+            android:layout_marginLeft="20dip"
+            android:hint="@string/http"
+            android:gravity="fill_horizontal"
+            android:inputType="textUri"
+            android:ellipsize="end"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+      </TableRow>
+      <TableRow>
+        <TextView
+            android:id="@+id/add_to"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:text="@string/containing_folder"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        <view class="com.android.browser.addbookmark.FolderSpinner"
+            android:id="@+id/folder"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginRight="20dip"
+            android:layout_marginLeft="20dip"
+            android:spinnerMode="dropdown"
+            android:gravity="center_vertical"
+            />
+      </TableRow>
+    </TableLayout>
+
+    <LinearLayout android:id="@+id/folder_selector"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/folder_selector_height"
+        android:orientation="vertical"
+        android:visibility="gone"
+        >
+
+        <view class="com.android.browser.AddBookmarkPage$CustomListView"
+            android:id="@+id/list"
+            android:layout_marginLeft="16dip"
+            android:layout_marginRight="16dip"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+        <TextView
+            android:id="@+id/empty"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            android:layout_marginLeft="16dip"
+            android:layout_marginTop="16dip"
+            android:text="@string/no_subfolders"
+            android:textStyle="italic"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="54dip"
+        android:orientation="horizontal"
+        android:paddingTop="4dip"
+        android:paddingLeft="2dip"
+        android:paddingRight="2dip" >
+        <Button android:id="@+id/OK"
+            android:text="@string/save"
+            android:layout_width="0dip"
+            android:layout_gravity="left"
+            android:layout_weight="1"
+            android:maxLines="2"
+            android:layout_height="wrap_content" />
+        <Button android:id="@+id/cancel"
+            android:text="@string/do_not_save"
+            android:layout_width="0dip"
+            android:layout_gravity="right"
+            android:layout_weight="1"
+            android:maxLines="2"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+</merge>
diff --git a/res/layout/suggestion_item.xml b/res/layout/suggestion_item.xml
index b85911f..c08ba84 100644
--- a/res/layout/suggestion_item.xml
+++ b/res/layout/suggestion_item.xml
@@ -57,8 +57,7 @@
                 style="@style/SuggestionLineSmall"
                 android:singleLine="true"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:textColor="@color/urlTextColor" />
+                android:layout_height="wrap_content" />
         </LinearLayout>
     </LinearLayout>
     <ImageView
diff --git a/res/layout/title_bar.xml b/res/layout/title_bar.xml
index 9bfba35..99ac04d 100644
--- a/res/layout/title_bar.xml
+++ b/res/layout/title_bar.xml
@@ -18,77 +18,81 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:paddingLeft="8dip"
-    android:paddingRight="12dip"
-    android:paddingTop="2dip"
-    android:paddingBottom="1dip"
-    android:background="@drawable/search_plate_browser" >
-
-    <ProgressBar android:id="@+id/progress_horizontal"
-        style="?android:attr/progressBarStyleHorizontal"
-        android:layout_width="match_parent"
-        android:layout_height="5dip"
-        android:layout_marginLeft="1dip"
-        android:max="100"
-        />
+    android:orientation="vertical" >
 
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
+        android:background="@drawable/bg_urlbar"
+        android:paddingLeft="4dip"
+        android:paddingRight="4dip"
+        android:paddingTop="2dip"
+        android:paddingBottom="1dip"
         >
 
         <LinearLayout android:id="@+id/title_bg"
-            android:background="@drawable/title_text"
             android:layout_width="0dip"
             android:layout_weight="1.0"
-            android:layout_height="wrap_content"
+            android:layout_height="48dip"
             android:layout_marginBottom="4dip"
             android:gravity="center_vertical"
             android:orientation="horizontal"
+            android:background="@drawable/url_background"
             >
                 <ImageView android:id="@+id/favicon"
                     android:layout_width="20dip"
                     android:layout_height="20dip"
-                    android:layout_marginLeft="3dip"
                     />
                 <ImageView android:id="@+id/lock"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_marginLeft="6dip"
+                    android:layout_marginLeft="3dip"
                     android:visibility="gone"
                     />
-                <TextView
-                    android:id="@+id/title"
-                    android:layout_height="wrap_content"
+                <com.android.browser.UrlInputView
+                    android:id="@+id/url_input"
+                    android:focusable="true"
                     android:layout_width="0dip"
                     android:layout_weight="1.0"
+                    android:layout_height="match_parent"
                     android:layout_marginLeft="3dip"
-                    android:gravity="center_vertical"
+                    android:paddingLeft="0dip"
+                    android:paddingRight="0dip"
+                    android:background="@null"
+                    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:imeOptions="actionGo"
+                    style="@style/Suggestions" />
         </LinearLayout>
-        <ImageView android:id="@+id/stop"
-            android:background="@drawable/stop_background"
+        <ImageButton
+            android:id="@+id/stop"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_marginBottom="4dip"
-            android:src="@drawable/ic_btn_stop_v2"
+            style="@style/HoloButton"
+            android:src="@drawable/ic_stop_holo_dark"
             android:visibility="gone"
         />
-        <ImageView
-            android:id="@+id/rt_btn"
+        <ImageButton
+            android:id="@+id/bookmark"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_marginLeft="-2dip"
-            android:layout_marginTop="-6.5dip"
-            android:layout_marginBottom="-2dip"
-            android:layout_marginRight="-5dip"
-            android:scaleType="center"
-            android:background="@drawable/btn_bookmark"
-            android:src="@drawable/ic_bookmark_on_holo_dark"
+            style="@style/HoloButton"
+            android:src="@drawable/btn_imageview_star"
         />
     </LinearLayout>
+
+    <com.android.browser.PageProgressView
+        android:id="@+id/progress_horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="22dip"
+        android:background="@null"
+        android:src="@drawable/progress"
+        android:layout_marginTop="-11dip"
+        android:visibility="gone" />
 </LinearLayout>
diff --git a/res/layout/url_bar.xml b/res/layout/url_bar.xml
index 526e44c..4d8efdd 100644
--- a/res/layout/url_bar.xml
+++ b/res/layout/url_bar.xml
@@ -12,7 +12,6 @@
     -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/content"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
@@ -61,7 +60,7 @@
                 android:id="@+id/voice_icon"
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
-                android:src="@drawable/ic_search_holo_dark"
+                android:src="@drawable/ic_voice_search_holo_dark"
                 style="@style/HoloIcon"
                 android:visibility="gone" />
             <ImageView
@@ -72,7 +71,6 @@
                 android:visibility="gone" />
             <com.android.browser.UrlInputView
                 android:id="@+id/url_focused"
-                android:focusable="true"
                 android:layout_width="0dip"
                 android:layout_weight="1.0"
                 android:layout_height="match_parent"
diff --git a/res/menu-xlarge/browser.xml b/res/menu-xlarge/browser.xml
index 1b52c9a..a791cdd 100644
--- a/res/menu-xlarge/browser.xml
+++ b/res/menu-xlarge/browser.xml
@@ -44,6 +44,8 @@
             android:title="@string/menu_preferences"
             android:icon="@drawable/ic_settings_holo_dark"
             android:alphabeticShortcut="p" />
+        <item android:id="@+id/save_webarchive_menu_id"
+            android:title="@string/menu_save_webarchive" />
         <!-- followings are debug only -->
         <item android:id="@+id/dump_nav_menu_id"
             android:title="@string/dump_nav"
diff --git a/res/menu/browser.xml b/res/menu/browser.xml
index beaa8f3..7a59ffd 100644
--- a/res/menu/browser.xml
+++ b/res/menu/browser.xml
@@ -57,6 +57,8 @@
             android:title="@string/menu_preferences"
             android:icon="@drawable/ic_settings_holo_dark"
             android:alphabeticShortcut="p" />
+        <item android:id="@+id/save_webarchive_menu_id"
+            android:title="@string/menu_save_webarchive" />
         <!-- followings are debug only -->
         <item android:id="@+id/dump_nav_menu_id"
             android:title="@string/dump_nav"
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index fd97a7f..05d137b 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"بطاقة SD غير متوفرة"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"وحدة تخزين USB مشغولة. للسماح بالتنزيلات، حدد \"إيقاف تشغيل وحدة تخزين USB\" في التنبيه."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"بطاقة SD مشغولة. للسماح بالتنزيلات، حدد \"إيقاف تشغيل تخزين USB\" في التنبيه."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"يمكن تنزيل عناوين URL لـ \"http\" أو \"https\" فقط."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"لا يمكن فتح الملف"</string>
     <string name="retry" msgid="1835923075542266721">"إعادة المحاولة"</string>
     <string name="no_downloads" msgid="3947445710685021498">"سجل التنزيل فارغ."</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 993d1d1..8cf987a 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Няма SD карта"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB хранилището е заето. За да разрешите изтегляния, изберете „Изключване на USB хранилището“ в известието."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD картата е заета. За да разрешите изтегляния, изберете „Изключване на USB устройството за съхранение“ в известието."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Могат да се изтеглят само URL адреси от тип „http“ или „https“."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Файлът не може да се отвори"</string>
     <string name="retry" msgid="1835923075542266721">"Повторен опит"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Историята на изтеглянията е празна."</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 3e33f4c..214c16a 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"La targeta SD no està disponible"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"L\'emmagatzematge USB està ocupat. Per permetre les baixades, selecciona \"Desactiva l\'emmagatzematge USB\" a la notificació."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La targeta SD està ocupada. Per permetre les baixades, seleccioneu \"Desactiva l\'emmagatzematge d\'USB\" a la notificació."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Només pot baixar URL \"http\" o \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"No pot obrir el fitxer"</string>
     <string name="retry" msgid="1835923075542266721">"Torna-ho a provar"</string>
     <string name="no_downloads" msgid="3947445710685021498">"L\'historial de baixades és buit."</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index c83ae1f..1395b28 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Karta SD není dostupná"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Úložiště USB je zaneprázdněno. Chcete-li povolit stahování, vyberte v oznámení možnost Vypnout úložiště USB."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Karta SD je zaneprázdněna. Chcete-li povolit stahování, vyberte v oznámení možnost Vypnout úložiště USB."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Lze stahovat pouze adresy URL začínající jako http nebo https."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Soubor nelze otevřít"</string>
     <string name="retry" msgid="1835923075542266721">"Zkusit znovu"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Historie stahování je prázdná."</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 74c1333..efd0298 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-kortet er ikke tilgængeligt"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-lager er optaget. Vælg \"Slå USB-lager fra\" i meddelelsen for at tillade downloads."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-kortet er optaget. Vælg \"Slå USB-lagring fra\" i meddelelsen for at tillade downloads."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Kan kun downloade webadresser, der starter med \"http\" eller https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Kan ikke åbne filen"</string>
     <string name="retry" msgid="1835923075542266721">"Prøv igen"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Downloadoversigten er tom."</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 2c7a909..6263e88 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-Karte nicht verfügbar"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Der USB-Speicher ist ausgelastet. Wählen Sie in der Benachrichtigung \"USB-Speicher deaktivieren\", um Downloads zuzulassen."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Die SD-Karte wird benutzt. Wählen Sie in der Benachrichtigung \"USB-Speicher deaktivieren\" aus, um Downloads zuzulassen."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Nur Download von URLs mit \"http\" oder \"https\" möglich"</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Datei kann nicht geöffnet werden."</string>
     <string name="retry" msgid="1835923075542266721">"Wiederholen"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Downloadverlauf ist leer."</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index b43d812..1b43b8f 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Η κάρτα SD δεν είναι διαθέσιμη"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Ο χώρος αποθήκευσης USB είναι απασχολημένος. Για να επιτρέψετε τις λήψεις, επιλέξτε \"Απενεργοποίηση χώρου αποθήκευσης USB\" στην ειδοποίηση."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Η κάρτα SD είναι απασχολημένη. Για να επιτρέψετε τις λήψεις, επιλέξτε \"Απενεργοποίηση χώρου αποθήκευσης USB\" στην ειδοποίηση."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Είναι δυνατή η λήψη μόνο διευθύνσεων URL \"http\" ή \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Δεν είναι δυνατό το άνοιγμα του αρχείου"</string>
     <string name="retry" msgid="1835923075542266721">"Επανάληψη"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Το ιστορικό λήψεων του προγράμματος περιήγησης είναι κενό."</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index e2bb5fe..4d0598c 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD card unavailable"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"The USB storage is busy. To allow downloads, select \"Turn off USB storage\" in the notification."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"The SD card is busy. To allow downloads, select \"Turn off USB storage\" in the notification."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Can only download \"http\" or \"https\" URLs."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Cannot open file"</string>
     <string name="retry" msgid="1835923075542266721">"Retry"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Download history is empty."</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 829e5c4..d5cf65a 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Tarjeta SD no disponible"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"El almacenamiento USB está ocupado. Para permitir descargas, selecciona \"Desactivar almacenamiento USB\" en la notificación."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La tarjeta SD está llena. Para permitir descargas, selecciona \"Desactivar almacenamiento USB\" en la notificación."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Solo se pueden descargar URL de \"http\" o de \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"No se puede abrir el archivo."</string>
     <string name="retry" msgid="1835923075542266721">"Intentar nuevamente"</string>
     <string name="no_downloads" msgid="3947445710685021498">"El historial de descarga está vacío."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 49c56f7..7728d29 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Tarjeta SD no disponible"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"El almacenamiento USB está ocupado. Para permitir descargas, selecciona \"Desactivar almacenamiento USB\" en la notificación."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La tarjeta SD está ocupada. Para permitir descargas, selecciona \"Desactivar almacenamiento USB\" en la notificación."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Solo se pueden descargar URL del tipo \"http\" o \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"No se puede abrir el archivo"</string>
     <string name="retry" msgid="1835923075542266721">"Reintentar"</string>
     <string name="no_downloads" msgid="3947445710685021498">"El historial de descargas está vacío."</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 71f9aae..61cfc01 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"کارت SD موجود نیست"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"حافظه USB مشغول است. برای اجازه به دانلودها، \"خاموش کردن حافظه USB\" را در اعلان انتخاب کنید."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"کارت SD مشغول است. برای اجازه به دانلودها، \"خاموش کردن ذخیره USB\" را در اعلان انتخاب کنید."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"فقط می تواند URL های \"http\" یا \"https\" را دانلود کند."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"فایل باز نمی شود"</string>
     <string name="retry" msgid="1835923075542266721">"امتحان مجدد"</string>
     <string name="no_downloads" msgid="3947445710685021498">"سابقه دانلود خالی است."</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 6bfe70e..096f37e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-kortti ei käytettävissä"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-tallennustila on varattu. Voit sallia lataukset valitsemalla ilmoituksessa Poista USB-tallennustila käytöstä."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-kortti on varattu. Voit sallia lataukset valitsemalla ilmoituksessa \"Poista USB-tallennustila käytöstä\"."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Vain http- tai https-URL-osoitteista voi ladata"</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Tiedostoa ei voi avata"</string>
     <string name="retry" msgid="1835923075542266721">"Yritä uudelleen"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Lataushistoria on tyhjä."</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index dbdb331..263989a 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Carte SD non disponible"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"La mémoire de stockage USB est occupée. Pour autoriser les téléchargements, sélectionnez l\'option \"Désactiver le stockage USB\" dans la notification."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La carte SD est occupée. Pour autoriser les téléchargements, sélectionnez l\'option \"Désactiver le stockage USB\" dans la notification."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Vous ne pouvez télécharger que des URL de type \"http\" et \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Fichier impossible à ouvrir"</string>
     <string name="retry" msgid="1835923075542266721">"Réessayer"</string>
     <string name="no_downloads" msgid="3947445710685021498">"L\'historique de téléchargement est vide."</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 21d76bb..32f8df8 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD kartica nije dostupna"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Memorija USB je zauzeta. Kako biste omogućili preuzimanja, odaberite \"Isključi USB pohranjivanje\" u obavijesti."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD kartica je zauzeta. Kako biste omogućili preuzimanja, odaberite \"Isključi USB pohranjivanje\" u obavijesti."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Mogu se preuzeti samo \"http\" ili \"https\" URL-ovi."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Datoteka se ne može otvoriti"</string>
     <string name="retry" msgid="1835923075542266721">"Pokušaj ponovo"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Povijest preuzimanja je prazna."</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 496bd20..7bf7226 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Az SD-kártya nem érhető el"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Az USB-tár dolgozik. A letöltések engedélyezéséhez válassza az \"USB-tár kikapcsolása\" lehetőséget."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Az SD-kártya dolgozik. A letöltések engedélyezéséhez válassza az \"USB-tár kikapcsolása\" lehetőséget."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Csak \"http\" vagy \"https\" URL-ek letöltése lehetséges."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"A fájlt nem lehet megnyitni"</string>
     <string name="retry" msgid="1835923075542266721">"Újra"</string>
     <string name="no_downloads" msgid="3947445710685021498">"A letöltési előzmények listája üres."</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 206edba..a20e768 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Kartu SD tidak tersedia"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Penyimpanan USB sibuk. Untuk mengizinkan pengunduhan, pilih \"Matikan penyimpanan USB\" pada pemberitahuan."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Kartu SD sibuk. Untuk mengizinkan unduhan, pilih \"Matikan penyimpanan USB\" pada pemberitahuan."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Hanya dapat mengunduh URL \"http\" atau \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Tidak dapat membuka berkas"</string>
     <string name="retry" msgid="1835923075542266721">"Coba Lagi"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Riwayat unduhan kosong."</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 6bf732a..a0367ee 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Scheda SD non disponibile"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"L\'archivio USB è già in uso. Per consentire i download, seleziona \"Disattiva archivio USB\" nella notifica."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La scheda SD è piena. Per consentire i download, seleziona \"Disattiva archivio USB\" nella notifica."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"È possibile scaricare soltanto URL \"http\" o \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Impossibile aprire il file"</string>
     <string name="retry" msgid="1835923075542266721">"Riprova"</string>
     <string name="no_downloads" msgid="3947445710685021498">"La cronologia download è vuota."</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 47a3e20..31d68c7 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"כרטיס SD לא זמין"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"אמצעי אחסון מסוג USB אינו פנוי. כדי לאפשר הורדות, בחר \"כבה אמצעי אחסון מסוג USB\" בהתראה."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"כרטיס ה-SD אינו פנוי. כדי לאפשר הורדות, בחר \"כבה אחסון USB\" בהתראה."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"ניתן להוריד רק כתובות אתרים המתחילות ב-\"http\" או \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"אין אפשרות לפתוח את הקובץ"</string>
     <string name="retry" msgid="1835923075542266721">"נסה שוב"</string>
     <string name="no_downloads" msgid="3947445710685021498">"היסטוריית ההורדות ריקה."</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index d45b0ef..6eaa178 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SDカードは利用できません"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USBストレージは使用中です。[USBストレージをOFFにする]を選択してからダウンロードしてください。"</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SDカードは使用中です。[USBストレージをOFFにする]を選択してからダウンロードしてください。"</string>
+    <string name="cannot_download" msgid="8150552478556798780">"ダウンロードできるのは「http」または「https」のURLのみです。"</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"ファイルを開けません"</string>
     <string name="retry" msgid="1835923075542266721">"やり直す"</string>
     <string name="no_downloads" msgid="3947445710685021498">"ダウンロード履歴はありません。"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 5aad619..79f2f6c 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD 카드를 사용할 수 없음"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB 저장소가 사용 중입니다. 다운로드를 허용하려면 알림에서 \'USB 저장소 사용 안함\'을 선택하세요."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD 카드가 사용 중입니다. 다운로드를 허용하려면 알림에서 \'USB 저장소 끄기\'를 선택하세요."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"\'http\' 또는 \'https\' URL만 다운로드할 수 있습니다."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"파일을 열 수 없음"</string>
     <string name="retry" msgid="1835923075542266721">"다시 시도"</string>
     <string name="no_downloads" msgid="3947445710685021498">"다운로드 기록이 비어 있습니다."</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 30bac67..a8e1980 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD kortelė negalima"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB atmintinė užimta. Jei norite leisti atsisiuntimus, pranešime pasirinkite „Išjungti USB atmintinę“."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD kortelė užimta. Jei norite leisti atsisiuntimus, pranešime pasirinkite „Išjungti USB saugyklą“."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Galima atsisiųsti tik „http“ ar „https“ prasidedančius URL."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Nepavyksta atidaryti failo"</string>
     <string name="retry" msgid="1835923075542266721">"Bandyti dar kartą"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Atsisiuntimo istorija tuščia."</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 277d82b..574bb8d 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD karte nav pieejama"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB atmiņa ir aizņemta. Lai atļautu lejupielādes, paziņojumā atlasiet Izslēgt USB atmiņu."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD karte ir aizņemta. Lai atļautu lejupielādes, paziņojumā atlasiet “Izslēgt USB krātuvi”."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Var lejupielādēt tikai tādus vietrāžus URL, kuri sākas ar “http” vai “https”."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Nevar atvērt failu"</string>
     <string name="retry" msgid="1835923075542266721">"Mēģināt vēlreiz"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Lejupielāžu vēsture ir tukša."</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 0584755..d542342 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Minnekort utilgjengelig"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-lagring er opptatt. Velg alternativet for å slå av USB-lagring i varselet for å tillate nedlastinger."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-kortet er opptatt. Velg alternativet for å slå av USB-lagring i varselet for å tillate nedlastinger."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Kan kun laste ned «http»- eller «https»-nettadresser."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Får ikke åpnet filen"</string>
     <string name="retry" msgid="1835923075542266721">"Prøv igjen"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Nedlastingsloggen er tom."</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 6152107..dc28a8d 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-kaart niet beschikbaar"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"De USB-opslag wordt gebruikt. Als u downloads wilt toestaan, selecteert u \'USB-opslag uitschakelen\' in de melding."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"De SD-kaart wordt gebruikt. Als u downloads wilt toestaan, selecteert u \'USB-opslag uitschakelen\' in de melding."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Kan alleen URL\'s met \'http\' of \'https\' downloaden."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Kan bestand niet openen"</string>
     <string name="retry" msgid="1835923075542266721">"Opnieuw proberen"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Downloadgeschiedenis is leeg."</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 3f8c0fd..ad998cd 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Karta SD jest niedostępna"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Nośnik USB jest zajęty. Aby umożliwić pobieranie, wybierz w powiadomieniu opcję „Wyłącz nośnik USB”."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Karta SD jest zajęta. Aby umożliwić pobieranie, wybierz w powiadomieniu opcję „Wyłącz nośnik USB”."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Pobieranie jest możliwe tylko z adresów „http” lub „https”."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Nie można otworzyć pliku"</string>
     <string name="retry" msgid="1835923075542266721">"Spróbuj ponownie"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Historia pobierania jest pusta."</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 5846b16..77b465f 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Cartão SD não disponível"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"O armazenamento USB está ocupado. Para permitir as transferências, seleccione \"Desactivar armazenamento USB\" na notificação."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"O cartão SD está ocupado. Para permitir as transferências, seleccione \"Desactivar armazenamento USB\" na notificação."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Apenas é possível transferir URLs \"http\" ou \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Não é possível abrir o ficheiro"</string>
     <string name="retry" msgid="1835923075542266721">"Tentar novamente"</string>
     <string name="no_downloads" msgid="3947445710685021498">"O histórico de transferências está vazio."</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 86ddbff..c6751b4 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Cartão SD não disponível"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"O armazenamento USB está ocupado. Para permitir downloads, selecione \"Desativar o armazenamento USB\" na notificação."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"O cartão SD está ocupado. Para permitir downloads, selecione \"Desativar armazenamento USB\" na notificação."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Somente é possível fazer download de URLs \"http\" ou \"https\""</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Não é possível abrir o arquivo."</string>
     <string name="retry" msgid="1835923075542266721">"Tentar novamente"</string>
     <string name="no_downloads" msgid="3947445710685021498">"O histórico de downloads está vazio."</string>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
index 777e778..1a4fe00 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -389,6 +389,8 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Carta SD betg disponibla"</string>
     <!-- outdated translation 3473883538192835204 -->     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"La carta SD è occupada. Tscherni «Deactivar la memoria USB» en l\'avis per permetter telechargiadas."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La carta SD è occupada. Tscherni «Deactivar la memoria USB» en l\'avis per permetter telechargiadas."</string>
+    <!-- no translation found for cannot_download (8150552478556798780) -->
+    <skip />
     <string name="download_no_application_title" msgid="1286056729168874295">"Impussibel dad avrir la datoteca"</string>
     <string name="retry" msgid="1835923075542266721">"Repeter"</string>
     <string name="no_downloads" msgid="3947445710685021498">"La cronologia da telechargiadas è vida."</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 1d22788..29f8bb6 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Card SD nedisponibil"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Dispozitivul de stocare USB este ocupat. Pentru a permite descărcări, selectaţi în notificare opţiunea „Dezactivaţi stocarea USB”."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Cardul SD este ocupat. Pentru a permite descărcări, selectaţi în notificare opţiunea „Dezactivaţi stocarea pe USB”."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Puteţi descărca numai de la adrese URL care încep cu „http” sau „https”."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Fişierul nu poate fi deschis"</string>
     <string name="retry" msgid="1835923075542266721">"Încercaţi din nou"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Istoricul de descărcări este gol."</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 9cf16ca..089c3e8 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-карта недоступна"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-накопитель занят. Чтобы разрешить загрузки, выберите \"Выключить USB-накопитель\" в уведомлении."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-карта занята. Чтобы разрешить загрузки, выберите \"Отключить USB-накопитель\" в уведомлении."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Загрузка возможна только с URL, начинающихся с \"http\" или \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Невозможно открыть файл"</string>
     <string name="retry" msgid="1835923075542266721">"Повторить попытку"</string>
     <string name="no_downloads" msgid="3947445710685021498">"История загрузок пуста."</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index edd1253..836d3f2 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Karta SD nie je dostupná"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Ukladací priestor USB je zaneprázdnený. Ak chcete povoliť preberanie, vyberte v upozornení možnosť „Vypnúť ukladací priestor USB“."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Karta SD je zaneprázdnená. Ak chcete povoliť preberanie, vyberte v upozornení možnosť Vypnúť pamäť USB."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Môžete prevziať len adresy URL začínajúce na „http“ alebo „https“."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Súbor nemožno otvoriť"</string>
     <string name="retry" msgid="1835923075542266721">"Skúsiť znova"</string>
     <string name="no_downloads" msgid="3947445710685021498">"História preberania je prázdna."</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 212d883..41b3ac4 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Kartica SD ni na voljo"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Pomnilnik USB je zaseden. Če želite omogočiti prenose, izberite »Izklopi pomnilnik USB« v območju za obvestila."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Kartica SD je zasedena. Če želite omogočiti prenose, izberite »Izklopi pomnilnik USB« v območju za obvestila."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Prenesti je možno le URL-je »http« ali »https«."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Ne morem odpreti datoteke"</string>
     <string name="retry" msgid="1835923075542266721">"Poskusi znova"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Zgodovina prenosov je prazna."</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index cd76430..90a4f02 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD картица је недоступна"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB меморија је заузета. Да бисте дозволили преузимања, изаберите „Искључи USB меморију“ у обавештењу."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD картица је заузета. Да бисте омогућили преузимања, у оквиру обавештења изаберите „Искључи USB складиште“."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Могуће је преузети само URL адресе које садрже „http“ или „https“."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Није могуће отворити датотеку"</string>
     <string name="retry" msgid="1835923075542266721">"Покушај поново"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Историја преузимања је празна."</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 6e23ccb..07b0d10 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-kortet är inte tillgängligt"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-lagringsenheten är upptagen. Om du vill tillåta hämtning väljer du Inaktivera USB-lagring i aviseringen."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-kortet är upptaget. Om du vill tillåta hämtning väljer du Inaktivera USB-lagring i meddelandet."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Det går bara att hämta webbadresser som inleds med http eller https."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Det går inte att öppna filen"</string>
     <string name="retry" msgid="1835923075542266721">"Försök igen"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Det finns ingen hämtningshistorik."</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index cd74d53..0c9f3ff 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"การ์ด SD ใช้งานไม่ได้"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"ที่เก็บข้อมูล USB ไม่ว่าง หากต้องการอนุญาตให้ดาวน์โหลด ให้เลือก \"ปิดที่เก็บข้อมูล USB\" ในการแจ้งเตือน"</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"การ์ด SD ไม่ว่าง หากต้องการอนุญาตให้ดาวน์โหลด เลือก \"ปิดพื้นที่จัดเก็บ USB\" ในการแจ้งเตือน"</string>
+    <string name="cannot_download" msgid="8150552478556798780">"ดาวน์โหลดได้เฉพาะ URL \"http\" หรือ \"https\""</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"ไม่สามารถเปิดไฟล์ได้"</string>
     <string name="retry" msgid="1835923075542266721">"ลองอีกครั้ง"</string>
     <string name="no_downloads" msgid="3947445710685021498">"ประวัติการดาวน์โหลดว่างเปล่า"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 04ff6c2..4d514d0 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Hindi available ang SD card."</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Abala ang SD card. Upang payagan ang mga pag-download, piliin ang \"I-off ang imbakan na USB\" sa notification."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Abala ang SD card. Upang payagan ang mga pag-download, piliin ang \"I-off ang USB storage\" sa notification."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Maaari lang mag-download ng mga URL ng \"http\" o \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Hindi mabuksan ang file"</string>
     <string name="retry" msgid="1835923075542266721">"Subukang muli"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Walang laman ang kasaysayan ng pag-download."</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 9ca233a..caa666f 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD kart kullanılamıyor"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB depolama birimi meşgul. İndirme işlemlerine izin vermek için bildirim alanında \"USB depolama birimini kapat\"ı seçin."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD kart meşgul. İndirme işlemlerine izin vermek için bildirim alanında \"USB\'de depolamayı kapat\"ı seçin."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Yalnızca \"http\" veya \"https\" URL\'leri indirilebilir."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Dosya açılamıyor"</string>
     <string name="retry" msgid="1835923075542266721">"Tekrar Dene"</string>
     <string name="no_downloads" msgid="3947445710685021498">"İndirme geçmişi boş."</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index b774d03..0ecd5a0 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Карта SD недоступна"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Носій USB зайнятий. Щоб дозволити завантаження, виберіть у сповіщенні \"Вимкнути носій USB\"."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Карта SD зайнята. Щоб дозвол. завантаж., виберіть у сповіщенні \"Вимкнути зберігання на USB\"."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Можна завантажувати лише URL-адреси \"http\" або \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Файл не відкривається"</string>
     <string name="retry" msgid="1835923075542266721">"Повтор."</string>
     <string name="no_downloads" msgid="3947445710685021498">"Історія завантажень порожня."</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index b24c8b4..ea8cbc5 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -317,6 +317,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Thẻ SD không khả dụng"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Thẻ SD đang bận. Để cho phép tải xuống, hãy chọn \"Tắt bộ bộ nhớ USB\" trong thông báo."</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Thẻ SD đang bận. Để cho phép tải xuống, hãy chọn \"Tắt bộ lưu trữ USB\" trong thông báo."</string>
+    <string name="cannot_download" msgid="8150552478556798780">"Chỉ có thể tải xuống URL \"http\" hoặc \"https\"."</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"Không thể mở tệp"</string>
     <string name="retry" msgid="1835923075542266721">"Thử lại"</string>
     <string name="no_downloads" msgid="3947445710685021498">"Lịch sử tải xuống trống."</string>
diff --git a/res/values-xlarge/dimensions.xml b/res/values-xlarge/dimensions.xml
index 27c1ce2..613c133 100644
--- a/res/values-xlarge/dimensions.xml
+++ b/res/values-xlarge/dimensions.xml
@@ -17,6 +17,7 @@
     <dimen name="widgetHorizontalSpacing">14dip</dimen>
     <dimen name="widgetVerticalSpacing">12dip</dimen>
     <dimen name="favicon_padded_size">28dip</dimen>
+    <dimen name="add_bookmark_width">500dip</dimen>
     <!-- For the most visited page -->
     <dimen name="mv_max_width">1010dp</dimen>
     <dimen name="mv_item_width">231dp</dimen>
diff --git a/res/values-xlarge/integers.xml b/res/values-xlarge/integers.xml
new file mode 100644
index 0000000..abdafbf
--- /dev/null
+++ b/res/values-xlarge/integers.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- The number of lines in the suggestions dropdown in landscape -->
+    <integer name="max_suggest_lines_landscape">5</integer>
+    <!-- The number of lines in the suggestions dropdown in portrait -->
+    <integer name="max_suggest_lines_portrait">12</integer>
+</resources>
diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml
deleted file mode 100644
index 473b170..0000000
--- a/res/values-xlarge/styles.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- *
- * Copyright 2006,2007,2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-
-<resources>
-    <style name="BrowserTheme" parent="@android:Theme.Holo">
-        <item name="android:windowBackground">@color/white</item>
-        <item name="android:colorBackground">#FFFFFFFF</item>
-        <item name="android:windowActionBar">true</item>
-        <item name="android:windowNoTitle">false</item>
-        <item name="android:actionBarStyle">@style/ActionBarStyle</item>
-        <item name="android:actionButtonStyle">@style/ActionButton</item>
-    </style>
-    <style name="Dialog" parent="@android:style/Theme.Holo.Dialog" >
-        <item name="android:windowActionBar">false</item>
-    </style>
-    <style name="BookmarkTheme" parent="@android:Theme.Holo">
-        <item name="android:windowActionBar">false</item>
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:colorBackgroundCacheHint">@null</item>
-        <item name="android:windowFrame">@null</item>
-        <item name="android:windowContentOverlay">@null</item>
-        <item name="android:windowIsFloating">true</item>
-        <item name="android:backgroundDimEnabled">false</item>
-        <item name="android:windowIsTranslucent">true</item>
-    </style>
-    <style name="ShortCutTheme" parent="@android:Theme.Holo">
-    </style>
-    <style name="ActionBarStyle">
-        <item name="android:height">56dip</item>
-        <item name="android:background">@drawable/bg_browsertabs</item>
-        <item name="android:displayOptions"></item>
-    </style>
-    <style name="ActionButton">
-        <item name="android:background">?android:attr/selectableItemBackground</item>
-    </style>
-    <style name="Suggestions" parent="@android:style/Widget.Holo.Light.AutoCompleteTextView">
-    </style>
-    <style name="SuggestionLineMedium"
-            parent="@android:style/TextAppearance.Holo.Medium.Inverse">
-        <item name="android:textSize">16sp</item>
-    </style>
-    <style name="SuggestionLineSmall"
-            parent="@android:style/TextAppearance.Holo.Small.Inverse">
-        <item name="android:textSize">12sp</item>
-    </style>
-</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 16bee5c..7da9430 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD 卡不可用"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB 存储设备正忙。要允许下载,请在通知中选择“关闭 USB 存储设备”。"</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD 卡正忙。要允许下载,请在通知中选择“关闭 USB 存储设备”。"</string>
+    <string name="cannot_download" msgid="8150552478556798780">"只能从“http”或“https”网址下载。"</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"无法打开文件"</string>
     <string name="retry" msgid="1835923075542266721">"重试"</string>
     <string name="no_downloads" msgid="3947445710685021498">"下载历史记录为空。"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index e5df8ec..5a40b1b 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -316,6 +316,7 @@
     <string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"無法使用 SD 卡"</string>
     <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB 儲存裝置忙碌中。如要允許下載,請選取通知中的 [關閉 USB 儲存裝置]。"</string>
     <string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD 記憶卡忙碌中。如要允許下載,請選取通知中的 [停用 USB 儲存裝置]。"</string>
+    <string name="cannot_download" msgid="8150552478556798780">"僅可下載「http」或「https」網址的檔案。"</string>
     <string name="download_no_application_title" msgid="1286056729168874295">"無法開啟檔案"</string>
     <string name="retry" msgid="1835923075542266721">"重試"</string>
     <string name="no_downloads" msgid="3947445710685021498">"下載記錄是空的。"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 68b58f7..6fa0840 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -33,6 +33,6 @@
     <color name="qc_slice_active">#E02090FF</color>
     <color name="bookmarkWidgetFaviconBackground">#23ffffff</color>
     <color name="bookmarkListFaviconBackground">#23ffffff</color>
-    <color name="urlTextColor">#0E774A</color>
     <color name="tabFaviconBackground">#FF555555</color>
+    <color name="tabFocusHighlight">#FF99CC00</color>
 </resources>
diff --git a/res/values/dimensions.xml b/res/values/dimensions.xml
index 300714b..3badd17 100644
--- a/res/values/dimensions.xml
+++ b/res/values/dimensions.xml
@@ -20,11 +20,10 @@
     <dimen name="tab_overlap">8dp</dimen>
     <dimen name="tab_addoverlap">14dp</dimen>
     <dimen name="tab_slice">15.5dp</dimen>
-    <dimen name="tab_padding">16dp</dimen>
+    <dimen name="tab_focus_stroke">2dip</dimen>
     <dimen name="max_tab_width">300dp</dimen>
     <dimen name="bookmarkThumbnailWidth">90dip</dimen>
     <dimen name="bookmarkThumbnailHeight">80dip</dimen>
-    <dimen name="add_bookmark_width">500dip</dimen>
     <dimen name="folder_selector_height">230dip</dimen>
     <dimen name="widgetItemMinHeight">48dip</dimen>
     <dimen name="favicon_size">16dip</dimen>
@@ -45,4 +44,11 @@
     <dimen name="list_favicon_corner_radius">3dip</dimen>
     <dimen name="tab_favicon_corner_radius">2dip</dimen>
     <dimen name="dropdown_offset">8dip</dimen>
+    <dimen name="widgetThumbnailHeight">104dip</dimen>
+    <dimen name="widgetHorizontalSpacing">14dip</dimen>
+    <dimen name="widgetVerticalSpacing">12dip</dimen>
+    <!-- For the combined Bookmarks History view -->
+    <dimen name="combo_paddingTop">10dip</dimen>
+    <dimen name="combo_paddingLeftRight">16dip</dimen>
+    <dimen name="combo_horizontalSpacing">8dip</dimen>
 </resources>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index a0b7ae3..a899a14 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -15,7 +15,7 @@
     <!-- The number of lines in the suggestions dropdown in landscape -->
     <integer name="max_suggest_lines_landscape">5</integer>
     <!-- The number of lines in the suggestions dropdown in portrait -->
-    <integer name="max_suggest_lines_portrait">12</integer>
+    <integer name="max_suggest_lines_portrait">4</integer>
     <!--  The maximum number of open tabs -->
     <integer name="max_tabs">16</integer>
     <!--  The duration of the tab animations in millisecs  -->
@@ -23,4 +23,6 @@
     <integer name="max_width_crumb">200</integer>
     <!-- The maximum number of most visited URLs in the history tab -->
     <integer name="most_visits_limit">10</integer>
+    <!-- Animation durations -->
+    <integer name="comboViewFadeInDuration">400</integer>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 75762a0..8005e14 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -18,6 +18,9 @@
     <string name="application_name">Browser</string>
     <!-- Displayed with a file picker to choose a file to upload -->
     <string name="choose_upload">Choose file for upload</string>
+    <!-- Toast to show the user after they try to open the file picker but no apps on the
+         system can provide a file [CHAR-LIMIT=NONE]-->
+    <string name="uploads_disabled">File uploads are disabled.</string>
     <!-- Name of menu item of a new tab.  Also used in the title bar when displaying a new tab -->
     <string name="new_tab">New window</string>
     <!-- Name of menu item of a new incognito tab.  Also used in the
@@ -573,6 +576,8 @@
     <!-- Do not tranlsate.  Development option -->
     <string name="pref_development_hardware_accel" translatable="false">Enable OpenGL Rendering</string>
     <!-- Do not tranlsate.  Development option -->
+    <string name="pref_development_visual_indicator" translatable="false">Enable Visual Indicator</string>
+    <!-- Do not tranlsate.  Development option -->
     <string name="js_engine_flags" translatable="false">Set JS flags</string>
     <!-- Do not tranlsate.  Development option -->
     <string name="pref_development_uastring" translatable="false">UAString</string>
@@ -749,6 +754,8 @@
             the user how to enable SD card storage -->
     <string name="download_sdcard_busy_dlg_msg" product="default">The SD card is busy. To allow downloads, select \"Turn off USB storage\" in the notification.</string>
 
+    <!-- Toast for a download which cannot begin because the URL is not http or https -->
+    <string name="cannot_download">Can only download \"http\" or \"https\" URLs.</string>
     <!-- Title for a dialog informing the user that there is no application on
             the phone that can open the file that was downloaded -->
     <string name="download_no_application_title">Cannot open file</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a4a27cc..8d222f4 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -18,20 +18,48 @@
  */
 -->
 <resources>
-    <style name="BrowserTheme" parent="@android:Theme.Black">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowContentOverlay">@null</item>
+    <style name="BrowserTheme" parent="@android:Theme.Holo">
+        <item name="android:windowBackground">@color/white</item>
+        <item name="android:colorBackground">#FFFFFFFF</item>
+        <item name="android:windowActionBar">true</item>
+        <item name="android:windowNoTitle">false</item>
+        <item name="android:actionBarStyle">@style/ActionBarStyle</item>
+        <item name="android:actionButtonStyle">@style/ActionButton</item>
     </style>
-    <style name="Dialog" parent="@android:style/Theme.Dialog">
-        <item name="android:background">@color/black</item>
+    <style name="DialogWhenLarge" parent="@android:style/Theme.Holo.DialogWhenLarge" >
+        <item name="android:windowActionBar">false</item>
+    </style>
+    <style name="BookmarkTheme" parent="@android:Theme.Holo">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:windowIsTranslucent">true</item>
+    </style>
+    <style name="ActionBarStyle">
+        <item name="android:height">56dip</item>
+        <item name="android:background">@drawable/bg_browsertabs</item>
+        <item name="android:displayOptions"></item>
+    </style>
+    <style name="ActionButton">
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+    <style name="Suggestions" parent="@android:style/Widget.Holo.Light.AutoCompleteTextView">
+    </style>
+    <style name="SuggestionLineMedium"
+            parent="@android:style/TextAppearance.Holo.Medium.Inverse">
+        <item name="android:textSize">16sp</item>
+    </style>
+    <style name="SuggestionLineSmall"
+            parent="@android:style/TextAppearance.Holo.Small.Inverse">
+        <item name="android:textSize">12sp</item>
     </style>
     <style name="ActionBar" parent="@android:style/Widget.ActionBar">
         <item name="android:background">@color/black</item>
     </style>
-    <style name="BookmarkTheme" parent="@android:Theme.Black">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowContentOverlay">@null</item>
-    </style>
     <style name="ShortcutTheme" parent="@android:Theme.Holo.DialogWhenLarge">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowContentOverlay">@null</item>
@@ -56,10 +84,4 @@
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:textStyle">normal</item>
     </style>
-    <style name="Suggestions" parent="@android:style/Widget.AutoCompleteTextView">
-    </style>
-    <style name="SuggestionLineMedium" parent="@android:style/TextAppearance.Medium">
-    </style>
-    <style name="SuggestionLineSmall" parent="@android:style/TextAppearance.Small">
-    </style>
 </resources>
diff --git a/res/xml/hidden_debug_preferences.xml b/res/xml/hidden_debug_preferences.xml
index 6d66eaa..661d9de 100644
--- a/res/xml/hidden_debug_preferences.xml
+++ b/res/xml/hidden_debug_preferences.xml
@@ -20,6 +20,11 @@
     <!-- The javascript console is enabled by default when the user has
          also enabled debug mode by navigating to about:debug. -->
     <CheckBoxPreference
+        android:key="enable_visual_indicator"
+        android:defaultValue="false"
+        android:title="@string/pref_development_visual_indicator" />
+
+    <CheckBoxPreference
         android:key="javascript_console"
         android:defaultValue="true"
         android:title="@string/pref_development_error_console" />
diff --git a/src/com/android/browser/AccountsChangedReceiver.java b/src/com/android/browser/AccountsChangedReceiver.java
index 92d6ad0..a34180a 100644
--- a/src/com/android/browser/AccountsChangedReceiver.java
+++ b/src/com/android/browser/AccountsChangedReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.browser;
 
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
+
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.BroadcastReceiver;
@@ -55,6 +57,7 @@
             ContentResolver.setSyncAutomatically(a, BrowserContract.AUTHORITY, false);
             ContentResolver.setIsSyncable(a, BrowserContract.AUTHORITY, 0);
         }
+        BookmarkThumbnailWidgetProvider.refreshWidgets(context, true);
     }
 
 }
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index 1444862..9a7227c 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -73,6 +73,7 @@
     // Place on an edited bookmark to remove the saved thumbnail
     public static final String REMOVE_THUMBNAIL = "remove_thumbnail";
     public static final String USER_AGENT = "user_agent";
+    public static final String CHECK_FOR_DUPE = "check_for_dupe";
 
     /* package */ static final String EXTRA_EDIT_BOOKMARK = "bookmark";
     /* package */ static final String EXTRA_IS_FOLDER = "is_folder";
@@ -86,6 +87,8 @@
     private final int LOADER_ID_ALL_FOLDERS = 1;
     private final int LOADER_ID_FIND_ROOT = 2;
     private final int LOADER_ID_CHECK_FOR_DUPE = 3;
+    private final int LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK = 4;
+    private final int LOADER_ID_FIND_FOLDER_BY_ID = 5;
 
     private EditText    mTitle;
     private EditText    mAddress;
@@ -117,6 +120,7 @@
     private Drawable mHeaderIcon;
     private View mRemoveLink;
     private View mFakeTitleHolder;
+    private FolderSpinnerAdapter mFolderAdapter;
     private static class Folder {
         String Name;
         long Id;
@@ -308,6 +312,15 @@
             case FolderSpinnerAdapter.OTHER_FOLDER:
                 switchToFolderSelector();
                 break;
+            case FolderSpinnerAdapter.RECENT_FOLDER:
+                mCurrentFolder = mFolderAdapter.recentFolderId();
+                mSaveToHomeScreen = false;
+                // In case the user decides to select OTHER_FOLDER
+                // and choose a different one, so that we will start from
+                // the correct place.
+                LoaderManager manager = getLoaderManager();
+                manager.initLoader(LOADER_ID_ALL_FOLDERS, null, this);
+                manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this);
             default:
                 break;
         }
@@ -412,6 +425,18 @@
                         selection,
                         selArgs,
                         null);
+            case LOADER_ID_FIND_FOLDER_BY_ID:
+                projection = new String[] {
+                        BrowserContract.Bookmarks._ID,
+                        BrowserContract.Bookmarks.TITLE
+                };
+                return new CursorLoader(this,
+                        BrowserContract.Bookmarks.CONTENT_URI,
+                        projection,
+                        BrowserContract.Bookmarks._ID + " = "
+                                + args.getLong(BrowserContract.Bookmarks._ID),
+                        null,
+                        null);
             case LOADER_ID_ALL_FOLDERS:
                 projection = new String[] {
                         BrowserContract.Bookmarks._ID,
@@ -442,6 +467,16 @@
                         where,
                         null,
                         BrowserContract.Bookmarks._ID + " ASC");
+            case LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK:
+                projection = new String[] {
+                        BrowserContract.Bookmarks.PARENT
+                };
+                return new CursorLoader(this,
+                        BrowserContract.Bookmarks.CONTENT_URI,
+                        projection,
+                        BrowserContract.Bookmarks.IS_FOLDER + " = 0",
+                        null,
+                        BrowserContract.Bookmarks.DATE_CREATED + " DESC");
             default:
                 throw new AssertionError("Asking for nonexistant loader!");
         }
@@ -484,6 +519,32 @@
             case LOADER_ID_FOLDER_CONTENTS:
                 mAdapter.changeCursor(cursor);
                 break;
+            case LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK:
+                LoaderManager manager = getLoaderManager();
+                if (cursor != null && cursor.moveToFirst()) {
+                    // Find the parent
+                    long lastUsedFolder = cursor.getLong(0);
+                    if (lastUsedFolder != mRootFolder
+                            && lastUsedFolder != mCurrentFolder
+                            && lastUsedFolder != 0) {
+                        // Find out the parent's name
+                        Bundle b = new Bundle();
+                        b.putLong(BrowserContract.Bookmarks._ID, lastUsedFolder);
+                        manager.initLoader(LOADER_ID_FIND_FOLDER_BY_ID, b, this);
+                    }
+                }
+                manager.destroyLoader(LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK);
+                break;
+            case LOADER_ID_FIND_FOLDER_BY_ID:
+                if (cursor != null && cursor.moveToFirst()) {
+                    long id = cursor.getLong(cursor.getColumnIndexOrThrow(
+                            BrowserContract.Bookmarks._ID));
+                    String title = cursor.getString(cursor.getColumnIndexOrThrow(
+                            BrowserContract.Bookmarks.TITLE));
+                    mFolderAdapter.addRecentFolder(id, title);
+                }
+                getLoaderManager().destroyLoader(LOADER_ID_FIND_FOLDER_BY_ID);
+                break;
             case LOADER_ID_ALL_FOLDERS:
                 long parent = mCurrentFolder;
                 int idIndex = cursor.getColumnIndexOrThrow(
@@ -665,7 +726,8 @@
         mCancelButton.setOnClickListener(this);
 
         mFolder = (FolderSpinner) findViewById(R.id.folder);
-        mFolder.setAdapter(new FolderSpinnerAdapter(!mEditingFolder));
+        mFolderAdapter = new FolderSpinnerAdapter(!mEditingFolder);
+        mFolder.setAdapter(mFolderAdapter);
         mFolder.setOnSetSelectionListener(this);
 
         mDefaultView = findViewById(R.id.default_view);
@@ -728,7 +790,8 @@
             mCurrentFolder = mRootFolder;
         }
         setupTopCrumb();
-        if (mEditingExisting || TextUtils.isEmpty(mOriginalUrl)) {
+        if (mEditingExisting || TextUtils.isEmpty(mOriginalUrl)
+                || !(mMap != null && mMap.getBoolean(CHECK_FOR_DUPE))) {
             onCurrentFolderFound();
         } else {
             // User is attempting to bookmark a site, rather than deliberately
@@ -757,6 +820,11 @@
             mFolder.setSelectionIgnoringSelectionChange(mEditingFolder ? 1 : 2);
         } else {
             setShowBookmarkIcon(true);
+            if (!mEditingExisting) {
+                // Find the most recently saved bookmark, so that we can include it in
+                // the list of options to save the current bookmark.
+                manager.initLoader(LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK, null, this);
+            }
             if (!mEditingFolder) {
                 // Initially the "Bookmarks" folder should be showing, rather than
                 // the home screen.  In the editing folder case, home screen is not
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index 756f8b8..bca999d 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -18,6 +18,7 @@
 
 import com.android.browser.Tab.LockIcon;
 
+import android.animation.ObjectAnimator;
 import android.app.Activity;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -32,7 +33,6 @@
 import android.view.Menu;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.View.OnKeyListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.WindowManager;
@@ -86,6 +86,8 @@
 
     private Toast mStopToast;
 
+    private boolean mTitleShowing;
+
     // the default <video> poster
     private Bitmap mDefaultVideoPoster;
     // the video progress view
@@ -114,6 +116,7 @@
         mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
                 .findViewById(R.id.fullscreen_custom_content);
         frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
+        mTitleShowing = false;
     }
 
     /**
@@ -161,6 +164,8 @@
     public void onConfigurationChanged(Configuration config) {
     }
 
+    public abstract void editUrl(boolean clearInput);
+
     // key handling
 
     @Override
@@ -189,7 +194,10 @@
 
     @Override
     public void bookmarkedStatusHasChanged(Tab tab) {
-        // no op in base case
+        if (tab.inForeground()) {
+            boolean isBookmark = tab.isBookmarkedSite();
+            getTitleBar().setCurrentUrlIsBookmark(isBookmark);
+        }
     }
 
     @Override
@@ -222,8 +230,7 @@
         onTabDataChanged(tab);
         onProgressChanged(tab);
         boolean incognito = mActiveTab.getWebView().isPrivateBrowsingEnabled();
-        getEmbeddedTitleBar().setIncognitoMode(incognito);
-        getFakeTitleBar().setIncognitoMode(incognito);
+        getTitleBar().setIncognitoMode(incognito);
     }
 
     Tab getActiveTab() {
@@ -292,7 +299,7 @@
     }
 
     private void removeTabFromContentView(Tab tab) {
-        hideFakeTitleBar();
+        hideTitleBar();
         // Remove the container that contains the main WebView.
         WebView mainView = tab.getWebView();
         View container = tab.getViewContainer();
@@ -381,49 +388,47 @@
         mContentView.addView(container, COVER_SCREEN_PARAMS);
     }
 
-    void showFakeTitleBar() {
-        if (!isFakeTitleBarShowing() && !isActivityPaused()) {
-            WebView mainView = mUiController.getCurrentWebView();
-            // if there is no current WebView, don't show the faked title bar;
-            if (mainView == null) {
-                return;
-            }
-            // Do not need to check for null, since the current tab will have
-            // at least a main WebView, or we would have returned above.
-            if (mUiController.isInCustomActionMode()) {
-                // Do not show the fake title bar, while a custom ActionMode
-                // (i.e. find or select) is showing.
-                return;
-            }
-            attachFakeTitleBar(mainView);
+    boolean canShowTitleBar() {
+        return !isTitleBarShowing()
+                && !isActivityPaused()
+                && (getActiveTab() != null)
+                && (getActiveTab().getWebView() != null)
+                && !mUiController.isInCustomActionMode();
+    }
+
+    void showTitleBar() {
+        mTitleShowing = true;
+    }
+
+    protected void hideTitleBar() {
+        mTitleShowing = false;
+    }
+
+    protected boolean isTitleBarShowing() {
+        return mTitleShowing;
+    }
+
+    protected abstract TitleBarBase getTitleBar();
+
+    protected void setTitleGravity(int gravity) {
+        getTitleBar().setTitleGravity(gravity);
+        Tab tab = getActiveTab();
+        if ((tab != null) && (tab.getWebView() != null)) {
+            tab.getWebView().setTitleBarGravity(gravity);
         }
     }
 
-    protected abstract void attachFakeTitleBar(WebView mainView);
-
-    protected abstract void hideFakeTitleBar();
-
-    protected abstract boolean isFakeTitleBarShowing();
-
-    protected abstract TitleBarBase getFakeTitleBar();
-
-    protected abstract TitleBarBase getEmbeddedTitleBar();
-
     @Override
     public void showVoiceTitleBar(String title) {
-        getEmbeddedTitleBar().setInVoiceMode(true);
-        getEmbeddedTitleBar().setDisplayTitle(title);
-        getFakeTitleBar().setInVoiceMode(true);
-        getFakeTitleBar().setDisplayTitle(title);
+        getTitleBar().setInVoiceMode(true);
+        getTitleBar().setDisplayTitle(title);
     }
 
     @Override
     public void revertVoiceTitleBar(Tab tab) {
-        getEmbeddedTitleBar().setInVoiceMode(false);
+        getTitleBar().setInVoiceMode(false);
         String url = tab.getUrl();
-        getEmbeddedTitleBar().setDisplayTitle(url);
-        getFakeTitleBar().setInVoiceMode(false);
-        getFakeTitleBar().setDisplayTitle(url);
+        getTitleBar().setDisplayTitle(url);
     }
 
     @Override
@@ -440,12 +445,17 @@
         FrameLayout wrapper =
             (FrameLayout) mContentView.findViewById(R.id.webview_wrapper);
         wrapper.setVisibility(View.GONE);
-        hideFakeTitleBar();
+        hideTitleBar();
         dismissIME();
         if (mActiveTab != null) {
             WebView web = mActiveTab.getWebView();
             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);
     }
 
@@ -547,8 +557,7 @@
         } else if (lockIconType == LockIcon.LOCK_ICON_MIXED) {
             d = mMixLockIcon;
         }
-        getEmbeddedTitleBar().setLock(d);
-        getFakeTitleBar().setLock(d);
+        getTitleBar().setLock(d);
     }
 
     protected void setUrlTitle(Tab tab) {
@@ -559,8 +568,7 @@
         }
         if (tab.isInVoiceSearchMode()) return;
         if (tab.inForeground()) {
-            getEmbeddedTitleBar().setDisplayTitle(url);
-            getFakeTitleBar().setDisplayTitle(url);
+            getTitleBar().setDisplayTitle(url);
         }
     }
 
@@ -568,8 +576,7 @@
     protected void setFavicon(Tab tab) {
         if (tab.inForeground()) {
             Bitmap icon = tab.getFavicon();
-            getEmbeddedTitleBar().setFavicon(icon);
-            getFakeTitleBar().setFavicon(icon);
+            getTitleBar().setFavicon(icon);
         }
     }
 
@@ -578,7 +585,7 @@
         if (inLoad) {
             // the titlebar was removed when the CAB was shown
             // if the page is loading, show it again
-            showFakeTitleBar();
+            showTitleBar();
         }
     }
 
@@ -623,6 +630,7 @@
 
     @Override
     public void setShouldShowErrorConsole(Tab tab, boolean flag) {
+        if (tab == null) return;
         ErrorConsoleView errorConsole = tab.getErrorConsole(true);
         if (flag) {
             // Setting the show state of the console will cause it's the layout
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
index beea489..e7dc729 100644
--- a/src/com/android/browser/Bookmarks.java
+++ b/src/com/android/browser/Bookmarks.java
@@ -39,7 +39,7 @@
 /**
  *  This class is purely to have a common place for adding/deleting bookmarks.
  */
-/* package */ class Bookmarks {
+public class Bookmarks {
     // We only want the user to be able to bookmark content that
     // the browser can handle directly.
     private static final String acceptableBookmarkSchemes[] = {
@@ -162,11 +162,9 @@
 
     static final String QUERY_BOOKMARKS_WHERE =
             Combined.URL + " == ? OR " +
-            Combined.URL + " == ? OR " +
-            Combined.URL + " LIKE ? || '%' OR " +
-            Combined.URL + " LIKE ? || '%'";
+            Combined.URL + " == ?";
 
-    /* package */ static Cursor queryCombinedForUrl(ContentResolver cr,
+    public static Cursor queryCombinedForUrl(ContentResolver cr,
             String originalUrl, String url) {
         if (cr == null || url == null) {
             return null;
@@ -179,17 +177,8 @@
     
         // Look for both the original url and the actual url. This takes in to
         // account redirects.
-        String originalUrlNoQuery = removeQuery(originalUrl);
-        String urlNoQuery = removeQuery(url);
-        originalUrl = originalUrlNoQuery + '?';
-        url = urlNoQuery + '?';
     
-        // Use NoQuery to search for the base url (i.e. if the url is
-        // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com)
-        // Use url to match the base url with other queries (i.e. if the url is
-        // http://www.google.com/m, search for
-        // http://www.google.com/m?some_query)
-        final String[] selArgs = new String[] { originalUrlNoQuery, urlNoQuery, originalUrl, url };
+        final String[] selArgs = new String[] { originalUrl, url };
         final String[] projection = new String[] { Combined.URL };
         return cr.query(Combined.CONTENT_URI, projection, QUERY_BOOKMARKS_WHERE, selArgs, null);
     }
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 899a7c2..8c38e59 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -19,6 +19,7 @@
 import com.google.common.annotations.VisibleForTesting;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
@@ -95,9 +96,7 @@
         }
 
         mController = new Controller(this);
-        boolean xlarge = (getResources().getConfiguration().screenLayout
-                & Configuration.SCREENLAYOUT_SIZE_MASK)
-                == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+        boolean xlarge = isXlarge(this);
         if (xlarge) {
             mUi = new XLargeUi(this, mController);
         } else {
@@ -114,6 +113,12 @@
         mController.start(icicle, getIntent());
     }
 
+    public static boolean isXlarge(Context context) {
+        return (context.getResources().getConfiguration().screenLayout
+                & Configuration.SCREENLAYOUT_SIZE_MASK)
+                == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+    }
+
     @VisibleForTesting
     Controller getController() {
         return mController;
@@ -271,5 +276,4 @@
         mController.onActivityResult(requestCode, resultCode, intent);
     }
 
-
 }
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index 7475237..98b25a8 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -262,6 +262,10 @@
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         inflater.inflate(R.menu.bookmark, menu);
+        if (!BrowserActivity.isXlarge(getActivity())) {
+            MenuItem item = menu.findItem(R.id.add_bookmark);
+            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+        }
     }
 
     @Override
@@ -372,7 +376,7 @@
                 .getDefaultSharedPreferences(getActivity());
         prefs.registerOnSharedPreferenceChangeListener(this);
         mCurrentView =
-            prefs.getInt(PREF_SELECTED_VIEW, BrowserBookmarksPage.VIEW_THUMBNAILS);
+            prefs.getInt(PREF_SELECTED_VIEW, getDefaultView());
         mAdapter = new BrowserBookmarksAdapter(getActivity(), mCurrentView);
         lm.restartLoader(LOADER_BOOKMARKS, null, this);
 
@@ -382,6 +386,13 @@
         return mRoot;
     }
 
+     private int getDefaultView() {
+        if (BrowserActivity.isXlarge(getActivity())) {
+            return VIEW_THUMBNAILS;
+        }
+        return VIEW_LIST;
+    }
+
     @Override
     public void onDestroyView() {
         super.onDestroyView();
@@ -585,13 +596,17 @@
         switch (mCurrentView) {
         case VIEW_THUMBNAILS:
             mList.setAdapter(null);
-            mGrid.setAdapter(mAdapter);
+            if (mGrid.getAdapter() != mAdapter) {
+                mGrid.setAdapter(mAdapter);
+            }
             mGrid.setVisibility(View.VISIBLE);
             mList.setVisibility(View.GONE);
             break;
         case VIEW_LIST:
             mGrid.setAdapter(null);
-            mList.setAdapter(mAdapter);
+            if (mList.getAdapter() != mAdapter) {
+                mList.setAdapter(mAdapter);
+            }
             mGrid.setVisibility(View.GONE);
             mList.setVisibility(View.VISIBLE);
             break;
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index faf0042..f3bc48a 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -109,7 +109,7 @@
     private boolean lightTouch = false;
     private boolean navDump = false;
     private boolean hardwareAccelerated = true;
-
+    private boolean showVisualIndicator = false;
     // Lab settings
     private boolean quickControls = false;
     private boolean useMostVisitedHomepage = false;
@@ -162,6 +162,7 @@
     public final static String PREF_AUTOFILL_PROFILE = "autofill_profile";
     public final static String PREF_AUTOFILL_ACTIVE_PROFILE_ID = "autofill_active_profile_id";
     public final static String PREF_HARDWARE_ACCEL = "enable_hardware_accel";
+    public final static String PREF_VISUAL_INDICATOR = "enable_visual_indicator";
     public final static String PREF_USER_AGENT = "user_agent";
 
     public final static String PREF_QUICK_CONTROLS = "enable_quick_controls";
@@ -251,6 +252,7 @@
             s.setUseWideViewPort(b.useWideViewPort);
             s.setLoadsImagesAutomatically(b.loadsImagesAutomatically);
             s.setJavaScriptEnabled(b.javaScriptEnabled);
+            s.setShowVisualIndicator(b.showVisualIndicator);
             s.setPluginState(b.pluginState);
             s.setJavaScriptCanOpenWindowsAutomatically(
                     b.javaScriptCanOpenWindowsAutomatically);
@@ -493,6 +495,7 @@
             tracing = p.getBoolean("enable_tracing", tracing);
             lightTouch = p.getBoolean("enable_light_touch", lightTouch);
             navDump = p.getBoolean("enable_nav_dump", navDump);
+            showVisualIndicator = p.getBoolean(PREF_VISUAL_INDICATOR, showVisualIndicator);
         }
 
         quickControls = p.getBoolean(PREF_QUICK_CONTROLS, quickControls);
@@ -589,6 +592,10 @@
         return hardwareAccelerated;
     }
 
+    public boolean showVisualIndicator() {
+        return showVisualIndicator;
+    }
+
     public boolean useQuickControls() {
         return quickControls;
     }
@@ -886,6 +893,8 @@
             SharedPreferences p, String key) {
         if (PREF_HARDWARE_ACCEL.equals(key)) {
             hardwareAccelerated = p.getBoolean(PREF_HARDWARE_ACCEL, hardwareAccelerated);
+        } else if (PREF_VISUAL_INDICATOR.equals(key)) {
+            showVisualIndicator = p.getBoolean(PREF_VISUAL_INDICATOR, showVisualIndicator);
         } else if (PREF_USER_AGENT.equals(key)) {
             userAgent = Integer.parseInt(p.getString(PREF_USER_AGENT, "0"));
             update();
diff --git a/src/com/android/browser/CombinedBookmarkHistoryView.java b/src/com/android/browser/CombinedBookmarkHistoryView.java
index 173abba..793f7a2 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryView.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryView.java
@@ -157,8 +157,12 @@
     }
 
     void setupActionBar(int startingFragment) {
-        mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
-                | ActionBar.DISPLAY_USE_LOGO);
+        if (BrowserActivity.isXlarge(mContext)) {
+            mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
+                    | ActionBar.DISPLAY_USE_LOGO);
+        } else {
+            mActionBar.setDisplayOptions(0);
+        }
         mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
         mActionBar.removeAllTabs();
         mTabBookmarks = mActionBar.newTab();
@@ -170,7 +174,7 @@
         mTabHistory.setTabListener(this);
         mActionBar.addTab(mTabHistory, FRAGMENT_ID_HISTORY == startingFragment);
         mActionBar.setCustomView(mBookmarksHeader);
-
+        mActionBar.show();
     }
 
     @Override
@@ -324,7 +328,7 @@
             mUiController.onUrlSelected(settings.getHomePage(), false);
             return true;
         case R.id.add_bookmark:
-            mUiController.bookmarkCurrentPage(mBookmarks.getFolderId());
+            mUiController.bookmarkCurrentPage(mBookmarks.getFolderId(), false);
             return true;
         case R.id.preferences_menu_id:
             Intent intent = new Intent(mActivity, BrowserPreferencesPage.class);
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 74a66b1..0961f80 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -43,6 +43,7 @@
 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;
@@ -77,6 +78,7 @@
 import android.webkit.WebIconDatabase;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
+import android.widget.Toast;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -508,9 +510,9 @@
                         break;
 
                     case UPDATE_BOOKMARK_THUMBNAIL:
-                        WebView view = (WebView) msg.obj;
-                        if (view != null) {
-                            updateScreenshot(view);
+                        Tab tab = (Tab) msg.obj;
+                        if (tab != null) {
+                            updateScreenshot(tab);
                         }
                         break;
                 }
@@ -676,7 +678,7 @@
     }
 
     void onDestroy() {
-        if (mUploadHandler != null) {
+        if (mUploadHandler != null && !mUploadHandler.handled()) {
             mUploadHandler.onResult(Activity.RESULT_CANCELED, null);
             mUploadHandler = null;
         }
@@ -747,7 +749,7 @@
         // an incorrect screenshot. Therefore, remove any pending thumbnail
         // messages from the queue.
         mHandler.removeMessages(Controller.UPDATE_BOOKMARK_THUMBNAIL,
-                view);
+                tab);
 
         // reset sync timer to avoid sync starts during loading a page
         CookieSyncManager.getInstance().resetSync();
@@ -794,7 +796,7 @@
                 // Only update the bookmark screenshot if the user did not
                 // cancel the load early.
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                        UPDATE_BOOKMARK_THUMBNAIL, 0, 0, tab.getWebView()),
+                        UPDATE_BOOKMARK_THUMBNAIL, 0, 0, tab),
                         500);
             }
         }
@@ -1024,9 +1026,7 @@
     // callback from phone title bar
     public void editUrl() {
         if (mOptionsMenuOpen) mActivity.closeOptionsMenu();
-        String url = (getCurrentTopWebView() == null) ? null : getCurrentTopWebView().getUrl();
-        startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
-                null, false);
+        mUi.editUrl(false);
     }
 
     public void startVoiceSearch() {
@@ -1088,10 +1088,10 @@
                 }
                 break;
             case FILE_SELECTED:
-                // Choose a file from the file picker.
-                if (null == mUploadHandler) break;
+                // Chose a file from the file picker.
+                if (null == mUploadHandler)
+                    break;
                 mUploadHandler.onResult(resultCode, intent);
-                mUploadHandler = null;
                 break;
             case AUTOFILL_SETUP:
                 // Determine whether a profile was actually set up or not
@@ -1465,6 +1465,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);
+                String url = w != null ? w.getUrl() : null;
+                archive.setVisible(url != null && !url.endsWith(".webarchivexml"));
                 break;
         }
         mCurrentMenuState = mMenuState;
@@ -1520,7 +1523,7 @@
                 break;
 
             case R.id.add_bookmark_menu_id:
-                bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID);
+                bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID, false);
                 break;
 
             case R.id.stop_reload_menu_id:
@@ -1567,6 +1570,54 @@
                 getCurrentTopWebView().showFindDialog(null, true);
                 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,
+                            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;
+                }
+                WebView topWebView = getCurrentTopWebView();
+                final String title = topWebView.getTitle();
+                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.completedDownload(null == title ? value : title,
+                                                value, true, "application/x-webarchive-xml",
+                                                value, length, true);
+                                    }
+                                }.start();
+                                return;
+                            }
+                        }
+                        Toast.makeText(mActivity,
+                                R.string.webarchive_failed, Toast.LENGTH_SHORT).show();
+                    }
+                });
+                break;
+
             case R.id.page_info_menu_id:
                 mPageDialogsHandler.showPageInfo(mTabControl.getCurrentTab(),
                         false);
@@ -1804,9 +1855,13 @@
     /**
      * add the current page as a bookmark to the given folder id
      * @param folderId use -1 for the default folder
+     * @param canBeAnEdit If true, check to see whether the site is already
+     *          bookmarked, and if it is, edit that bookmark.  If false, and
+     *          the site is already bookmarked, do not attempt to edit the
+     *          existing bookmark.
      */
     @Override
-    public void bookmarkCurrentPage(long folderId) {
+    public void bookmarkCurrentPage(long folderId, boolean canBeAnEdit) {
         Intent i = new Intent(mActivity,
                 AddBookmarkPage.class);
         WebView w = getCurrentTopWebView();
@@ -1827,6 +1882,9 @@
         i.putExtra(BrowserContract.Bookmarks.FAVICON, w.getFavicon());
         i.putExtra(BrowserContract.Bookmarks.PARENT,
                 folderId);
+        if (canBeAnEdit) {
+            i.putExtra(AddBookmarkPage.CHECK_FOR_DUPE, true);
+        }
         // Put the dialog at the upper right of the screen, covering the
         // star on the title bar.
         i.putExtra("gravity", Gravity.RIGHT | Gravity.TOP);
@@ -1905,13 +1963,31 @@
         return ret;
     }
 
-    private void updateScreenshot(WebView view) {
+    private void updateScreenshot(Tab tab) {
         // If this is a bookmarked site, add a screenshot to the database.
-        // FIXME: When should we update?  Every time?
         // FIXME: Would like to make sure there is actually something to
         // draw, but the API for that (WebViewCore.pictureReady()) is not
         // currently accessible here.
 
+        WebView view = tab.getWebView();
+        if (view == null) {
+            // Tab was destroyed
+            return;
+        }
+        final String url = tab.getUrl();
+        final String originalUrl = view.getOriginalUrl();
+
+        if (TextUtils.isEmpty(url)) {
+            return;
+        }
+
+        // Only update thumbnails for web urls (http(s)://), not for
+        // about:, javascript:, data:, etc...
+        // Unless it is a bookmarked site, then always update
+        if (!Patterns.WEB_URL.matcher(url).matches() && !tab.isBookmarkedSite()) {
+            return;
+        }
+
         final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(mActivity),
                 getDesiredThumbnailHeight(mActivity));
         if (bm == null) {
@@ -1919,41 +1995,34 @@
         }
 
         final ContentResolver cr = mActivity.getContentResolver();
-        final String url = view.getUrl();
-        final String originalUrl = view.getOriginalUrl();
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... unused) {
+                Cursor cursor = null;
+                try {
+                    // TODO: Clean this up
+                    cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
+                    if (cursor != null && cursor.moveToFirst()) {
+                        final ByteArrayOutputStream os =
+                                new ByteArrayOutputStream();
+                        bm.compress(Bitmap.CompressFormat.PNG, 100, os);
 
-        // Only update thumbnails for web urls (http(s)://), not for
-        // about:, javascript:, data:, etc...
-        if (url != null && Patterns.WEB_URL.matcher(url).matches()) {
-            new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... unused) {
-                    Cursor cursor = null;
-                    try {
-                        // TODO: Clean this up
-                        cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
-                        if (cursor != null && cursor.moveToFirst()) {
-                            final ByteArrayOutputStream os =
-                                    new ByteArrayOutputStream();
-                            bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+                        ContentValues values = new ContentValues();
+                        values.put(Images.THUMBNAIL, os.toByteArray());
 
-                            ContentValues values = new ContentValues();
-                            values.put(Images.THUMBNAIL, os.toByteArray());
+                        do {
                             values.put(Images.URL, cursor.getString(0));
-
-                            do {
-                                cr.update(Images.CONTENT_URI, values, null, null);
-                            } while (cursor.moveToNext());
-                        }
-                    } catch (IllegalStateException e) {
-                        // Ignore
-                    } finally {
-                        if (cursor != null) cursor.close();
+                            cr.update(Images.CONTENT_URI, values, null, null);
+                        } while (cursor.moveToNext());
                     }
-                    return null;
+                } catch (IllegalStateException e) {
+                    // Ignore
+                } finally {
+                    if (cursor != null) cursor.close();
                 }
-            }.execute();
-        }
+                return null;
+            }
+        }.execute();
     }
 
     private class Copy implements OnMenuItemClickListener {
@@ -2311,7 +2380,7 @@
                                 + "while handing goBackOnePageOrQuit.");
                     }
                     pauseWebViewTimers(current);
-                    removeTab(current);
+                    closeCurrentTab();
                 }
                 /*
                  * Instead of finishing the activity, simply push this to the back
@@ -2345,6 +2414,12 @@
         startSearch(result, false, bundle, false);
     }
 
+    @Override
+    public void startSearch(String url) {
+        startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
+                null, false);
+    }
+
     private void startSearch(String initialQuery, boolean selectInitialQuery,
             Bundle appSearchData, boolean globalSearch) {
         if (appSearchData == null) {
@@ -2378,14 +2453,13 @@
 
         // Even if MENU is already held down, we need to call to super to open
         // the IME on long press.
-        if (!noModifiers && KeyEvent.KEYCODE_MENU == keyCode) {
+        if (!noModifiers
+                && ((KeyEvent.KEYCODE_MENU == keyCode)
+                        || (KeyEvent.KEYCODE_CTRL_LEFT == keyCode)
+                        || (KeyEvent.KEYCODE_CTRL_RIGHT == keyCode))) {
             mMenuIsDown = true;
             return false;
         }
-        // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
-        // still down, we don't want to trigger the search. Pretend to consume
-        // the key and do nothing.
-        if (mMenuIsDown) return true;
 
         WebView webView = getCurrentTopWebView();
         if (webView == null) return false;
@@ -2458,10 +2532,12 @@
 //          case KeyEvent.KEYCODE_O:    // in Chrome: open file
 //          case KeyEvent.KEYCODE_P:    // in Chrome: print page
 //          case KeyEvent.KEYCODE_Q:    // unused
-//            case KeyEvent.KEYCODE_R:
+//          case KeyEvent.KEYCODE_R:
 //          case KeyEvent.KEYCODE_S:    // in Chrome: saves page
             case KeyEvent.KEYCODE_T:
-                if (ctrl) {
+                // we can't use the ctrl/shift flags, they check for
+                // exclusive use of a modifier
+                if (event.isCtrlPressed()) {
                     if (event.isShiftPressed()) {
                         openIncognitoTab();
                     } else {
@@ -2477,8 +2553,8 @@
 //          case KeyEvent.KEYCODE_Y:    // unused
 //          case KeyEvent.KEYCODE_Z:    // unused
         }
-        // if we get here, it is a regular key and webview is not null
-        return mUi.dispatchKey(keyCode, event);
+        // it is a regular key and webview is not null
+         return mUi.dispatchKey(keyCode, event);
     }
 
     boolean onKeyUp(int keyCode, KeyEvent event) {
diff --git a/src/com/android/browser/DownloadHandler.java b/src/com/android/browser/DownloadHandler.java
index 40278f4..4903a41 100644
--- a/src/com/android/browser/DownloadHandler.java
+++ b/src/com/android/browser/DownloadHandler.java
@@ -181,7 +181,13 @@
 
         String addressString = webAddress.toString();
         Uri uri = Uri.parse(addressString);
-        final DownloadManager.Request request = new DownloadManager.Request(uri);
+        final DownloadManager.Request request;
+        try {
+            request = new DownloadManager.Request(uri);
+        } catch (IllegalArgumentException e) {
+            Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show();
+            return;
+        }
         request.setMimeType(mimetype);
         // set downloaded file destination to /sdcard/Download.
         // or, should it be set to one of several Environment.DIRECTORY* dirs depending on mimetype?
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
index 768eab5..64a7316 100644
--- a/src/com/android/browser/DownloadTouchIcon.java
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -39,10 +39,10 @@
 import android.webkit.WebView;
 
 import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.InputStream;
 
 class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
+
     private final ContentResolver mContentResolver;
     private Cursor mCursor;
     private final String mOriginalUrl;
@@ -110,18 +110,21 @@
         String url = values[0];
 
         if (inDatabase || mMessage != null) {
-            AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
-            HttpHost httpHost = Proxy.getPreferredHttpHost(mContext, url);
-            if (httpHost != null) {
-                ConnRouteParams.setDefaultProxy(client.getParams(), httpHost);
-            }
-
-            HttpGet request = new HttpGet(url);
-
-            // Follow redirects
-            HttpClientParams.setRedirecting(client.getParams(), true);
+            AndroidHttpClient client = null;
+            HttpGet request = null;
 
             try {
+                client = AndroidHttpClient.newInstance(mUserAgent);
+                HttpHost httpHost = Proxy.getPreferredHttpHost(mContext, url);
+                if (httpHost != null) {
+                    ConnRouteParams.setDefaultProxy(client.getParams(), httpHost);
+                }
+
+                request = new HttpGet(url);
+
+                // Follow redirects
+                HttpClientParams.setRedirecting(client.getParams(), true);
+
                 HttpResponse response = client.execute(request);
                 if (response.getStatusLine().getStatusCode() == 200) {
                     HttpEntity entity = response.getEntity();
@@ -139,12 +142,14 @@
                         }
                     }
                 }
-            } catch (IllegalArgumentException ex) {
-                request.abort();
-            } catch (IOException ex) {
-                request.abort();
+            } catch (Exception ex) {
+                if (request != null) {
+                    request.abort();
+                }
             } finally {
-                client.close();
+                if (client != null) {
+                    client.close();
+                }
             }
         }
 
@@ -183,9 +188,9 @@
 
             ContentValues values = new ContentValues();
             values.put(Images.TOUCH_ICON, os.toByteArray());
-            values.put(Images.URL, mCursor.getString(0));
 
             do {
+                values.put(Images.URL, mCursor.getString(0));
                 mContentResolver.update(Images.CONTENT_URI, values, null, null);
             } while (mCursor.moveToNext());
         }
diff --git a/src/com/android/browser/GoogleAccountLogin.java b/src/com/android/browser/GoogleAccountLogin.java
index eaf45ea..3712490 100644
--- a/src/com/android/browser/GoogleAccountLogin.java
+++ b/src/com/android/browser/GoogleAccountLogin.java
@@ -336,7 +336,7 @@
                 @Override public void run() {
                     mProgressDialog.dismiss();
                 }
-            }, 1000);
+            }, 2000);
 
             mRunnable = null;
             mWebView.destroy();
diff --git a/src/com/android/browser/PhoneUi.java b/src/com/android/browser/PhoneUi.java
index 4119c29..f1939e4 100644
--- a/src/com/android/browser/PhoneUi.java
+++ b/src/com/android/browser/PhoneUi.java
@@ -24,8 +24,8 @@
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.Menu;
+import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.webkit.WebView;
 
@@ -37,8 +37,8 @@
     private static final String LOGTAG = "PhoneUi";
 
     private TitleBar mTitleBar;
-    private TitleBar mFakeTitleBar;
     private ActiveTabsPage mActiveTabsPage;
+    private TouchProxy mTitleOverlay;
 
     boolean mExtendedMenuOpen;
     boolean mOptionsMenuOpen;
@@ -49,12 +49,17 @@
      */
     public PhoneUi(Activity browser, UiController controller) {
         super(browser, controller);
-        mTitleBar = new TitleBar(mActivity, mUiController);
+        mTitleBar = new TitleBar(mActivity, mUiController, this);
         // mTitleBar will be always be shown in the fully loaded mode on
         // phone
         mTitleBar.setProgress(100);
-        mFakeTitleBar = new TitleBar(mActivity, mUiController);
+        mActivity.getActionBar().hide();
+    }
 
+    @Override
+    public void hideComboView() {
+        super.hideComboView();
+        mActivity.getActionBar().hide();
     }
 
     // webview factory
@@ -90,7 +95,13 @@
 
     @Override
     public void onDestroy() {
-        hideFakeTitleBar();
+        hideTitleBar();
+    }
+
+    @Override
+    public void editUrl(boolean clearInput) {
+        String url = getActiveTab().getUrl();
+        mUiController.startSearch(url);
     }
 
     @Override
@@ -107,14 +118,14 @@
     public void onProgressChanged(Tab tab) {
         if (tab.inForeground()) {
             int progress = tab.getLoadProgress();
-            mFakeTitleBar.setProgress(progress);
+            mTitleBar.setProgress(progress);
             if (progress == 100) {
                 if (!mOptionsMenuOpen || !mExtendedMenuOpen) {
-                    hideFakeTitleBar();
+                    hideTitleBar();
                 }
             } else {
                 if (!mOptionsMenuOpen || mExtendedMenuOpen) {
-                    showFakeTitleBar();
+                    showTitleBar();
                 }
             }
         }
@@ -130,7 +141,7 @@
             Log.e(LOGTAG, "active tab with no webview detected");
             return;
         }
-        view.setEmbeddedTitleBar(getEmbeddedTitleBar());
+        view.setEmbeddedTitleBar(getTitleBar());
         if (tab.isInVoiceSearchMode()) {
             showVoiceTitleBar(tab.getVoiceDisplayTitle());
         } else {
@@ -140,57 +151,23 @@
     }
 
     @Override
-    protected void attachFakeTitleBar(WebView mainView) {
-        WindowManager manager = (WindowManager)
-                mActivity.getSystemService(Context.WINDOW_SERVICE);
-
-        // Add the title bar to the window manager so it can receive
-        // touches while the menu is up
-        WindowManager.LayoutParams params =
-                new WindowManager.LayoutParams(
-                        ViewGroup.LayoutParams.MATCH_PARENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT,
-                        WindowManager.LayoutParams.TYPE_APPLICATION,
-                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-                        PixelFormat.TRANSLUCENT);
-        params.gravity = Gravity.TOP;
-        boolean atTop = mainView.getScrollY() == 0;
-        params.windowAnimations = atTop ? 0 : R.style.TitleBar;
-        manager.addView(mFakeTitleBar, params);
+    protected void showTitleBar() {
+        if (canShowTitleBar()) {
+            setTitleGravity(Gravity.TOP);
+            super.showTitleBar();
+        }
     }
 
     @Override
-    protected void hideFakeTitleBar() {
-        if (!isFakeTitleBarShowing()) return;
-        WindowManager.LayoutParams params =
-                (WindowManager.LayoutParams) mFakeTitleBar.getLayoutParams();
-        WebView mainView = mUiController.getCurrentWebView();
-        // Although we decided whether or not to animate based on the
-        // current
-        // scroll position, the scroll position may have changed since the
-        // fake title bar was displayed. Make sure it has the appropriate
-        // animation/lack thereof before removing.
-        params.windowAnimations =
-                mainView != null && mainView.getScrollY() == 0 ?
-                        0 : R.style.TitleBar;
-        WindowManager manager = (WindowManager) mActivity
-                .getSystemService(Context.WINDOW_SERVICE);
-        manager.updateViewLayout(mFakeTitleBar, params);
-        manager.removeView(mFakeTitleBar);
+    protected void hideTitleBar() {
+        if (isTitleBarShowing()) {
+            setTitleGravity(Gravity.NO_GRAVITY);
+            super.hideTitleBar();
+        }
     }
 
     @Override
-    protected boolean isFakeTitleBarShowing() {
-        return (mFakeTitleBar.getParent() != null);
-    }
-
-    @Override
-    protected TitleBarBase getFakeTitleBar() {
-        return mFakeTitleBar;
-    }
-
-    @Override
-    protected TitleBarBase getEmbeddedTitleBar() {
+    protected TitleBarBase getTitleBar() {
         return mTitleBar;
     }
 
@@ -200,7 +177,7 @@
     public void showActiveTabsPage() {
         mActiveTabsPage = new ActiveTabsPage(mActivity, mUiController);
         mTitleBar.setVisibility(View.GONE);
-        hideFakeTitleBar();
+        hideTitleBar();
         mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
         mActiveTabsPage.requestFocus();
     }
@@ -225,8 +202,14 @@
     @Override
     public void onOptionsMenuOpened() {
         mOptionsMenuOpen = true;
-        // options menu opened, show fake title bar
-        showFakeTitleBar();
+        // options menu opened, show title bar
+        showTitleBar();
+        if (mTitleOverlay == null) {
+            // This assumes that getTitleBar always returns the same View
+            mTitleOverlay = new TouchProxy(mActivity, getTitleBar());
+        }
+        mActivity.getWindowManager().addView(mTitleOverlay,
+                mTitleOverlay.getWindowLayoutParams());
     }
 
     @Override
@@ -234,32 +217,33 @@
         // Switching the menu to expanded view, so hide the
         // title bar.
         mExtendedMenuOpen = true;
-        hideFakeTitleBar();
+        hideTitleBar();
     }
 
     @Override
     public void onOptionsMenuClosed(boolean inLoad) {
         mOptionsMenuOpen = false;
-        if (!inLoad) {
-            hideFakeTitleBar();
+        mActivity.getWindowManager().removeView(mTitleOverlay);
+        if (!inLoad && !getTitleBar().hasFocus()) {
+            hideTitleBar();
         }
     }
 
     @Override
     public void onExtendedMenuClosed(boolean inLoad) {
         mExtendedMenuOpen = false;
-        showFakeTitleBar();
+        showTitleBar();
     }
 
     @Override
     public void onContextMenuCreated(Menu menu) {
-        hideFakeTitleBar();
+        hideTitleBar();
     }
 
     @Override
     public void onContextMenuClosed(Menu menu, boolean inLoad) {
         if (inLoad) {
-            showFakeTitleBar();
+            showTitleBar();
         }
     }
 
@@ -267,7 +251,7 @@
 
     @Override
     public void onActionModeStarted(ActionMode mode) {
-        hideFakeTitleBar();
+        hideTitleBar();
     }
 
     @Override
@@ -275,4 +259,32 @@
         return false;
     }
 
+    static class TouchProxy extends View {
+
+        View mTarget;
+
+        TouchProxy(Context context, View target) {
+            super(context);
+            mTarget = target;
+        }
+
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent event) {
+            return mTarget.dispatchTouchEvent(event);
+        }
+
+        WindowManager.LayoutParams getWindowLayoutParams() {
+            WindowManager.LayoutParams params =
+                new WindowManager.LayoutParams(
+                        mTarget.getWidth(),
+                        mTarget.getHeight(),
+                        WindowManager.LayoutParams.TYPE_APPLICATION,
+                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                        PixelFormat.TRANSPARENT);
+            params.gravity = Gravity.TOP | Gravity.LEFT;
+            params.y = mTarget.getTop();
+            params.x = mTarget.getLeft();
+            return params;
+        }
+    }
 }
diff --git a/src/com/android/browser/PieControl.java b/src/com/android/browser/PieControl.java
index 6326f2e..23dcced 100644
--- a/src/com/android/browser/PieControl.java
+++ b/src/com/android/browser/PieControl.java
@@ -126,14 +126,14 @@
                 web.reload();
             }
         } else if (mUrl == v) {
-            mUi.showFakeTitleBarAndEdit();
+            mUi.showTitleBarAndEdit();
         } else if (mOptions == v) {
             mActivity.openOptionsMenu();
         } else if (mBookmarks == v) {
             mUiController.bookmarksOrHistoryPicker(false);
         } else if (mNewTab == v) {
             mUiController.openTabToHomePage();
-            mUi.showFakeTitleBarAndEdit();
+            mUi.showTitleBarAndEdit();
         } else if (mClose == v) {
             mUiController.closeCurrentTab();
         } else {
diff --git a/src/com/android/browser/ScrollWebView.java b/src/com/android/browser/ScrollWebView.java
index e2ef902..1d7f23a 100644
--- a/src/com/android/browser/ScrollWebView.java
+++ b/src/com/android/browser/ScrollWebView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.webkit.WebView;
 
@@ -31,6 +32,7 @@
     private ScrollListener mScrollListener;
     private boolean mIsCancelled;
     private boolean mBackgroundRemoved = false;
+    private boolean mUserInitiated = false;
 
     /**
      * @param context
@@ -71,7 +73,7 @@
     // scroll runnable implementation
     public void run() {
         if (!mIsCancelled && (mScrollListener != null)) {
-            mScrollListener.onScroll(getVisibleTitleHeight());
+            mScrollListener.onScroll(getVisibleTitleHeight(), mUserInitiated);
         }
     }
 
@@ -89,6 +91,17 @@
     }
 
     @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();
@@ -111,7 +124,7 @@
     // callback for scroll events
 
     interface ScrollListener {
-        public void onScroll(int visibleTitleHeight);
+        public void onScroll(int visibleTitleHeight, boolean userInitiated);
     }
 
     @Override
diff --git a/src/com/android/browser/TabBar.java b/src/com/android/browser/TabBar.java
index d115f1a..1d17cb3 100644
--- a/src/com/android/browser/TabBar.java
+++ b/src/com/android/browser/TabBar.java
@@ -75,8 +75,6 @@
 
     private Map<Tab, TabView> mTabMap;
 
-    private int mVisibleTitleHeight;
-
     private Drawable mGenericFavicon;
 
     private int mCurrentTextureWidth = 0;
@@ -87,6 +85,7 @@
 
     private final Paint mActiveShaderPaint = new Paint();
     private final Paint mInactiveShaderPaint = new Paint();
+    private final Paint mFocusPaint = new Paint();
     private final Matrix mActiveMatrix = new Matrix();
     private final Matrix mInactiveMatrix = new Matrix();
 
@@ -96,7 +95,6 @@
     private int mTabOverlap;
     private int mAddTabOverlap;
     private int mTabSliceWidth;
-    private int mTabPadding;
     private boolean mUseQuickControls;
 
     public TabBar(Activity activity, UiController controller, XLargeUi ui) {
@@ -122,14 +120,11 @@
         mGenericFavicon = res.getDrawable(R.drawable.app_web_browser_sm);
 
         updateTabs(mUiController.getTabs());
-
-        mVisibleTitleHeight = 1;
         mButtonWidth = -1;
         // tab dimensions
         mTabOverlap = (int) res.getDimension(R.dimen.tab_overlap);
         mAddTabOverlap = (int) res.getDimension(R.dimen.tab_addoverlap);
         mTabSliceWidth = (int) res.getDimension(R.dimen.tab_slice);
-        mTabPadding = (int) res.getDimension(R.dimen.tab_padding);
 
         mActiveShaderPaint.setStyle(Paint.Style.FILL);
         mActiveShaderPaint.setAntiAlias(true);
@@ -137,6 +132,10 @@
         mInactiveShaderPaint.setStyle(Paint.Style.FILL);
         mInactiveShaderPaint.setAntiAlias(true);
 
+        mFocusPaint.setStyle(Paint.Style.STROKE);
+        mFocusPaint.setStrokeWidth(res.getDimension(R.dimen.tab_focus_stroke));
+        mFocusPaint.setAntiAlias(true);
+        mFocusPaint.setColor(res.getColor(R.color.tabFocusHighlight));
     }
 
     void setUseQuickControls(boolean useQuickControls) {
@@ -198,14 +197,16 @@
             mUiController.openTabToHomePage();
         } else if (mTabs.getSelectedTab() == view) {
             if (mUseQuickControls) {
-                if (mUi.isFakeTitleBarShowing() && !isLoading()) {
-                    mUi.hideFakeTitleBar();
+                if (mUi.isTitleBarShowing() && !isLoading()) {
+                    mUi.stopEditingUrl();
+                    mUi.hideTitleBar();
                 } else {
                     mUi.stopWebViewScrolling();
-                    mUi.showFakeTitleBarAndEdit();
+                    mUi.showTitleBarAndEdit();
                 }
-            } else if (mUi.isFakeTitleBarShowing() && !isLoading()) {
-                mUi.hideFakeTitleBar();
+            } else if (mUi.isTitleBarShowing() && !isLoading()) {
+                mUi.stopEditingUrl();
+                mUi.hideTitleBar();
             } else {
                 showUrlBar();
             }
@@ -220,7 +221,7 @@
 
     private void showUrlBar() {
         mUi.stopWebViewScrolling();
-        mUi.showFakeTitleBar();
+        mUi.showTitleBar();
     }
 
     void showTitleBarIndicator(boolean show) {
@@ -251,22 +252,25 @@
 
     // callback after fake titlebar is hidden
     void onHideTitleBar() {
-        showTitleBarIndicator(mVisibleTitleHeight == 0);
         Tab tab = mTabControl.getCurrentTab();
-        tab.getWebView().requestFocus();
+        WebView w = tab.getWebView();
+        if (w != null) {
+            showTitleBarIndicator(w.getVisibleTitleHeight() == 0);
+        }
     }
 
     // webview scroll listener
 
     @Override
-    public void onScroll(int visibleTitleHeight) {
+    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
                 && !isLoading()) {
             if (visibleTitleHeight == 0) {
-                if (!showsTitleBarIndicator()) {
-                    mUi.hideFakeTitleBar();
+                if (!showsTitleBarIndicator()
+                        && (!mUi.isEditingUrl() || userInitiated)) {
+                    mUi.hideTitleBar();
                     showTitleBarIndicator(true);
                 }
             } else {
@@ -275,7 +279,6 @@
                 }
             }
         }
-        mVisibleTitleHeight = visibleTitleHeight;
     }
 
     @Override
@@ -316,6 +319,7 @@
         boolean mSelected;
         boolean mInLoad;
         Path mPath;
+        Path mFocusPath;
         int[] mWindowPos;
 
         /**
@@ -325,6 +329,7 @@
             super(context);
             setWillNotDraw(false);
             mPath = new Path();
+            mFocusPath = new Path();
             mWindowPos = new int[2];
             mTab = tab;
             setGravity(Gravity.CENTER_VERTICAL);
@@ -402,6 +407,8 @@
             lp.width = selected ? mTabWidthSelected : mTabWidthUnselected;
             lp.height =  LayoutParams.MATCH_PARENT;
             setLayoutParams(lp);
+            setFocusable(!selected);
+            postInvalidate();
         }
 
         void setDisplayTitle(String title) {
@@ -443,6 +450,7 @@
         protected void onLayout(boolean changed, int l, int t, int r, int b) {
             super.onLayout(changed, l, t, r, b);
             setTabPath(mPath, 0, 0, r - l, b - t);
+            setFocusPath(mFocusPath, 0, 0, r - l, b - t);
         }
 
         @Override
@@ -467,12 +475,14 @@
                     mInactiveShaderPaint.setShader(mInactiveShader);
                 }
             }
-
-            int state = canvas.save();
-            getLocationInWindow(mWindowPos);
-            Paint paint = mSelected ? mActiveShaderPaint : mInactiveShaderPaint;
-            drawClipped(canvas, paint, mPath, mWindowPos[0]);
-            canvas.restoreToCount(state);
+            // add some monkey protection
+            if ((mActiveShader != null) && (mInactiveShader != null)) {
+                int state = canvas.save();
+                getLocationInWindow(mWindowPos);
+                Paint paint = mSelected ? mActiveShaderPaint : mInactiveShaderPaint;
+                drawClipped(canvas, paint, mPath, mWindowPos[0]);
+                canvas.restoreToCount(state);
+            }
             super.dispatchDraw(canvas);
         }
 
@@ -482,6 +492,9 @@
             matrix.setTranslate(-left, 0.0f);
             (mSelected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix);
             canvas.drawPath(clipPath, paint);
+            if (isFocused()) {
+                canvas.drawPath(mFocusPath, mFocusPaint);
+            }
         }
 
         private void setTabPath(Path path, int l, int t, int r, int b) {
@@ -493,6 +506,14 @@
             path.close();
         }
 
+        private void setFocusPath(Path path, int l, int t, int r, int b) {
+            path.reset();
+            path.moveTo(l, b);
+            path.lineTo(l, t);
+            path.lineTo(r - mTabSliceWidth, t);
+            path.lineTo(r, b);
+        }
+
     }
 
     static Drawable createFaviconBackground(Context context) {
@@ -588,8 +609,7 @@
             WebView webview = tab.getWebView();
             if (webview != null) {
                 int h = webview.getVisibleTitleHeight();
-                mVisibleTitleHeight = h -1;
-                onScroll(h);
+                onScroll(h, true);
             }
         }
     }
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
index bdef82e..686416c 100644
--- a/src/com/android/browser/TitleBar.java
+++ b/src/com/android/browser/TitleBar.java
@@ -16,92 +16,65 @@
 
 package com.android.browser;
 
-import com.android.common.speech.LoggingEvents;
-
 import android.app.Activity;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
-import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
 import android.speech.RecognizerIntent;
 import android.text.SpannableString;
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.style.ImageSpan;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
 import android.view.ContextMenu;
 import android.view.LayoutInflater;
 import android.view.MenuInflater;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.widget.ImageButton;
 import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
 
 /**
  * This class represents a title bar for a particular "tab" or "window" in the
  * browser.
  */
-public class TitleBar extends TitleBarBase {
+public class TitleBar extends TitleBarBase implements OnFocusChangeListener,
+        OnClickListener {
 
     private Activity mActivity;
-    private UiController mController;
-    private TextView mTitle;
-    private ImageView mRtButton;
-    private Drawable mCircularProgress;
-    private ProgressBar mHorizontalProgress;
-    private ImageView mStopButton;
+    private ImageButton mBookmarkButton;
+    private PageProgressView mHorizontalProgress;
+    private ImageButton mStopButton;
     private Drawable mBookmarkDrawable;
     private Drawable mVoiceDrawable;
     private boolean mInLoad;
-    private View mTitleBg;
-    private MyHandler mHandler;
     private Intent mVoiceSearchIntent;
-    private boolean mInVoiceMode;
-    private Drawable mVoiceModeBackground;
-    private Drawable mNormalBackground;
-    private Drawable mLoadingBackground;
     private ImageSpan mArcsSpan;
-    private int mLeftMargin;
-    private int mRightMargin;
 
-    private static int LONG_PRESS = 1;
-
-    public TitleBar(Activity activity, UiController controller) {
-        super(activity);
-        mHandler = new MyHandler();
+    public TitleBar(Activity activity, UiController controller, PhoneUi ui) {
+        super(activity, controller, ui);
         LayoutInflater factory = LayoutInflater.from(activity);
         factory.inflate(R.layout.title_bar, this);
         mActivity = activity;
-        mController = controller;
 
-        mTitle = (TextView) findViewById(R.id.title);
-        mTitle.setCompoundDrawablePadding(5);
+        mUrlInput = (UrlInputView) findViewById(R.id.url_input);
+        mUrlInput.setCompoundDrawablePadding(5);
+        mUrlInput.setContainer(this);
+        mUrlInput.setSelectAllOnFocus(true);
+        mUrlInput.setController(mUiController);
+        mUrlInput.setUrlInputListener(this);
+        mUrlInput.setOnFocusChangeListener(this);
 
-        mTitleBg = findViewById(R.id.title_bg);
         mLockIcon = (ImageView) findViewById(R.id.lock);
         mFavicon = (ImageView) findViewById(R.id.favicon);
-        mStopButton = (ImageView) findViewById(R.id.stop);
+        mStopButton = (ImageButton) findViewById(R.id.stop);
+        mBookmarkButton = (ImageButton) findViewById(R.id.bookmark);
+        mStopButton.setOnClickListener(this);
+        mBookmarkButton.setOnClickListener(this);
 
-        mRtButton = (ImageView) findViewById(R.id.rt_btn);
-        Resources resources = activity.getResources();
-        mCircularProgress = resources.getDrawable(
-                com.android.internal.R.drawable.search_spinner);
-        DisplayMetrics metrics = resources.getDisplayMetrics();
-        mLeftMargin = (int) TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_DIP, 8f, metrics);
-        mRightMargin = (int) TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_DIP, 6f, metrics);
-        int iconDimension = (int) TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_DIP, 20f, metrics);
-        mCircularProgress.setBounds(0, 0, iconDimension, iconDimension);
-        mHorizontalProgress = (ProgressBar) findViewById(
+        mHorizontalProgress = (PageProgressView) findViewById(
                 R.id.progress_horizontal);
         mVoiceSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
         mVoiceSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
@@ -115,40 +88,18 @@
         PackageManager pm = activity.getPackageManager();
         ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
                 PackageManager.MATCH_DEFAULT_ONLY);
+        Resources resources = getResources();
         if (ri == null) {
             mVoiceSearchIntent = null;
         } else {
             mVoiceDrawable = resources.getDrawable(
                     android.R.drawable.ic_btn_speak_now);
         }
-        mBookmarkDrawable = mRtButton.getDrawable();
-        mVoiceModeBackground = resources.getDrawable(
-                R.drawable.title_voice);
-        mNormalBackground = mTitleBg.getBackground();
-        mLoadingBackground = resources.getDrawable(R.drawable.title_loading);
+        mBookmarkDrawable = mBookmarkButton.getDrawable();
         mArcsSpan = new ImageSpan(activity, R.drawable.arcs,
                 ImageSpan.ALIGN_BASELINE);
     }
 
-    private class MyHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == LONG_PRESS) {
-                // Prevent the normal action from happening by setting the title
-                // bar's state to false.
-                mTitleBg.setPressed(false);
-                // Need to call a special method on BrowserActivity for when the
-                // fake title bar is up, because its ViewGroup does not show a
-                // context menu.
-                // TODO:
-                // this test is not valid for all UIs; fix later
-                if (getParent() != null) {
-                    mActivity.openContextMenu(TitleBar.this);
-                }
-            }
-        }
-    };
-
     @Override
     public void createContextMenu(ContextMenu menu) {
         MenuInflater inflater = mActivity.getMenuInflater();
@@ -156,93 +107,6 @@
         mActivity.onCreateContextMenu(menu, this, null);
     }
 
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        ImageView button = mInLoad ? mStopButton : mRtButton;
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                // Make all touches hit either the textfield or the button,
-                // depending on which side of the right edge of the textfield
-                // they hit.
-                if ((int) event.getX() > mTitleBg.getRight()) {
-                    button.setPressed(true);
-                } else {
-                    mTitleBg.setPressed(true);
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                            LONG_PRESS),
-                            ViewConfiguration.getLongPressTimeout());
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                int slop = ViewConfiguration.get(mActivity)
-                        .getScaledTouchSlop();
-                if ((int) event.getY() > getHeight() + slop) {
-                    // We only trigger the actions in ACTION_UP if one or the
-                    // other is pressed.  Since the user moved off the title
-                    // bar, mark both as not pressed.
-                    mTitleBg.setPressed(false);
-                    button.setPressed(false);
-                    mHandler.removeMessages(LONG_PRESS);
-                    break;
-                }
-                int x = (int) event.getX();
-                int titleRight = mTitleBg.getRight();
-                if (mTitleBg.isPressed() && x > titleRight + slop) {
-                    mTitleBg.setPressed(false);
-                    mHandler.removeMessages(LONG_PRESS);
-                } else if (button.isPressed() && x < titleRight - slop) {
-                    button.setPressed(false);
-                }
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                button.setPressed(false);
-                mTitleBg.setPressed(false);
-                mHandler.removeMessages(LONG_PRESS);
-                break;
-            case MotionEvent.ACTION_UP:
-                if (button.isPressed()) {
-                    if (mInVoiceMode) {
-                        if (mController.getTabControl().getCurrentTab()
-                                .voiceSearchSourceIsGoogle()) {
-                            Intent i = new Intent(
-                                    LoggingEvents.ACTION_LOG_EVENT);
-                            i.putExtra(LoggingEvents.EXTRA_EVENT,
-                                    LoggingEvents.VoiceSearch.RETRY);
-                            mActivity.sendBroadcast(i);
-                        }
-                        mActivity.startActivity(mVoiceSearchIntent);
-                    } else if (mInLoad) {
-                        mController.stopLoading();
-                    } else {
-                        mController.bookmarkCurrentPage(
-                                AddBookmarkPage.DEFAULT_FOLDER_ID);
-                    }
-                    button.setPressed(false);
-                } else if (mTitleBg.isPressed()) {
-                    mHandler.removeMessages(LONG_PRESS);
-                    if (mInVoiceMode) {
-                        if (mController.getTabControl().getCurrentTab()
-                                .voiceSearchSourceIsGoogle()) {
-                            Intent i = new Intent(
-                                    LoggingEvents.ACTION_LOG_EVENT);
-                            i.putExtra(LoggingEvents.EXTRA_EVENT,
-                                    LoggingEvents.VoiceSearch.N_BEST_REVEAL);
-                            mActivity.sendBroadcast(i);
-                        }
-                        mController.showVoiceSearchResults(
-                                mTitle.getText().toString().trim());
-                    } else {
-                        mController.editUrl();
-                    }
-                    mTitleBg.setPressed(false);
-                }
-                break;
-            default:
-                break;
-        }
-        return true;
-    }
-
     /**
      * Change the TitleBar to or from voice mode.  If there is no package to
      * handle voice search, the TitleBar cannot be set to voice mode.
@@ -253,30 +117,22 @@
         mInVoiceMode = inVoiceMode && mVoiceSearchIntent != null;
         Drawable titleDrawable;
         if (mInVoiceMode) {
-            mRtButton.setImageDrawable(mVoiceDrawable);
-            titleDrawable = mVoiceModeBackground;
-            mTitle.setEllipsize(null);
-            mRtButton.setVisibility(View.VISIBLE);
+            mBookmarkButton.setImageDrawable(mVoiceDrawable);
+            mUrlInput.setEllipsize(null);
+            mBookmarkButton.setVisibility(View.VISIBLE);
             mStopButton.setVisibility(View.GONE);
-            mTitleBg.setBackgroundDrawable(titleDrawable);
-            mTitleBg.setPadding(mLeftMargin, mTitleBg.getPaddingTop(),
-                    mRightMargin, mTitleBg.getPaddingBottom());
         } else {
             if (mInLoad) {
-                titleDrawable = mLoadingBackground;
-                mRtButton.setVisibility(View.GONE);
+                mBookmarkButton.setVisibility(View.GONE);
                 mStopButton.setVisibility(View.VISIBLE);
             } else {
-                titleDrawable = mNormalBackground;
-                mRtButton.setVisibility(View.VISIBLE);
+                mBookmarkButton.setVisibility(View.VISIBLE);
                 mStopButton.setVisibility(View.GONE);
-                mRtButton.setImageDrawable(mBookmarkDrawable);
+                mBookmarkButton.setImageDrawable(mBookmarkDrawable);
             }
-            mTitle.setEllipsize(TextUtils.TruncateAt.END);
-            mTitleBg.setBackgroundDrawable(titleDrawable);
-            mTitleBg.setPadding(mLeftMargin, 0, mRightMargin, 0);
+            mUrlInput.setEllipsize(TextUtils.TruncateAt.END);
         }
-        mTitle.setSingleLine(!mInVoiceMode);
+        mUrlInput.setSingleLine(!mInVoiceMode);
     }
 
     /**
@@ -284,33 +140,21 @@
      */
     @Override
     void setProgress(int newProgress) {
-        if (newProgress >= mHorizontalProgress.getMax()) {
-            mTitle.setCompoundDrawables(null, null, null, null);
-            ((Animatable) mCircularProgress).stop();
-            mHorizontalProgress.setVisibility(View.INVISIBLE);
+        if (newProgress >= PROGRESS_MAX) {
+            mHorizontalProgress.setVisibility(View.GONE);
             if (!mInVoiceMode) {
-                mRtButton.setImageDrawable(mBookmarkDrawable);
-                mRtButton.setVisibility(View.VISIBLE);
+                mBookmarkButton.setImageDrawable(mBookmarkDrawable);
+                mBookmarkButton.setVisibility(View.VISIBLE);
                 mStopButton.setVisibility(View.GONE);
-                mTitleBg.setBackgroundDrawable(mNormalBackground);
-                mTitleBg.setPadding(mLeftMargin, 0, mRightMargin, 0);
             }
             mInLoad = false;
         } else {
-            mHorizontalProgress.setProgress(newProgress);
-            if (!mInLoad && getWindowToken() != null) {
-                // checking the window token lets us be sure that we
-                // are attached to a window before starting the animation,
-                // preventing a potential race condition
-                // (fix for bug http://b/2115736)
-                mTitle.setCompoundDrawables(null, null, mCircularProgress,
-                        null);
-                ((Animatable) mCircularProgress).start();
+            mHorizontalProgress.setProgress(newProgress * PageProgressView.MAX_PROGRESS
+                    / PROGRESS_MAX);
+            if (!mInLoad) {
                 mHorizontalProgress.setVisibility(View.VISIBLE);
                 if (!mInVoiceMode) {
-                    mTitleBg.setBackgroundDrawable(mLoadingBackground);
-                    mTitleBg.setPadding(mLeftMargin, 0, mRightMargin, 0);
-                    mRtButton.setVisibility(View.GONE);
+                    mBookmarkButton.setVisibility(View.GONE);
                     mStopButton.setVisibility(View.VISIBLE);
                 }
                 mInLoad = true;
@@ -326,7 +170,7 @@
     @Override
     void setDisplayTitle(String title) {
         if (title == null) {
-            mTitle.setText(R.string.new_tab);
+            mUrlInput.setText(R.string.new_tab);
         } else {
             if (mInVoiceMode) {
                 // Add two spaces.  The second one will be replaced with an
@@ -336,10 +180,32 @@
                 int end = spannable.length();
                 spannable.setSpan(mArcsSpan, end - 1, end,
                         Spanned.SPAN_MARK_POINT);
-                mTitle.setText(spannable);
+                mUrlInput.setText(spannable);
             } else {
-                mTitle.setText(title);
+                mUrlInput.setText(title);
             }
         }
     }
+
+    @Override
+    public void onFocusChange(View v, boolean hasFocus) {
+        if (v == mUrlInput && hasFocus) {
+            mActivity.closeOptionsMenu();
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mStopButton) {
+            mUiController.stopLoading();
+        } else if (v == mBookmarkButton) {
+            mUiController.bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID,
+                    true);
+        }
+    }
+
+    @Override
+    public void setCurrentUrlIsBookmark(boolean isBookmark) {
+        mBookmarkButton.setActivated(isBookmark);
+    }
 }
diff --git a/src/com/android/browser/TitleBarBase.java b/src/com/android/browser/TitleBarBase.java
index 024f83c..2b64aa3 100644
--- a/src/com/android/browser/TitleBarBase.java
+++ b/src/com/android/browser/TitleBarBase.java
@@ -16,29 +16,46 @@
 
 package com.android.browser;
 
+import com.android.browser.UrlInputView.UrlInputListener;
+
+import android.app.SearchManager;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.PaintDrawable;
+import android.os.Bundle;
+import android.speech.RecognizerResultsIntent;
+import android.view.Gravity;
 import android.view.View;
+import android.widget.AbsoluteLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
 /**
  * Base class for a title bar used by the browser.
  */
-public class TitleBarBase extends LinearLayout {
+public class TitleBarBase extends LinearLayout implements UrlInputListener {
+
+    protected static final int PROGRESS_MAX = 100;
+
     // These need to be set by the subclass.
     protected ImageView mFavicon;
     protected ImageView mLockIcon;
 
     protected Drawable mGenericFavicon;
+    protected UiController mUiController;
+    protected BaseUi mBaseUi;
+    protected UrlInputView mUrlInput;
+    protected boolean mInVoiceMode;
 
-    public TitleBarBase(Context context) {
+    public TitleBarBase(Context context, UiController controller, BaseUi ui) {
         super(context, null);
+        mUiController = controller;
+        mBaseUi = ui;
         mGenericFavicon = context.getResources().getDrawable(
                 R.drawable.app_web_browser_sm);
     }
@@ -76,4 +93,85 @@
     /* package */ void setInVoiceMode(boolean inVoiceMode) {}
 
     /* package */ void setIncognitoMode(boolean incognito) {}
+
+    void setTitleGravity(int gravity) {
+        int newTop = 0;
+        if (gravity != Gravity.NO_GRAVITY) {
+            View parent = (View) getParent();
+            if (parent != null) {
+                if (gravity == Gravity.TOP) {
+                    newTop = parent.getScrollY();
+                } else if (gravity == Gravity.BOTTOM) {
+                    newTop = parent.getScrollY() + parent.getHeight() - getHeight();
+                }
+            }
+        }
+        AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) getLayoutParams();
+        if (lp != null) {
+            lp.y = newTop;
+            setLayoutParams(lp);
+        }
+    }
+
+    // UrlInputListener implementation
+
+    /**
+     * callback from suggestion dropdown
+     * user selected a suggestion
+     */
+    @Override
+    public void onAction(String text, String extra, String source) {
+        mUiController.getCurrentTopWebView().requestFocus();
+        mBaseUi.hideTitleBar();
+        Intent i = new Intent();
+        String action = null;
+        if (UrlInputView.VOICE.equals(source)) {
+            action = RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS;
+            source = null;
+        } else {
+            action = Intent.ACTION_SEARCH;
+        }
+        i.setAction(action);
+        i.putExtra(SearchManager.QUERY, text);
+        if (extra != null) {
+            i.putExtra(SearchManager.EXTRA_DATA_KEY, extra);
+        }
+        if (source != null) {
+            Bundle appData = new Bundle();
+            appData.putString(com.android.common.Search.SOURCE, source);
+            i.putExtra(SearchManager.APP_DATA, appData);
+        }
+        mUiController.handleNewIntent(i);
+        setDisplayTitle(text);
+    }
+
+    @Override
+    public void onDismiss() {
+        final Tab currentTab = mBaseUi.getActiveTab();
+        mBaseUi.hideTitleBar();
+        post(new Runnable() {
+            public void run() {
+                clearFocus();
+                if ((currentTab != null) && !mInVoiceMode) {
+                    setDisplayTitle(currentTab.getUrl());
+                }
+            }
+        });
+    }
+
+    /**
+     * callback from the suggestion dropdown
+     * copy text to input field and stay in edit mode
+     */
+    @Override
+    public void onCopySuggestion(String text) {
+        mUrlInput.setText(text, true);
+        if (text != null) {
+            mUrlInput.setSelection(text.length());
+        }
+    }
+
+    public void setCurrentUrlIsBookmark(boolean isBookmark) {
+    }
+
 }
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
index 0dcece6..c732da8 100644
--- a/src/com/android/browser/TitleBarXLarge.java
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -16,26 +16,26 @@
 
 package com.android.browser;
 
-import com.android.browser.UrlInputView.UrlInputListener;
 import com.android.browser.search.SearchEngine;
 
 import android.app.Activity;
-import android.app.SearchManager;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.speech.RecognizerResultsIntent;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
 import android.webkit.WebView;
+import android.widget.AbsoluteLayout;
+import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 
@@ -45,12 +45,9 @@
  * tabbed title bar for xlarge screen browser
  */
 public class TitleBarXLarge extends TitleBarBase
-    implements UrlInputListener, OnClickListener, OnFocusChangeListener,
+        implements OnClickListener, OnFocusChangeListener,
     TextWatcher {
 
-    private static final int PROGRESS_MAX = 100;
-
-    private UiController mUiController;
     private XLargeUi mUi;
 
     private Drawable mStopDrawable;
@@ -70,19 +67,15 @@
     private View mVoiceSearch;
     private View mVoiceSearchIndicator;
     private PageProgressView mProgressView;
-    private UrlInputView mUrlInput;
     private Drawable mFocusDrawable;
     private Drawable mUnfocusDrawable;
-    private boolean mInVoiceMode;
 
     private boolean mInLoad;
-    private boolean mEditable;
     private boolean mUseQuickControls;
 
     public TitleBarXLarge(Activity activity, UiController controller,
             XLargeUi ui) {
-        super(activity);
-        mUiController = controller;
+        super(activity, controller, ui);
         mUi = ui;
         Resources resources = activity.getResources();
         mStopDrawable = resources.getDrawable(R.drawable.ic_stop_holo_dark);
@@ -95,6 +88,18 @@
         mInVoiceMode = false;
     }
 
+    @Override
+    void setTitleGravity(int gravity) {
+        if (mUseQuickControls) {
+            FrameLayout.LayoutParams lp =
+                    (FrameLayout.LayoutParams) getLayoutParams();
+            lp.gravity = gravity;
+            setLayoutParams(lp);
+        } else {
+            super.setTitleGravity(gravity);
+        }
+    }
+
     private void initLayout(Context context) {
         LayoutInflater factory = LayoutInflater.from(context);
         factory.inflate(R.layout.url_bar, this);
@@ -126,14 +131,13 @@
         mGoButton.setOnClickListener(this);
         mClearButton.setOnClickListener(this);
         mVoiceSearch.setOnClickListener(this);
-        mUrlContainer.setOnClickListener(this);
         mUrlInput.setUrlInputListener(this);
         mUrlInput.setContainer(mUrlContainer);
         mUrlInput.setController(mUiController);
         mUrlInput.setOnFocusChangeListener(this);
         mUrlInput.setSelectAllOnFocus(true);
         mUrlInput.addTextChangedListener(this);
-        setEditMode(false);
+        setFocusState(false);
     }
 
     void updateNavigationState(Tab tab) {
@@ -148,19 +152,21 @@
         }
     }
 
-    public void setEditable(boolean editable) {
-        mEditable = editable;
-        mUrlInput.setFocusable(mEditable);
-        if (!mEditable) {
-            mUrlInput.setOnClickListener(this);
+    private ViewGroup.LayoutParams makeLayoutParams() {
+        if (mUseQuickControls) {
+            return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+                    LayoutParams.WRAP_CONTENT);
         } else {
-            mUrlContainer.setOnClickListener(null);
+            return new AbsoluteLayout.LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT,
+                    0, 0);
         }
     }
 
     void setUseQuickControls(boolean useQuickControls) {
         mUseQuickControls = useQuickControls;
         mUrlInput.setUseQuickControls(mUseQuickControls);
+        setLayoutParams(makeLayoutParams());
         if (mUseQuickControls) {
             mBackButton.setVisibility(View.GONE);
             mForwardButton.setVisibility(View.GONE);
@@ -184,30 +190,34 @@
 
     @Override
     public void onFocusChange(View view, boolean hasFocus) {
-        if (!mEditable && hasFocus) {
-            mUi.editUrl(false);
-        } else {
-            if (hasFocus) {
-                setEditMode(hasFocus);
-            } else {
-                mUrlInput.stopEditing();
-            }
+        // if losing focus and not in touch mode, leave as is
+        if (hasFocus || view.isInTouchMode() || mUrlInput.needsUpdate()) {
+            setFocusState(hasFocus);
+            mUrlContainer.setBackgroundDrawable(hasFocus
+                    ? mFocusDrawable : mUnfocusDrawable);
         }
-        mUrlContainer.setBackgroundDrawable(hasFocus
-                ? mFocusDrawable : mUnfocusDrawable);
+        if (hasFocus) {
+            mUrlInput.forceIme();
+            if (mInVoiceMode) {
+                mUrlInput.forceFilter();
+            }
+        } else if (!mUrlInput.needsUpdate()) {
+            mUrlInput.dismissDropDown();
+            mUrlInput.hideIME();
+        }
+        mUrlInput.clearNeedsUpdate();
     }
 
+    @Override
     public void setCurrentUrlIsBookmark(boolean isBookmark) {
         mStar.setActivated(isBookmark);
     }
 
     /**
      * called from the Ui when the user wants to edit
-     * Note: only the fake titlebar will get this callback
-     * independent of which input field started the edit mode
      * @param clearInput clear the input field
      */
-    void onEditUrl(boolean clearInput) {
+    void startEditingUrl(boolean clearInput) {
         // editing takes preference of progress
         mContainer.setVisibility(View.VISIBLE);
         if (mUseQuickControls) {
@@ -227,21 +237,19 @@
         return mUrlInput.hasFocus();
     }
 
+    void stopEditingUrl() {
+        mUrlInput.clearFocus();
+    }
+
     @Override
     public void onClick(View v) {
-        if (mUrlInput == v) {
-            mUi.editUrl(false);
-        } else if (mUrlContainer == v) {
-            if (!mUrlInput.hasFocus()) {
-                mUi.editUrl(false);
-            }
-        } else if (mBackButton == v) {
+        if (mBackButton == v) {
             mUiController.getCurrentTopWebView().goBack();
         } else if (mForwardButton == v) {
             mUiController.getCurrentTopWebView().goForward();
         } else if (mStar == v) {
             mUiController.bookmarkCurrentPage(
-                    AddBookmarkPage.DEFAULT_FOLDER_ID);
+                    AddBookmarkPage.DEFAULT_FOLDER_ID, true);
         } else if (mAllButton == v) {
             mUiController.bookmarksOrHistoryPicker(false);
         } else if (mSearchButton == v) {
@@ -260,83 +268,21 @@
         }
     }
 
-    int getHeightWithoutProgress() {
-        return mContainer.getHeight();
-    }
-
     @Override
     void setFavicon(Bitmap icon) { }
 
     private void clearOrClose() {
         if (TextUtils.isEmpty(mUrlInput.getText())) {
             // close
-            mUrlInput.stopEditing();
+            mUrlInput.clearFocus();
         } else {
             // clear
             mUrlInput.setText("");
         }
     }
 
-    // UrlInputListener implementation
-
-    /**
-     * callback from suggestion dropdown
-     * user selected a suggestion
-     */
-    @Override
-    public void onAction(String text, String extra, String source) {
-        mUiController.getCurrentTopWebView().requestFocus();
-        mUi.hideFakeTitleBar();
-        Intent i = new Intent();
-        String action = null;
-        if (UrlInputView.VOICE.equals(source)) {
-            action = RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS;
-            source = null;
-        } else {
-            action = Intent.ACTION_SEARCH;
-        }
-        i.setAction(action);
-        i.putExtra(SearchManager.QUERY, text);
-        if (extra != null) {
-            i.putExtra(SearchManager.EXTRA_DATA_KEY, extra);
-        }
-        if (source != null) {
-            Bundle appData = new Bundle();
-            appData.putString(com.android.common.Search.SOURCE, source);
-            i.putExtra(SearchManager.APP_DATA, appData);
-        }
-        mUiController.handleNewIntent(i);
-        setDisplayTitle(text);
-    }
-
-    @Override
-    public void onDismiss() {
-        final Tab currentTab = mUi.getActiveTab();
-        mUi.hideFakeTitleBar();
-        post(new Runnable() {
-            public void run() {
-                TitleBarXLarge.this.clearFocus();
-                if ((currentTab != null) && !mInVoiceMode) {
-                    setDisplayTitle(currentTab.getUrl());
-                }
-            }
-        });
-    }
-
-    /**
-     * callback from the suggestion dropdown
-     * copy text to input field and stay in edit mode
-     */
-    @Override
-    public void onEdit(String text) {
-        mUrlInput.setText(text, true);
-        if (text != null) {
-            mUrlInput.setSelection(text.length());
-        }
-    }
-
-    void setEditMode(boolean edit) {
-        if (edit) {
+    private void setFocusState(boolean focus) {
+        if (focus) {
             mUrlInput.setDropDownWidth(mUrlContainer.getWidth());
             mUrlInput.setDropDownHorizontalOffset(-mUrlInput.getLeft());
             mSearchButton.setVisibility(View.GONE);
@@ -450,6 +396,7 @@
         mUrlInput.setVoiceResults(voiceResults);
         mVoiceSearchIndicator.setVisibility(mInVoiceMode
                 ? View.VISIBLE : View.GONE);
+        mWebIcon.setVisibility(mInVoiceMode ? View.GONE : View.VISIBLE);
     }
 
     @Override
@@ -457,4 +404,31 @@
         mUrlInput.setIncognitoMode(incognito);
     }
 
+    @Override
+    public View focusSearch(View focused, int dir) {
+        if (FOCUS_DOWN == dir && hasFocus()) {
+            return getCurrentWebView();
+        }
+        return super.focusSearch(focused, dir);
+    }
+
+    @Override
+    public boolean dispatchKeyEventPreIme(KeyEvent evt) {
+        if (evt.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            // catch back key in order to do slightly more cleanup than usual
+            mUrlInput.clearFocus();
+            return true;
+        }
+        return super.dispatchKeyEventPreIme(evt);
+    }
+
+    private WebView getCurrentWebView() {
+        Tab t = mUi.getActiveTab();
+        if (t != null) {
+            return t.getWebView();
+        } else {
+            return null;
+        }
+    }
+
 }
diff --git a/src/com/android/browser/UI.java b/src/com/android/browser/UI.java
index 8de2b19..34dcaee 100644
--- a/src/com/android/browser/UI.java
+++ b/src/com/android/browser/UI.java
@@ -118,6 +118,8 @@
 
     void showMaxTabsWarning();
 
+    void editUrl(boolean clearInput);
+
     boolean dispatchKey(int code, KeyEvent event);
 
 }
diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java
index ae38cff..6075d36 100644
--- a/src/com/android/browser/UiController.java
+++ b/src/com/android/browser/UiController.java
@@ -49,10 +49,12 @@
 
     void stopLoading();
 
-    void bookmarkCurrentPage(long folderId);
+    void bookmarkCurrentPage(long folderId, boolean canBeAnEdit);
 
     void bookmarksOrHistoryPicker(boolean openHistory);
 
+    void startSearch(String url);
+
     void startVoiceSearch();
 
     void showVoiceSearchResults(String title);
diff --git a/src/com/android/browser/UploadHandler.java b/src/com/android/browser/UploadHandler.java
index d9b387f..5947e4a 100644
--- a/src/com/android/browser/UploadHandler.java
+++ b/src/com/android/browser/UploadHandler.java
@@ -17,11 +17,13 @@
 package com.android.browser;
 
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Environment;
 import android.provider.MediaStore;
 import android.webkit.ValueCallback;
+import android.widget.Toast;
 
 import java.io.File;
 import java.util.Vector;
@@ -37,6 +39,9 @@
     private ValueCallback<Uri> mUploadMessage;
     private String mCameraFilePath;
 
+    private boolean mHandled;
+    private boolean mCaughtActivityNotFoundException;
+
     private Controller mController;
 
     public UploadHandler(Controller controller) {
@@ -47,7 +52,19 @@
         return mCameraFilePath;
     }
 
+    boolean handled() {
+        return mHandled;
+    }
+
     void onResult(int resultCode, Intent intent) {
+
+        if (resultCode == Activity.RESULT_CANCELED && mCaughtActivityNotFoundException) {
+            // Couldn't resolve an activity, we are going to try again so skip
+            // this result.
+            mCaughtActivityNotFoundException = false;
+            return;
+        }
+
         Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
                 : intent.getData();
 
@@ -69,6 +86,8 @@
         }
 
         mUploadMessage.onReceiveValue(result);
+        mHandled = true;
+        mCaughtActivityNotFoundException = false;
     }
 
     void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
@@ -82,14 +101,10 @@
         final String mediaSourceValueCamcorder = "camcorder";
         final String mediaSourceValueMicrophone = "microphone";
 
-        // media source can be 'filesystem' or 'camera' or 'camcorder' or 'microphone'.
+        // According to the spec, media source can be 'filesystem' or 'camera' or 'camcorder'
+        // or 'microphone'.
         String mediaSource = "";
 
-        // We add the camera intent if there was no accept type (or '*/*' or 'image/*').
-        boolean addCameraIntent = true;
-        // We add the camcorder intent if there was no accept type (or '*/*' or 'video/*').
-        boolean addCamcorderIntent = true;
-
         if (mUploadMessage != null) {
             // Already a file picker operation in progress.
             return;
@@ -111,15 +126,122 @@
             }
         }
 
-        // This intent will display the standard OPENABLE file picker.
+        //Ensure it is not still set from a previous upload.
+        mCameraFilePath = null;
+
+        if (mimeType.equals(imageMimeType)) {
+            if (mediaSource.equals(mediaSourceValueCamera)) {
+                // Specified 'image/*' and requested the camera, so go ahead and launch the
+                // camera directly.
+                startActivity(createCameraIntent());
+                return;
+            } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+                // Specified 'image/*' and requested the filesystem, so go ahead and launch an
+                // OPENABLE intent.
+                startActivity(createOpenableIntent(imageMimeType));
+                return;
+            } else {
+                // Specified just 'image/*', so launch an intent for both the Camera and image/*
+                // OPENABLE.
+                Intent chooser = createChooserIntent(createCameraIntent());
+                chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(imageMimeType));
+                startActivity(chooser);
+                return;
+            }
+        } else if (mimeType.equals(videoMimeType)) {
+            if (mediaSource.equals(mediaSourceValueCamcorder)) {
+                // Specified 'video/*' and requested the camcorder, so go ahead and launch the
+                // camcorder directly.
+                startActivity(createCamcorderIntent());
+                return;
+            } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+                // Specified 'video/*' and requested the filesystem, so go ahead and launch an
+                // an OPENABLE intent.
+                startActivity(createOpenableIntent(videoMimeType));
+                return;
+            } else {
+                // Specified just 'video/*', so go ahead and launch an intent for both camcorder and
+                // video/* OPENABLE.
+                Intent chooser = createChooserIntent(createCamcorderIntent());
+                chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(videoMimeType));
+                startActivity(chooser);
+                return;
+            }
+        } else if (mimeType.equals(audioMimeType)) {
+            if (mediaSource.equals(mediaSourceValueMicrophone)) {
+                // Specified 'audio/*' and requested microphone, so go ahead and launch the sound
+                // recorder.
+                startActivity(createSoundRecorderIntent());
+                return;
+            } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+                // Specified 'audio/*' and requested filesystem, so go ahead and launch an
+                // OPENABLE intent.
+                startActivity(createOpenableIntent(audioMimeType));
+                return;
+            } else {
+                // Specified just 'audio/*', so go ahead and launch an intent for both the sound
+                // recorder and audio/* OPENABLE.
+                Intent chooser = createChooserIntent(createSoundRecorderIntent());
+                chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(audioMimeType));
+                startActivity(chooser);
+                return;
+            }
+        }
+
+        // No special handling based on the accept type was necessary, so trigger the default
+        // file upload chooser.
+        startActivity(createDefaultOpenableIntent());
+    }
+
+    private void startActivity(Intent intent) {
+        try {
+            mController.getActivity().startActivityForResult(intent, Controller.FILE_SELECTED);
+        } catch (ActivityNotFoundException e) {
+            // No installed app was able to handle the intent that
+            // we sent, so fallback to the default file upload control.
+            try {
+                mCaughtActivityNotFoundException = true;
+                mController.getActivity().startActivityForResult(createDefaultOpenableIntent(),
+                        Controller.FILE_SELECTED);
+            } catch (ActivityNotFoundException e2) {
+                // Nothing can return us a file, so file upload is effectively disabled.
+                Toast.makeText(mController.getActivity(), R.string.uploads_disabled,
+                        Toast.LENGTH_LONG).show();
+            }
+        }
+    }
+
+    private Intent createDefaultOpenableIntent() {
+        // Create and return a chooser with the default OPENABLE
+        // actions including the camera, camcorder and sound
+        // recorder where available.
         Intent i = new Intent(Intent.ACTION_GET_CONTENT);
         i.addCategory(Intent.CATEGORY_OPENABLE);
+        i.setType("*/*");
 
-        // Create an intent to add to the standard file picker that will
-        // capture an image from the camera. We'll combine this intent with
-        // the standard OPENABLE picker unless the web developer specifically
-        // requested the camera or gallery be opened by passing a parameter
-        // in the accept type.
+        Intent chooser = createChooserIntent(createCameraIntent(), createCamcorderIntent(),
+                createSoundRecorderIntent());
+        chooser.putExtra(Intent.EXTRA_INTENT, i);
+        return chooser;
+    }
+
+    private Intent createChooserIntent(Intent... intents) {
+        Intent chooser = new Intent(Intent.ACTION_CHOOSER);
+        chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents);
+        chooser.putExtra(Intent.EXTRA_TITLE,
+                mController.getActivity().getResources()
+                        .getString(R.string.choose_upload));
+        return chooser;
+    }
+
+    private Intent createOpenableIntent(String type) {
+        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
+        i.addCategory(Intent.CATEGORY_OPENABLE);
+        i.setType(type);
+        return i;
+    }
+
+    private Intent createCameraIntent() {
         Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
         File externalDataDir = Environment.getExternalStoragePublicDirectory(
                 Environment.DIRECTORY_DCIM);
@@ -129,86 +251,15 @@
         mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator +
                 System.currentTimeMillis() + ".jpg";
         cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath)));
-
-        Intent camcorderIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
-
-        Intent soundRecIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
-
-        if (mimeType.equals(imageMimeType)) {
-            i.setType(imageMimeType);
-            addCamcorderIntent = false;
-            if (mediaSource.equals(mediaSourceValueCamera)) {
-                // Specified 'image/*' and requested the camera, so go ahead and launch the camera
-                // directly.
-                startActivity(cameraIntent);
-                return;
-            } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
-                // Specified filesytem as the source, so don't want to consider the camera.
-                addCameraIntent = false;
-            }
-        } else if (mimeType.equals(videoMimeType)) {
-            i.setType(videoMimeType);
-            addCameraIntent = false;
-            // The camcorder saves it's own file and returns it to us in the intent, so
-            // we don't need to generate one here.
-            mCameraFilePath = null;
-
-            if (mediaSource.equals(mediaSourceValueCamcorder)) {
-                // Specified 'video/*' and requested the camcorder, so go ahead and launch the
-                // camcorder directly.
-                startActivity(camcorderIntent);
-                return;
-            } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
-                // Specified filesystem as the source, so don't want to consider the camcorder.
-                addCamcorderIntent = false;
-            }
-        } else if (mimeType.equals(audioMimeType)) {
-            i.setType(audioMimeType);
-            addCameraIntent = false;
-            addCamcorderIntent = false;
-            if (mediaSource.equals(mediaSourceValueMicrophone)) {
-                // Specified 'audio/*' and requested microphone, so go ahead and launch the sound
-                // recorder.
-                startActivity(soundRecIntent);
-                return;
-            }
-            // On a default system, there is no single option to open an audio "gallery". Both the
-            // sound recorder and music browser respond to the OPENABLE/audio/* intent unlike the
-            // image/* and video/* OPENABLE intents where the image / video gallery are the only
-            // respondants (and so the user is not prompted by default).
-        } else {
-            i.setType("*/*");
-        }
-
-        // Combine the chooser and the extra choices (like camera or camcorder)
-        Intent chooser = new Intent(Intent.ACTION_CHOOSER);
-        chooser.putExtra(Intent.EXTRA_INTENT, i);
-
-        Vector<Intent> extraInitialIntents = new Vector<Intent>(0);
-
-        if (addCameraIntent) {
-            extraInitialIntents.add(cameraIntent);
-        }
-
-        if (addCamcorderIntent) {
-            extraInitialIntents.add(camcorderIntent);
-        }
-
-        if (extraInitialIntents.size() > 0) {
-            Intent[] extraIntents = new Intent[extraInitialIntents.size()];
-            chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS,
-                    extraInitialIntents.toArray(extraIntents));
-        }
-
-        chooser.putExtra(Intent.EXTRA_TITLE,
-                mController.getActivity().getResources()
-                        .getString(R.string.choose_upload));
-        startActivity(chooser);
+        return cameraIntent;
     }
 
-    private void startActivity(Intent intent) {
-        mController.getActivity().startActivityForResult(intent,
-                Controller.FILE_SELECTED);
+    private Intent createCamcorderIntent() {
+        return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+    }
+
+    private Intent createSoundRecorderIntent() {
+        return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
     }
 
 }
diff --git a/src/com/android/browser/UrlHandler.java b/src/com/android/browser/UrlHandler.java
index f1d1c4c..03bab9b 100644
--- a/src/com/android/browser/UrlHandler.java
+++ b/src/com/android/browser/UrlHandler.java
@@ -19,15 +19,18 @@
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Bundle;
 import android.util.Log;
 import android.webkit.WebView;
 
 import java.net.URISyntaxException;
+import java.util.List;
+import java.util.regex.Matcher;
 
 /**
  *
@@ -153,6 +156,13 @@
       // security (only access to BROWSABLE activities).
       intent.addCategory(Intent.CATEGORY_BROWSABLE);
       intent.setComponent(null);
+      // Make sure webkit can handle it internally before checking for specialized
+      // handlers. If webkit can't handle it internally, we need to call
+      // startActivityIfNeeded
+      Matcher m = UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url);
+      if (m.matches() && !isSpecializedHandlerAvailable(intent)) {
+          return false;
+      }
       try {
           if (mActivity.startActivityIfNeeded(intent, -1)) {
               // before leaving BrowserActivity, close the empty child tab.
@@ -170,6 +180,33 @@
       return false;
     }
 
+    /**
+     * Search for intent handlers that are specific to this URL
+     * aka, specialized apps like google maps or youtube
+     */
+    private boolean isSpecializedHandlerAvailable(Intent intent) {
+        PackageManager pm = mActivity.getPackageManager();
+          List<ResolveInfo> handlers = pm.queryIntentActivities(intent,
+                  PackageManager.GET_RESOLVED_FILTER);
+          if (handlers == null || handlers.size() == 0) {
+              return false;
+          }
+          for (ResolveInfo resolveInfo : handlers) {
+              IntentFilter filter = resolveInfo.filter;
+              if (filter == null) {
+                  // No intent filter matches this intent?
+                  // Error on the side of staying in the browser, ignore
+                  continue;
+              }
+              if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) {
+                  // Generic handler, skip
+                  continue;
+              }
+              return true;
+          }
+          return false;
+    }
+
     // In case a physical keyboard is attached, handle clicks with the menu key
     // depressed by opening in a new tab
     boolean handleMenuClick(Tab tab, String url) {
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
index 23e412d..2ec2111 100644
--- a/src/com/android/browser/UrlInputView.java
+++ b/src/com/android/browser/UrlInputView.java
@@ -29,7 +29,6 @@
 import android.util.Patterns;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.View.OnFocusChangeListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
@@ -44,7 +43,7 @@
  * handling suggestions
  */
 public class UrlInputView extends AutoCompleteTextView
-        implements OnFocusChangeListener, OnEditorActionListener,
+        implements OnEditorActionListener,
         CompletionListener, OnItemClickListener {
 
 
@@ -55,12 +54,11 @@
     private UrlInputListener   mListener;
     private InputMethodManager mInputManager;
     private SuggestionsAdapter mAdapter;
-    private OnFocusChangeListener mWrappedFocusListener;
     private View mContainer;
     private boolean mLandscape;
-    private boolean mInVoiceMode;
     private boolean mIncognitoMode;
     private int mVOffset;
+    private boolean mNeedsUpdate;
 
     public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
@@ -80,7 +78,6 @@
     private void init(Context ctx) {
         mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
         setOnEditorActionListener(this);
-        super.setOnFocusChangeListener(this);
         mAdapter = new SuggestionsAdapter(ctx, this);
         setAdapter(mAdapter);
         setSelectAllOnFocus(true);
@@ -88,6 +85,21 @@
         setThreshold(1);
         setOnItemClickListener(this);
         mVOffset = 0;
+        mNeedsUpdate = false;
+    }
+
+    /**
+     * check if focus change requires a title bar update
+     */
+    boolean needsUpdate() {
+        return mNeedsUpdate;
+    }
+
+    /**
+     * clear the focus change needs title bar update flag
+     */
+    void clearNeedsUpdate() {
+        mNeedsUpdate = false;
     }
 
     void setController(UiController controller) {
@@ -107,9 +119,12 @@
         mContainer = container;
     }
 
+    public void setUrlInputListener(UrlInputListener listener) {
+        mListener = listener;
+    }
+
     void setVoiceResults(List<String> voiceResults) {
         mAdapter.setVoiceResults(voiceResults);
-        mInVoiceMode = (voiceResults != null);
     }
 
     @Override
@@ -137,7 +152,7 @@
     }
 
     private void setupDropDown() {
-        int width = mContainer.getWidth();
+        int width = mContainer != null ? mContainer.getWidth() : getWidth();
         if (width != getDropDownWidth()) {
             setDropDownWidth(width);
         }
@@ -148,44 +163,28 @@
     }
 
     @Override
-    public void setOnFocusChangeListener(OnFocusChangeListener focusListener) {
-        mWrappedFocusListener = focusListener;
-    }
-
-    @Override
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
         finishInput(getText().toString(), null, TYPED);
         return true;
     }
 
-    @Override
-    public void onFocusChange(View v, boolean hasFocus) {
-        if (hasFocus) {
-            forceIme();
-            if (mInVoiceMode) {
-                performFiltering(getText().toString(), 0);
-                showDropDown();
-            }
-        }
-        if (mWrappedFocusListener != null) {
-            mWrappedFocusListener.onFocusChange(v, hasFocus);
-        }
+    void forceFilter() {
+        performFiltering(getText().toString(), 0);
+        showDropDown();
     }
 
-    void stopEditing() {
-        finishInput(null, null, null);
-    }
-
-    public void setUrlInputListener(UrlInputListener listener) {
-        mListener = listener;
-    }
-
-    public void forceIme() {
+    void forceIme() {
+        mInputManager.focusIn(this);
         mInputManager.showSoftInput(this, 0);
     }
 
+    void hideIME() {
+        mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
+    }
+
     private void finishInput(String url, String extra, String source) {
-        this.dismissDropDown();
+        mNeedsUpdate = true;
+        dismissDropDown();
         mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
         if (TextUtils.isEmpty(url)) {
             mListener.onDismiss();
@@ -221,7 +220,7 @@
 
     @Override
     public void onSearch(String search) {
-        mListener.onEdit(search);
+        mListener.onCopySuggestion(search);
     }
 
     @Override
@@ -231,16 +230,6 @@
     }
 
     @Override
-    public boolean onKeyPreIme(int keyCode, KeyEvent evt) {
-        if (keyCode == KeyEvent.KEYCODE_BACK) {
-            // catch back key in order to do slightly more cleanup than usual
-            finishInput(null, null, null);
-            return true;
-        }
-        return super.onKeyPreIme(keyCode, evt);
-    }
-
-    @Override
     public void onItemClick(
             AdapterView<?> parent, View view, int position, long id) {
         SuggestItem item = mAdapter.getItem(position);
@@ -254,7 +243,7 @@
 
         public void onAction(String text, String extra, String source);
 
-        public void onEdit(String text);
+        public void onCopySuggestion(String text);
 
     }
 
@@ -263,4 +252,13 @@
         mAdapter.setIncognitoMode(mIncognitoMode);
     }
 
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent evt) {
+        if (keyCode == KeyEvent.KEYCODE_ESCAPE && !isInTouchMode()) {
+            finishInput(null, null, null);
+            return true;
+        }
+        return super.onKeyDown(keyCode, evt);
+    }
+
 }
diff --git a/src/com/android/browser/WallpaperHandler.java b/src/com/android/browser/WallpaperHandler.java
index 0c88a50..2cb223a 100644
--- a/src/com/android/browser/WallpaperHandler.java
+++ b/src/com/android/browser/WallpaperHandler.java
@@ -21,12 +21,14 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
 import android.view.MenuItem;
 import android.view.MenuItem.OnMenuItemClickListener;
 
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
@@ -41,6 +43,9 @@
 
 
     private static final String LOGTAG = "WallpaperHandler";
+    // This should be large enough for BitmapFactory to decode the header so
+    // that we can mark and reset the input stream to avoid duplicate network i/o
+    private static final int BUFFER_SIZE = 128 * 1024;
 
     private Context mContext;
     private URL mUrl;
@@ -82,8 +87,9 @@
 
     @Override
     public void run() {
-        Drawable oldWallpaper =
-                WallpaperManager.getInstance(mContext).getDrawable();
+        WallpaperManager wm = WallpaperManager.getInstance(mContext);
+        Drawable oldWallpaper = wm.getDrawable();
+        InputStream inputstream = null;
         try {
             // TODO: This will cause the resource to be downloaded again, when
             // we should in most cases be able to grab it from the cache. To fix
@@ -91,15 +97,59 @@
             // version and instead open an input stream on that. This pattern
             // could also be used in the download manager where the same problem
             // exists.
-            InputStream inputstream = mUrl.openStream();
+            inputstream = mUrl.openStream();
             if (inputstream != null) {
-                WallpaperManager.getInstance(mContext).setStream(inputstream);
+                if (!inputstream.markSupported()) {
+                    inputstream = new BufferedInputStream(inputstream, BUFFER_SIZE);
+                }
+                inputstream.mark(BUFFER_SIZE);
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                options.inJustDecodeBounds = true;
+                // We give decodeStream a wrapped input stream so it doesn't
+                // mess with our mark (currently it sets a mark of 1024)
+                BitmapFactory.decodeStream(
+                        new BufferedInputStream(inputstream), null, options);
+                int maxWidth = wm.getDesiredMinimumWidth();
+                int maxHeight = wm.getDesiredMinimumHeight();
+                // Give maxWidth and maxHeight some leeway
+                maxWidth *= 1.25;
+                maxHeight *= 1.25;
+                int bmWidth = options.outWidth;
+                int bmHeight = options.outHeight;
+
+                int scale = 1;
+                while (bmWidth > maxWidth || bmHeight > maxWidth) {
+                    scale <<= 1;
+                    bmWidth >>= 1;
+                    bmHeight >>= 1;
+                }
+                options.inJustDecodeBounds = false;
+                options.inSampleSize = scale;
+                try {
+                    inputstream.reset();
+                } catch (IOException e) {
+                    // BitmapFactory read more than we could buffer
+                    // Re-open the stream
+                    inputstream.close();
+                    inputstream = mUrl.openStream();
+                }
+                Bitmap scaledWallpaper = BitmapFactory.decodeStream(inputstream,
+                        null, options);
+                wm.setBitmap(scaledWallpaper);
             }
         } catch (IOException e) {
             Log.e(LOGTAG, "Unable to set new wallpaper");
             // Act as though the user canceled the operation so we try to
             // restore the old wallpaper.
             mCanceled = true;
+        } finally {
+            if (inputstream != null) {
+                try {
+                    inputstream.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
         }
 
         if (mCanceled) {
@@ -113,7 +163,7 @@
             oldWallpaper.setBounds(0, 0, width, height);
             oldWallpaper.draw(canvas);
             try {
-                WallpaperManager.getInstance(mContext).setBitmap(bm);
+                wm.setBitmap(bm);
             } catch (IOException e) {
                 Log.e(LOGTAG, "Unable to restore old wallpaper.");
             }
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index 562705b..7a00422 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -28,8 +28,6 @@
 import android.view.View;
 import android.webkit.WebChromeClient.CustomViewCallback;
 import android.webkit.WebView;
-import android.widget.FrameLayout;
-import android.widget.FrameLayout.LayoutParams;
 
 import java.util.List;
 
@@ -44,7 +42,6 @@
     private TabBar mTabBar;
 
     private TitleBarXLarge mTitleBar;
-    private TitleBarXLarge mFakeTitleBar;
 
     private boolean mUseQuickControls;
     private PieControl mPieControl;
@@ -57,9 +54,6 @@
         super(browser, controller);
         mTitleBar = new TitleBarXLarge(mActivity, mUiController, this);
         mTitleBar.setProgress(100);
-        mTitleBar.setEditable(false);
-        mFakeTitleBar = new TitleBarXLarge(mActivity, mUiController, this);
-        mFakeTitleBar.setEditable(true);
         mTabBar = new TabBar(mActivity, mUiController, this);
         mActionBar = mActivity.getActionBar();
         setupActionBar();
@@ -90,31 +84,28 @@
 
     private void setUseQuickControls(boolean useQuickControls) {
         mUseQuickControls = useQuickControls;
+        mTitleBar.setUseQuickControls(mUseQuickControls);
         if (useQuickControls) {
             checkTabCount();
             mPieControl = new PieControl(mActivity, mUiController, this);
             mPieControl.attachToContainer(mContentView);
-            setFakeTitleBarGravity(Gravity.BOTTOM);
-
-            // remove embedded title bar if present
-            WebView web = mTabControl.getCurrentWebView();
-            if ((web != null) && (web.getVisibleTitleHeight() > 0)) {
-                web.setEmbeddedTitleBar(null);
+            Tab tab = getActiveTab();
+            if ((tab != null) && (tab.getWebView() != null)) {
+                tab.getWebView().setEmbeddedTitleBar(null);
             }
+            setTitleGravity(Gravity.BOTTOM);
         } else {
             mActivity.getActionBar().show();
             if (mPieControl != null) {
                 mPieControl.removeFromContainer(mContentView);
             }
-            setFakeTitleBarGravity(Gravity.TOP);
-            // remove embedded title bar if present
+            setTitleGravity(Gravity.TOP);
             WebView web = mTabControl.getCurrentWebView();
-            if ((web != null) && (web.getVisibleTitleHeight() == 0)) {
+            if (web != null) {
                 web.setEmbeddedTitleBar(mTitleBar);
             }
         }
         mTabBar.setUseQuickControls(mUseQuickControls);
-        mFakeTitleBar.setUseQuickControls(mUseQuickControls);
     }
 
     private void checkTabCount() {
@@ -130,7 +121,7 @@
 
     @Override
     public void onDestroy() {
-        hideFakeTitleBar();
+        hideTitleBar();
     }
 
     // webview factory
@@ -156,8 +147,8 @@
     }
 
     @Override
-    public void onScroll(int visibleTitleHeight) {
-        mTabBar.onScroll(visibleTitleHeight);
+    public void onScroll(int visibleTitleHeight, boolean userInitiated) {
+        mTabBar.onScroll(visibleTitleHeight, userInitiated);
     }
 
     void stopWebViewScrolling() {
@@ -170,36 +161,27 @@
     // WebView callbacks
 
     @Override
-    public void bookmarkedStatusHasChanged(Tab tab) {
-        if (tab.inForeground()) {
-            boolean isBookmark = tab.isBookmarkedSite();
-            mTitleBar.setCurrentUrlIsBookmark(isBookmark);
-            mFakeTitleBar.setCurrentUrlIsBookmark(isBookmark);
-        }
-    }
-
-    @Override
     public void onProgressChanged(Tab tab) {
         int progress = tab.getLoadProgress();
         mTabBar.onProgress(tab, progress);
         if (tab.inForeground()) {
-            mFakeTitleBar.setProgress(progress);
+            mTitleBar.setProgress(progress);
             if (progress == 100) {
-                if (!mFakeTitleBar.isEditingUrl()) {
-                    hideFakeTitleBar();
+                if (!mTitleBar.isEditingUrl()) {
+                    hideTitleBar();
                     if (mUseQuickControls) {
-                        mFakeTitleBar.setShowProgressOnly(false);
-                        setFakeTitleBarGravity(Gravity.BOTTOM);
+                        mTitleBar.setShowProgressOnly(false);
+                        setTitleGravity(Gravity.BOTTOM);
                     }
                 }
             } else {
-                if (mUseQuickControls && !mFakeTitleBar.isEditingUrl()) {
-                    mFakeTitleBar.setShowProgressOnly(true);
-                    if (!isFakeTitleBarShowing()) {
-                        setFakeTitleBarGravity(Gravity.TOP);
+                if (!isTitleBarShowing()) {
+                    if (mUseQuickControls && !mTitleBar.isEditingUrl()) {
+                        mTitleBar.setShowProgressOnly(true);
+                        setTitleGravity(Gravity.TOP);
                     }
+                    showTitleBar();
                 }
-                showFakeTitleBar();
             }
         }
     }
@@ -220,6 +202,9 @@
 
     @Override
     public void setActiveTab(Tab tab) {
+        if (mTitleBar.isEditingUrl()) {
+            mTitleBar.stopEditingUrl();
+        }
         super.setActiveTab(tab);
         ScrollWebView view = (ScrollWebView) tab.getWebView();
         // TabControl.setCurrentTab has been called before this,
@@ -270,59 +255,57 @@
         return 0;
     }
 
-    void editUrl(boolean clearInput) {
+    @Override
+    public void editUrl(boolean clearInput) {
         if (mUiController.isInCustomActionMode()) {
             mUiController.endActionMode();
         }
-        showFakeTitleBar();
-        mFakeTitleBar.onEditUrl(clearInput);
+        showTitleBar();
+        mTitleBar.startEditingUrl(clearInput);
     }
 
-    void setFakeTitleBarGravity(int gravity) {
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)
-                mFakeTitleBar.getLayoutParams();
-        if (lp == null) {
-            lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
-                    LayoutParams.WRAP_CONTENT);
+    void showTitleBarAndEdit() {
+        mTitleBar.setShowProgressOnly(false);
+        showTitleBar();
+        mTitleBar.startEditingUrl(false);
+    }
+
+    void stopEditingUrl() {
+        mTitleBar.stopEditingUrl();
+    }
+
+    @Override
+    protected void showTitleBar() {
+        if (canShowTitleBar()) {
+            if (mUseQuickControls) {
+                setTitleGravity(Gravity.BOTTOM);
+                mContentView.addView(mTitleBar);
+            } else {
+                setTitleGravity(Gravity.TOP);
+            }
+            super.showTitleBar();
+            mTabBar.onShowTitleBar();
         }
-        lp.gravity = gravity;
-        mFakeTitleBar.setLayoutParams(lp);
-    }
-
-    void showFakeTitleBarAndEdit() {
-        mFakeTitleBar.setShowProgressOnly(false);
-        setFakeTitleBarGravity(Gravity.BOTTOM);
-        showFakeTitleBar();
-        mFakeTitleBar.onEditUrl(false);
     }
 
     @Override
-    protected void attachFakeTitleBar(WebView mainView) {
-        mContentView.addView(mFakeTitleBar);
-        mTabBar.onShowTitleBar();
-    }
-
-    @Override
-    protected void hideFakeTitleBar() {
-        if (isFakeTitleBarShowing()) {
-            mFakeTitleBar.setEditMode(false);
+    protected void hideTitleBar() {
+        if (isTitleBarShowing()) {
             mTabBar.onHideTitleBar();
-            mContentView.removeView(mFakeTitleBar);
+            setTitleGravity(Gravity.NO_GRAVITY);
+            if (mUseQuickControls) {
+                mContentView.removeView(mTitleBar);
+            }
+            super.hideTitleBar();
         }
     }
 
-    @Override
-    protected boolean isFakeTitleBarShowing() {
-        return (mFakeTitleBar.getParent() != null);
+    public boolean isEditingUrl() {
+        return mTitleBar.isEditingUrl();
     }
 
     @Override
-    protected TitleBarBase getFakeTitleBar() {
-        return mFakeTitleBar;
-    }
-
-    @Override
-    protected TitleBarBase getEmbeddedTitleBar() {
+    protected TitleBarBase getTitleBar() {
         return mTitleBar;
     }
 
@@ -330,9 +313,9 @@
 
     @Override
     public void onActionModeStarted(ActionMode mode) {
-        if (!mFakeTitleBar.isEditingUrl()) {
+        if (!mTitleBar.isEditingUrl()) {
             // hide the fake title bar when CAB is shown
-            hideFakeTitleBar();
+            hideTitleBar();
         }
     }
 
@@ -342,18 +325,17 @@
         if (inLoad) {
             // the titlebar was removed when the CAB was shown
             // if the page is loading, show it again
-            mFakeTitleBar.setShowProgressOnly(true);
-            if (!isFakeTitleBarShowing()) {
-                setFakeTitleBarGravity(Gravity.TOP);
+            mTitleBar.setShowProgressOnly(true);
+            if (!isTitleBarShowing()) {
+                setTitleGravity(Gravity.TOP);
             }
-            showFakeTitleBar();
+            showTitleBar();
         }
     }
 
     @Override
     protected void updateNavigationState(Tab tab) {
         mTitleBar.updateNavigationState(tab);
-        mFakeTitleBar.updateNavigationState(tab);
     }
 
     @Override
@@ -375,10 +357,8 @@
         if (getActiveTab() != null) {
             vsresults = getActiveTab().getVoiceSearchResults();
         }
-        mTitleBar.setInVoiceMode(true, null);
+        mTitleBar.setInVoiceMode(true, vsresults);
         mTitleBar.setDisplayTitle(title);
-        mFakeTitleBar.setInVoiceMode(true, vsresults);
-        mFakeTitleBar.setDisplayTitle(title);
     }
 
     @Override
@@ -386,8 +366,6 @@
         mTitleBar.setInVoiceMode(false, null);
         String url = tab.getUrl();
         mTitleBar.setDisplayTitle(url);
-        mFakeTitleBar.setInVoiceMode(false, null);
-        mFakeTitleBar.setDisplayTitle(url);
     }
 
     @Override
@@ -409,21 +387,32 @@
     @Override
     public boolean dispatchKey(int code, KeyEvent event) {
         WebView web = getActiveTab().getWebView();
-        switch (code) {
-            case KeyEvent.KEYCODE_TAB:
-            case KeyEvent.KEYCODE_DPAD_UP:
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-                if ((web != null) && web.hasFocus()) {
-                    editUrl(true);
-                    return true;
-                }
-        }
-        boolean ctrl = event.hasModifiers(KeyEvent.META_CTRL_ON);
-        if (!ctrl && event.isPrintingKey() && !mFakeTitleBar.isEditingUrl()) {
-            editUrl(true);
-            return mContentView.dispatchKeyEvent(event);
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+
+            switch (code) {
+                case KeyEvent.KEYCODE_TAB:
+                case KeyEvent.KEYCODE_DPAD_UP:
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    if ((web != null) && web.hasFocus() && !mTitleBar.hasFocus()) {
+                        editUrl(false);
+                        return true;
+                    }
+            }
+            boolean ctrl = event.hasModifiers(KeyEvent.META_CTRL_ON);
+            if (!ctrl && isTypingKey(event) && !mTitleBar.isEditingUrl()) {
+                editUrl(true);
+                return mContentView.dispatchKeyEvent(event);
+            }
         }
         return false;
     }
 
+    private boolean isTypingKey(KeyEvent evt) {
+        return evt.getUnicodeChar() > 0;
+    }
+
+    TabBar getTabBar() {
+        return mTabBar;
+    }
+
 }
diff --git a/src/com/android/browser/addbookmark/FolderSpinnerAdapter.java b/src/com/android/browser/addbookmark/FolderSpinnerAdapter.java
index 0712469..261aa62 100644
--- a/src/com/android/browser/addbookmark/FolderSpinnerAdapter.java
+++ b/src/com/android/browser/addbookmark/FolderSpinnerAdapter.java
@@ -35,15 +35,27 @@
  */
 public class FolderSpinnerAdapter implements SpinnerAdapter {
     private boolean mIncludeHomeScreen;
+    private boolean mIncludesRecentFolder;
+    private long mRecentFolderId;
+    private String mRecentFolderName;
 
     public static final int HOME_SCREEN = 0;
     public static final int ROOT_FOLDER = 1;
     public static final int OTHER_FOLDER = 2;
+    public static final int RECENT_FOLDER = 3;
 
     public FolderSpinnerAdapter(boolean includeHomeScreen) {
         mIncludeHomeScreen = includeHomeScreen;
     }
 
+    public void addRecentFolder(long folderId, String folderName) {
+        mIncludesRecentFolder = true;
+        mRecentFolderId = folderId;
+        mRecentFolderName = folderName;
+    }
+
+    public long recentFolderId() { return mRecentFolderId; }
+
     @Override
     public View getDropDownView(int position, View convertView, ViewGroup parent) {
         int labelResource;
@@ -60,6 +72,8 @@
                 labelResource = R.string.add_to_bookmarks_menu_option;
                 drawableResource = R.drawable.ic_bookmarks_holo_dark;
                 break;
+            case RECENT_FOLDER:
+                // Fall through and use the same icon resource
             case OTHER_FOLDER:
                 labelResource = R.string.add_to_other_folder_menu_option;
                 drawableResource = R.drawable.ic_folder_holo_dark;
@@ -73,7 +87,11 @@
         Context context = parent.getContext();
         LayoutInflater factory = LayoutInflater.from(context);
         TextView textView = (TextView) factory.inflate(R.layout.add_to_option, null);
-        textView.setText(labelResource);
+        if (position == RECENT_FOLDER) {
+            textView.setText(mRecentFolderName);
+        } else {
+            textView.setText(labelResource);
+        }
         Drawable drawable = context.getResources().getDrawable(drawableResource);
         textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null,
                 null, null);
@@ -90,7 +108,10 @@
 
     @Override
     public int getCount() {
-        return mIncludeHomeScreen ? 3 : 2;
+        int count = 2;
+        if (mIncludeHomeScreen) count++;
+        if (mIncludesRecentFolder) count++;
+        return count;
     }
 
     @Override
diff --git a/src/com/android/browser/preferences/GeneralPreferencesFragment.java b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
index b6228dc..1c82e1c 100644
--- a/src/com/android/browser/preferences/GeneralPreferencesFragment.java
+++ b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
@@ -21,9 +21,12 @@
 import com.android.browser.BrowserPreferencesPage;
 import com.android.browser.BrowserSettings;
 import com.android.browser.R;
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -125,11 +128,46 @@
             if (BrowserBookmarksPage.PREF_ACCOUNT_NAME.equals(key)
                     || BrowserBookmarksPage.PREF_ACCOUNT_TYPE.equals(key)) {
                 refreshUi();
+                BookmarkThumbnailWidgetProvider.refreshWidgets(getActivity(), true);
             }
         }
 
     };
 
+    private AccountManagerCallback<Bundle> mCallback =
+            new AccountManagerCallback<Bundle>() {
+
+        @Override
+        public void run(AccountManagerFuture<Bundle> future) {
+            try {
+                Bundle bundle = future.getResult();
+                String name = bundle.getString(AccountManager.KEY_ACCOUNT_NAME);
+                String type = bundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
+                Account account = new Account(name, type);
+                Fragment frag = new ImportWizardDialog();
+                Bundle extras = mChromeSync.getExtras();
+                extras.putParcelableArray("accounts", new Account[] { account });
+                frag.setArguments(extras);
+                getFragmentManager().beginTransaction()
+                        .add(frag, null)
+                        .commit();
+            } catch (Exception ex) {
+                // Canceled or failed to login, doesn't matter to us
+            }
+        }
+    };
+
+    OnPreferenceClickListener mAddAccount = new OnPreferenceClickListener() {
+
+        @Override
+        public boolean onPreferenceClick(Preference preference) {
+            AccountManager am = AccountManager.get(getActivity());
+            am.addAccount("com.google", null, null, null, getActivity(),
+                    mCallback, null);
+            return true;
+        }
+    };
+
     private class GetAccountsTask extends AsyncTask<Void, Void, String> {
         private Context mContext;
 
@@ -144,7 +182,7 @@
             if (accounts == null || accounts.length == 0) {
                 // No Google accounts setup, don't offer Chrome sync
                 if (mChromeSync != null) {
-                    getPreferenceScreen().removePreference(mChromeSync);
+                    mChromeSync.setOnPreferenceClickListener(mAddAccount);
                 }
             } else {
                 // Google accounts are present.
diff --git a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
index 409ddb7..2b2ee3e 100644
--- a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
+++ b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
@@ -44,6 +44,7 @@
 
         Preference e = findPreference(BrowserSettings.PREF_CLEAR_HISTORY);
         e.setOnPreferenceChangeListener(this);
+        setupAutoLoginPreference();
     }
 
     @Override
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index a51635c..3033db4 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -16,9 +16,12 @@
 
 package com.android.browser.provider;
 
+import com.google.common.annotations.VisibleForTesting;
+
 import com.android.browser.BookmarkUtils;
 import com.android.browser.BrowserBookmarksPage;
 import com.android.browser.R;
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
 import com.android.common.content.SyncStateContentProviderHelper;
 
 import android.accounts.Account;
@@ -33,8 +36,10 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.database.AbstractCursor;
+import android.database.ContentObserver;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
+import android.database.MatrixCursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
@@ -60,11 +65,12 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
 import java.util.HashMap;
 
 public class BrowserProvider2 extends SQLiteContentProvider {
 
-    static final String LEGACY_AUTHORITY = "browser";
+    public static final String LEGACY_AUTHORITY = "browser";
     static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder()
             .authority(LEGACY_AUTHORITY).scheme("content").build();
 
@@ -106,6 +112,7 @@
     static final int BOOKMARKS_FOLDER = 1002;
     static final int BOOKMARKS_FOLDER_ID = 1003;
     static final int BOOKMARKS_SUGGESTIONS = 1004;
+    static final int BOOKMARKS_DEFAULT_FOLDER_ID = 1005;
 
     static final int HISTORY = 2000;
     static final int HISTORY_ID = 2001;
@@ -159,6 +166,7 @@
         matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID);
         matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER);
         matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
+        matcher.addURI(authority, "bookmarks/folder/id", BOOKMARKS_DEFAULT_FOLDER_ID);
         matcher.addURI(authority,
                 SearchManager.SUGGEST_URI_PATH_QUERY,
                 BOOKMARKS_SUGGESTIONS);
@@ -315,6 +323,8 @@
 
     DatabaseHelper mOpenHelper;
     SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
+    // This is so provider tests can intercept widget updating
+    ContentObserver mWidgetObserver = null;
 
     final class DatabaseHelper extends SQLiteOpenHelper {
         static final String DATABASE_NAME = "browser2.db";
@@ -569,11 +579,17 @@
         return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false);
     }
 
-    @Override
-    public void notifyChange(boolean callerIsSyncAdapter) {
-        ContentResolver resolver = getContext().getContentResolver();
-        resolver.notifyChange(BrowserContract.AUTHORITY_URI, null, !callerIsSyncAdapter);
-        resolver.notifyChange(LEGACY_AUTHORITY_URI, null, !callerIsSyncAdapter);
+    @VisibleForTesting
+    public void setWidgetObserver(ContentObserver obs) {
+        mWidgetObserver = obs;
+    }
+
+    void refreshWidgets() {
+        if (mWidgetObserver == null) {
+            BookmarkThumbnailWidgetProvider.refreshWidgets(getContext());
+        } else {
+            mWidgetObserver.dispatchChange(false);
+        }
     }
 
     @Override
@@ -758,6 +774,15 @@
                 return cursor;
             }
 
+            case BOOKMARKS_DEFAULT_FOLDER_ID: {
+                String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
+                String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
+                long id = queryDefaultFolderId(accountName, accountType);
+                MatrixCursor c = new MatrixCursor(new String[] {Bookmarks._ID});
+                c.newRow().add(id);
+                return c;
+            }
+
             case BOOKMARKS_SUGGESTIONS: {
                 return doSuggestQuery(selection, selectionArgs, limit);
             }
@@ -958,6 +983,7 @@
             boolean callerIsSyncAdapter) {
         final int match = URI_MATCHER.match(uri);
         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int deleted = 0;
         switch (match) {
             case BOOKMARKS_ID: {
                 selection = DatabaseUtils.concatenateWhere(selection,
@@ -971,9 +997,12 @@
                 Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
                 selection = (String) withAccount[0];
                 selectionArgs = (String[]) withAccount[1];
-                int deleted = deleteBookmarks(selection, selectionArgs, callerIsSyncAdapter);
+                deleted = deleteBookmarks(selection, selectionArgs, callerIsSyncAdapter);
                 pruneImages();
-                return deleted;
+                if (deleted > 0) {
+                    refreshWidgets();
+                }
+                break;
             }
 
             case HISTORY_ID: {
@@ -984,9 +1013,9 @@
             }
             case HISTORY: {
                 filterSearchClient(selectionArgs);
-                int deleted = db.delete(TABLE_HISTORY, selection, selectionArgs);
+                deleted = db.delete(TABLE_HISTORY, selection, selectionArgs);
                 pruneImages();
-                return deleted;
+                break;
             }
 
             case SEARCHES_ID: {
@@ -996,17 +1025,20 @@
                 // fall through
             }
             case SEARCHES: {
-                return db.delete(TABLE_SEARCHES, selection, selectionArgs);
+                deleted = db.delete(TABLE_SEARCHES, selection, selectionArgs);
+                break;
             }
 
             case SYNCSTATE: {
-                return mSyncHelper.delete(db, selection, selectionArgs);
+                deleted = mSyncHelper.delete(db, selection, selectionArgs);
+                break;
             }
             case SYNCSTATE_ID: {
                 String selectionWithId =
                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
                         + (selection == null ? "" : " AND (" + selection + ")");
-                return mSyncHelper.delete(db, selectionWithId, selectionArgs);
+                deleted = mSyncHelper.delete(db, selectionWithId, selectionArgs);
+                break;
             }
             case LEGACY_ID: {
                 selection = DatabaseUtils.concatenateWhere(
@@ -1030,7 +1062,6 @@
                 }
                 Cursor c = qb.query(db, projection, selection, selectionArgs,
                         null, null, null);
-                int deleted = 0;
                 while (c.moveToNext()) {
                     long id = c.getLong(0);
                     boolean isBookmark = c.getInt(1) != 0;
@@ -1047,14 +1078,21 @@
                                 new String[] { Long.toString(id) });
                     }
                 }
-                return deleted;
+                break;
+            }
+            default: {
+                throw new UnsupportedOperationException("Unknown delete URI " + uri);
             }
         }
-        throw new UnsupportedOperationException("Unknown delete URI " + uri);
+        if (deleted > 0) {
+            postNotifyUri(uri);
+            postNotifyUri(LEGACY_AUTHORITY_URI);
+        }
+        return deleted;
     }
 
     long queryDefaultFolderId(String accountName, String accountType) {
-        if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+        if (!isNullAccount(accountName) && !isNullAccount(accountType)) {
             final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
             Cursor c = db.query(TABLE_BOOKMARKS, new String[] { Bookmarks._ID },
                     ChromeSyncColumns.SERVER_UNIQUE + " = ?" +
@@ -1134,6 +1172,7 @@
                 }
 
                 id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
+                refreshWidgets();
                 break;
             }
 
@@ -1179,6 +1218,8 @@
         }
 
         if (id >= 0) {
+            postNotifyUri(uri);
+            postNotifyUri(LEGACY_AUTHORITY_URI);
             return ContentUris.withAppendedId(uri, id);
         } else {
             return null;
@@ -1287,6 +1328,7 @@
                 values.remove(BookmarkColumns.USER_ENTERED);
             }
         }
+        int modified = 0;
         switch (match) {
             case BOOKMARKS_ID: {
                 selection = DatabaseUtils.concatenateWhere(selection,
@@ -1299,10 +1341,12 @@
                 Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
                 selection = (String) withAccount[0];
                 selectionArgs = (String[]) withAccount[1];
-                int updated = updateBookmarksInTransaction(values, selection, selectionArgs,
+                modified = updateBookmarksInTransaction(values, selection, selectionArgs,
                         callerIsSyncAdapter);
-                pruneImages();
-                return updated;
+                if (modified > 0) {
+                    refreshWidgets();
+                }
+                break;
             }
 
             case HISTORY_ID: {
@@ -1312,14 +1356,14 @@
                 // fall through
             }
             case HISTORY: {
-                int updated = updateHistoryInTransaction(values, selection, selectionArgs);
-                pruneImages();
-                return updated;
+                modified = updateHistoryInTransaction(values, selection, selectionArgs);
+                break;
             }
 
             case SYNCSTATE: {
-                return mSyncHelper.update(mDb, values,
+                modified = mSyncHelper.update(mDb, values,
                         appendAccountToSelection(uri, selection), selectionArgs);
+                break;
             }
 
             case SYNCSTATE_ID: {
@@ -1327,8 +1371,9 @@
                 String selectionWithId =
                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
                         + (selection == null ? "" : " AND (" + selection + ")");
-                return mSyncHelper.update(mDb, values,
+                modified = mSyncHelper.update(mDb, values,
                         selectionWithId, selectionArgs);
+                break;
             }
 
             case IMAGES: {
@@ -1336,20 +1381,107 @@
                 if (TextUtils.isEmpty(url)) {
                     throw new IllegalArgumentException("Images.URL is required");
                 }
+                if (!shouldUpdateImages(db, url, values)) {
+                    return 0;
+                }
                 int count = db.update(TABLE_IMAGES, values, Images.URL + "=?",
                         new String[] { url });
                 if (count == 0) {
                     db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values);
                     count = 1;
                 }
+                if (getUrlCount(db, TABLE_BOOKMARKS, url) > 0) {
+                    postNotifyUri(Bookmarks.CONTENT_URI);
+                    refreshWidgets();
+                }
+                if (getUrlCount(db, TABLE_HISTORY, url) > 0) {
+                    postNotifyUri(History.CONTENT_URI);
+                }
+                postNotifyUri(LEGACY_AUTHORITY_URI);
+                pruneImages();
                 return count;
             }
 
             case SEARCHES: {
-                return db.update(TABLE_SEARCHES, values, selection, selectionArgs);
+                modified = db.update(TABLE_SEARCHES, values, selection, selectionArgs);
+                break;
+            }
+
+            default: {
+                throw new UnsupportedOperationException("Unknown update URI " + uri);
             }
         }
-        throw new UnsupportedOperationException("Unknown update URI " + uri);
+        pruneImages();
+        if (modified > 0) {
+            postNotifyUri(uri);
+            postNotifyUri(LEGACY_AUTHORITY_URI);
+        }
+        return modified;
+    }
+
+    // We want to avoid sending out more URI notifications than we have to
+    // Thus, we check to see if the images we are about to store are already there
+    // This is used because things like a site's favion or touch icon is rarely
+    // changed, but the browser tries to update it every time the page loads.
+    // Without this, we will always send out 3 URI notifications per page load.
+    // With this, that drops to 0 or 1, depending on if the thumbnail changed.
+    private boolean shouldUpdateImages(
+            SQLiteDatabase db, String url, ContentValues values) {
+        final String[] projection = new String[] {
+                Images.FAVICON,
+                Images.THUMBNAIL,
+                Images.TOUCH_ICON,
+        };
+        Cursor cursor = db.query(TABLE_IMAGES, projection, Images.URL + "=?",
+                new String[] { url }, null, null, null);
+        byte[] nfavicon = values.getAsByteArray(Images.FAVICON);
+        byte[] nthumb = values.getAsByteArray(Images.THUMBNAIL);
+        byte[] ntouch = values.getAsByteArray(Images.TOUCH_ICON);
+        byte[] cfavicon = null;
+        byte[] cthumb = null;
+        byte[] ctouch = null;
+        try {
+            if (cursor.getCount() <= 0) {
+                return nfavicon != null || nthumb != null || ntouch != null;
+            }
+            while (cursor.moveToNext()) {
+                if (nfavicon != null) {
+                    cfavicon = cursor.getBlob(0);
+                    if (!Arrays.equals(nfavicon, cfavicon)) {
+                        return true;
+                    }
+                }
+                if (nthumb != null) {
+                    cthumb = cursor.getBlob(1);
+                    if (!Arrays.equals(nthumb, cthumb)) {
+                        return true;
+                    }
+                }
+                if (ntouch != null) {
+                    ctouch = cursor.getBlob(2);
+                    if (!Arrays.equals(ntouch, ctouch)) {
+                        return true;
+                    }
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        return false;
+    }
+
+    int getUrlCount(SQLiteDatabase db, String table, String url) {
+        Cursor c = db.query(table, new String[] { "COUNT(*)" },
+                "url = ?", new String[] { url }, null, null, null);
+        try {
+            int count = 0;
+            if (c.moveToFirst()) {
+                count = c.getInt(0);
+            }
+            return count;
+        } finally {
+            c.close();
+        }
     }
 
     /**
diff --git a/src/com/android/browser/provider/SQLiteContentProvider.java b/src/com/android/browser/provider/SQLiteContentProvider.java
index a50894a..13acd3d 100644
--- a/src/com/android/browser/provider/SQLiteContentProvider.java
+++ b/src/com/android/browser/provider/SQLiteContentProvider.java
@@ -19,26 +19,27 @@
 import android.content.ContentProvider;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.OperationApplicationException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteTransactionListener;
 import android.net.Uri;
 
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage.
  */
-public abstract class SQLiteContentProvider extends ContentProvider
-        implements SQLiteTransactionListener {
+public abstract class SQLiteContentProvider extends ContentProvider {
 
     private static final String TAG = "SQLiteContentProvider";
 
     private SQLiteOpenHelper mOpenHelper;
-    private volatile boolean mNotifyChange;
+    private Set<Uri> mChangedUris;
     protected SQLiteDatabase mDb;
 
     private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
@@ -53,6 +54,7 @@
     public boolean onCreate() {
         Context context = getContext();
         mOpenHelper = getDatabaseHelper(context);
+        mChangedUris = new HashSet<Uri>();
         return true;
     }
 
@@ -80,10 +82,14 @@
             boolean callerIsSyncAdapter);
 
     /**
-     * Called when the provider needs to notify the system of a change.
-     * @param callerIsSyncAdapter true if the caller that caused the change was a sync adapter.
+     * Call this to add a URI to the list of URIs to be notified when the transaction
+     * is committed.
      */
-    public abstract void notifyChange(boolean callerIsSyncAdapter);
+    protected void postNotifyUri(Uri uri) {
+        synchronized (mChangedUris) {
+            mChangedUris.add(uri);
+        }
+    }
 
     public boolean isCallerSyncAdapter(Uri uri) {
         return false;
@@ -104,12 +110,9 @@
         boolean applyingBatch = applyingBatch();
         if (!applyingBatch) {
             mDb = mOpenHelper.getWritableDatabase();
-            mDb.beginTransactionWithListener(this);
+            mDb.beginTransaction();
             try {
                 result = insertInTransaction(uri, values, callerIsSyncAdapter);
-                if (result != null) {
-                    mNotifyChange = true;
-                }
                 mDb.setTransactionSuccessful();
             } finally {
                 mDb.endTransaction();
@@ -118,9 +121,6 @@
             onEndTransaction(callerIsSyncAdapter);
         } else {
             result = insertInTransaction(uri, values, callerIsSyncAdapter);
-            if (result != null) {
-                mNotifyChange = true;
-            }
         }
         return result;
     }
@@ -130,13 +130,10 @@
         int numValues = values.length;
         boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
         mDb = mOpenHelper.getWritableDatabase();
-        mDb.beginTransactionWithListener(this);
+        mDb.beginTransaction();
         try {
             for (int i = 0; i < numValues; i++) {
                 Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter);
-                if (result != null) {
-                    mNotifyChange = true;
-                }
                 mDb.yieldIfContendedSafely();
             }
             mDb.setTransactionSuccessful();
@@ -155,13 +152,10 @@
         boolean applyingBatch = applyingBatch();
         if (!applyingBatch) {
             mDb = mOpenHelper.getWritableDatabase();
-            mDb.beginTransactionWithListener(this);
+            mDb.beginTransaction();
             try {
                 count = updateInTransaction(uri, values, selection, selectionArgs,
                         callerIsSyncAdapter);
-                if (count > 0) {
-                    mNotifyChange = true;
-                }
                 mDb.setTransactionSuccessful();
             } finally {
                 mDb.endTransaction();
@@ -170,9 +164,6 @@
             onEndTransaction(callerIsSyncAdapter);
         } else {
             count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter);
-            if (count > 0) {
-                mNotifyChange = true;
-            }
         }
 
         return count;
@@ -185,12 +176,9 @@
         boolean applyingBatch = applyingBatch();
         if (!applyingBatch) {
             mDb = mOpenHelper.getWritableDatabase();
-            mDb.beginTransactionWithListener(this);
+            mDb.beginTransaction();
             try {
                 count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
-                if (count > 0) {
-                    mNotifyChange = true;
-                }
                 mDb.setTransactionSuccessful();
             } finally {
                 mDb.endTransaction();
@@ -199,9 +187,6 @@
             onEndTransaction(callerIsSyncAdapter);
         } else {
             count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
-            if (count > 0) {
-                mNotifyChange = true;
-            }
         }
         return count;
     }
@@ -213,7 +198,7 @@
         int opCount = 0;
         boolean callerIsSyncAdapter = false;
         mDb = mOpenHelper.getWritableDatabase();
-        mDb.beginTransactionWithListener(this);
+        mDb.beginTransaction();
         try {
             mApplyingBatch.set(true);
             final int numOperations = operations.size();
@@ -246,31 +231,15 @@
         }
     }
 
-    @Override
-    public void onBegin() {
-        onBeginTransaction();
-    }
-
-    @Override
-    public void onCommit() {
-        beforeTransactionCommit();
-    }
-
-    @Override
-    public void onRollback() {
-        // not used
-    }
-
-    protected void onBeginTransaction() {
-    }
-
-    protected void beforeTransactionCommit() {
-    }
-
     protected void onEndTransaction(boolean callerIsSyncAdapter) {
-        if (mNotifyChange) {
-            mNotifyChange = false;
-            notifyChange(callerIsSyncAdapter);
+        Set<Uri> changed;
+        synchronized (mChangedUris) {
+            changed = new HashSet<Uri>(mChangedUris);
+            mChangedUris.clear();
+        }
+        ContentResolver resolver = getContext().getContentResolver();
+        for (Uri uri : changed) {
+            resolver.notifyChange(uri, null, !callerIsSyncAdapter);
         }
     }
 }
diff --git a/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java b/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java
index b991abd..48d7123 100644
--- a/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java
+++ b/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java
@@ -32,12 +32,9 @@
  * Widget that shows a preview of the user's bookmarks.
  */
 public class BookmarkThumbnailWidgetProvider extends AppWidgetProvider {
-    static final String ACTION_BOOKMARK_APPWIDGET_UPDATE =
+    public static final String ACTION_BOOKMARK_APPWIDGET_UPDATE =
         "com.android.browser.BOOKMARK_APPWIDGET_UPDATE";
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void onReceive(Context context, Intent intent) {
         // Handle bookmark-specific updates ourselves because they might be
@@ -58,23 +55,27 @@
     }
 
     @Override
-    public void onEnabled(Context context) {
-        // Start the backing service
-        context.startService(new Intent(context, BookmarkThumbnailWidgetService.class));
+    public void onDeleted(Context context, int[] appWidgetIds) {
+        super.onDeleted(context, appWidgetIds);
+        for (int widgetId : appWidgetIds) {
+            BookmarkThumbnailWidgetService.deleteWidgetState(context, widgetId);
+        }
+        removeOrphanedFiles(context);
     }
 
     @Override
     public void onDisabled(Context context) {
-        // Stop the backing service
-        context.stopService(new Intent(context, BookmarkThumbnailWidgetService.class));
+        super.onDisabled(context);
+        removeOrphanedFiles(context);
     }
 
-    @Override
-    public void onDeleted(Context context, int[] appWidgetIds) {
-        super.onDeleted(context, appWidgetIds);
-        context.startService(new Intent(BookmarkThumbnailWidgetService.ACTION_REMOVE_FACTORIES,
-                null, context, BookmarkThumbnailWidgetService.class)
-                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds));
+    /**
+     *  Checks for any state files that may have not received onDeleted
+     */
+    void removeOrphanedFiles(Context context) {
+        AppWidgetManager wm = AppWidgetManager.getInstance(context);
+        int[] ids = wm.getAppWidgetIds(getComponentName(context));
+        BookmarkThumbnailWidgetService.removeOrphanedStates(context, ids);
     }
 
     private void performUpdate(Context context,
@@ -92,9 +93,9 @@
             views.setOnClickPendingIntent(R.id.app_shortcut, launchBrowser);
             views.setRemoteAdapter(appWidgetId, R.id.bookmarks_list, updateIntent);
             appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.bookmarks_list);
-            Intent ic = new Intent(context, BookmarkThumbnailWidgetService.class);
+            Intent ic = new Intent(context, BookmarkWidgetProxy.class);
             views.setPendingIntentTemplate(R.id.bookmarks_list,
-                    PendingIntent.getService(context, 0, ic,
+                    PendingIntent.getBroadcast(context, 0, ic,
                     PendingIntent.FLAG_UPDATE_CURRENT));
             appWidgetManager.updateAppWidget(appWidgetId, views);
         }
@@ -107,4 +108,29 @@
     static ComponentName getComponentName(Context context) {
         return new ComponentName(context, BookmarkThumbnailWidgetProvider.class);
     }
+
+    public static void refreshWidgets(Context context) {
+        refreshWidgets(context, false);
+    }
+
+    public static void refreshWidgets(Context context, boolean zeroState) {
+        if (zeroState) {
+            final Context appContext = context.getApplicationContext();
+            new Thread() {
+                @Override
+                public void run() {
+                    AppWidgetManager wm = AppWidgetManager.getInstance(appContext);
+                    int[] ids = wm.getAppWidgetIds(getComponentName(appContext));
+                    for (int id : ids) {
+                        BookmarkThumbnailWidgetService.clearWidgetState(appContext, id);
+                    }
+                    refreshWidgets(appContext, false);
+                }
+            }.start();
+        } else {
+            context.sendBroadcast(new Intent(
+                    BookmarkThumbnailWidgetProvider.ACTION_BOOKMARK_APPWIDGET_UPDATE,
+                    null, context, BookmarkThumbnailWidgetProvider.class));
+        }
+    }
 }
diff --git a/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java b/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java
index e525159..675cdd9 100644
--- a/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java
+++ b/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java
@@ -16,8 +16,8 @@
 
 package com.android.browser.widget;
 
+import com.android.browser.BookmarkUtils;
 import com.android.browser.BrowserActivity;
-import com.android.browser.BrowserBookmarksPage;
 import com.android.browser.R;
 
 import android.appwidget.AppWidgetManager;
@@ -25,17 +25,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.database.ContentObserver;
 import android.database.Cursor;
+import android.database.MergeCursor;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory.Options;
 import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.preference.PreferenceManager;
+import android.os.Binder;
 import android.provider.BrowserContract;
 import android.provider.BrowserContract.Bookmarks;
 import android.text.TextUtils;
@@ -43,120 +40,38 @@
 import android.widget.RemoteViews;
 import android.widget.RemoteViewsService;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Stack;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class BookmarkThumbnailWidgetService extends RemoteViewsService {
 
     static final String TAG = "BookmarkThumbnailWidgetService";
-    static final boolean USE_FOLDERS = true;
-
-    static final String ACTION_REMOVE_FACTORIES
-            = "com.android.browser.widget.REMOVE_FACTORIES";
     static final String ACTION_CHANGE_FOLDER
             = "com.android.browser.widget.CHANGE_FOLDER";
 
+    static final String STATE_CURRENT_FOLDER = "current_folder";
+    static final String STATE_ROOT_FOLDER = "root_folder";
+
     private static final String[] PROJECTION = new String[] {
             BrowserContract.Bookmarks._ID,
             BrowserContract.Bookmarks.TITLE,
             BrowserContract.Bookmarks.URL,
             BrowserContract.Bookmarks.FAVICON,
             BrowserContract.Bookmarks.IS_FOLDER,
-            BrowserContract.Bookmarks.TOUCH_ICON,
             BrowserContract.Bookmarks.POSITION, /* needed for order by */
-            BrowserContract.Bookmarks.THUMBNAIL};
+            BrowserContract.Bookmarks.THUMBNAIL,
+            BrowserContract.Bookmarks.PARENT};
     private static final int BOOKMARK_INDEX_ID = 0;
     private static final int BOOKMARK_INDEX_TITLE = 1;
     private static final int BOOKMARK_INDEX_URL = 2;
     private static final int BOOKMARK_INDEX_FAVICON = 3;
     private static final int BOOKMARK_INDEX_IS_FOLDER = 4;
-    private static final int BOOKMARK_INDEX_TOUCH_ICON = 5;
-    private static final int BOOKMARK_INDEX_THUMBNAIL = 7;
-
-    // The service will likely be destroyed at any time, so we need to keep references to the
-    // factories across services connections.
-    private static final Map<Integer, BookmarkFactory> mFactories =
-            new HashMap<Integer, BookmarkFactory>();
-    private Handler mUiHandler;
-    private BookmarksObserver mBookmarksObserver;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mUiHandler = new Handler();
-        mBookmarksObserver = new BookmarksObserver(mUiHandler);
-        getContentResolver().registerContentObserver(
-                BrowserContract.Bookmarks.CONTENT_URI, true, mBookmarksObserver);
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        String action = intent.getAction();
-        if (Intent.ACTION_VIEW.equals(action)) {
-            if (intent.getData() == null) {
-                startActivity(new Intent(BrowserActivity.ACTION_SHOW_BROWSER, null,
-                        this, BrowserActivity.class)
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-            } else {
-                Intent view = new Intent(intent);
-                view.setComponent(null);
-                startActivity(view);
-            }
-        } else if (ACTION_REMOVE_FACTORIES.equals(action)) {
-            int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
-            if (ids != null) {
-                for (int id : ids) {
-                    BookmarkFactory bf = mFactories.remove(id);
-                    if (bf != null) {
-                        // Workaround a known framework bug
-                        // onDestroy is currently never called
-                        bf.onDestroy();
-                    }
-                }
-            }
-        } else if (ACTION_CHANGE_FOLDER.equals(action)) {
-            int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
-            long folderId = intent.getLongExtra(Bookmarks._ID, -1);
-            BookmarkFactory fac = mFactories.get(widgetId);
-            if (fac != null && folderId >= 0) {
-                fac.changeFolder(folderId);
-            } else {
-                // This a workaround to the issue when the Browser process crashes, after which
-                // mFactories is not populated (due to onBind() not being called).  Calling
-                // notifyDataSetChanged() will trigger a connection to be made.
-                AppWidgetManager.getInstance(getApplicationContext())
-                    .notifyAppWidgetViewDataChanged(widgetId, R.id.bookmarks_list);
-            }
-        }
-        return START_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        getContentResolver().unregisterContentObserver(mBookmarksObserver);
-    }
-
-    private class BookmarksObserver extends ContentObserver {
-        public BookmarksObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-
-            // Update all the bookmark widgets
-            if (mFactories != null) {
-                for (BookmarkFactory fac : mFactories.values()) {
-                    fac.loadData();
-                }
-            }
-        }
-    }
+    private static final int BOOKMARK_INDEX_THUMBNAIL = 6;
+    private static final int BOOKMARK_INDEX_PARENT_ID = 7;
 
     @Override
     public RemoteViewsFactory onGetViewFactory(Intent intent) {
@@ -164,64 +79,125 @@
         if (widgetId < 0) {
             Log.w(TAG, "Missing EXTRA_APPWIDGET_ID!");
             return null;
-        } else {
-            BookmarkFactory fac = mFactories.get(widgetId);
-            if (fac == null) {
-                fac = new BookmarkFactory(getApplicationContext(), widgetId);
+        }
+        return new BookmarkFactory(getApplicationContext(), widgetId);
+    }
+
+    static SharedPreferences getWidgetState(Context context, int widgetId) {
+        return context.getSharedPreferences(
+                String.format("widgetState-%d", widgetId),
+                Context.MODE_PRIVATE);
+    }
+
+    static void deleteWidgetState(Context context, int widgetId) {
+        File file = context.getSharedPrefsFile(
+                String.format("widgetState-%d", widgetId));
+        if (file.exists()) {
+            if (!file.delete()) {
+                file.deleteOnExit();
             }
-            mFactories.put(widgetId, fac);
-            return fac;
         }
     }
 
-    private static class Breadcrumb {
-        long mId;
-        String mTitle;
-        public Breadcrumb(long id, String title) {
-            mId = id;
-            mTitle = title;
+    static void changeFolder(Context context, Intent intent) {
+        int wid = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+        long fid = intent.getLongExtra(Bookmarks._ID, -1);
+        if (wid >= 0 && fid >= 0) {
+            SharedPreferences prefs = getWidgetState(context, wid);
+            prefs.edit().putLong(STATE_CURRENT_FOLDER, fid).commit();
+            AppWidgetManager.getInstance(context)
+                    .notifyAppWidgetViewDataChanged(wid, R.id.bookmarks_list);
         }
     }
 
-    static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory,
-            OnSharedPreferenceChangeListener {
-        private List<RenderResult> mBookmarks;
+    static void clearWidgetState(Context context, int widgetId) {
+        SharedPreferences pref = getWidgetState(context, widgetId);
+        pref.edit()
+            .remove(STATE_CURRENT_FOLDER)
+            .remove(STATE_ROOT_FOLDER)
+            .commit();
+    }
+
+    /**
+     *  Checks for any state files that may have not received onDeleted
+     */
+    static void removeOrphanedStates(Context context, int[] widgetIds) {
+        File prefsDirectory = context.getSharedPrefsFile("null").getParentFile();
+        File[] widgetStates = prefsDirectory.listFiles(new StateFilter(widgetIds));
+        for (File f : widgetStates) {
+            Log.w(TAG, "Found orphaned state: " + f.getName());
+            if (!f.delete()) {
+                f.deleteOnExit();
+            }
+        }
+    }
+
+    static class StateFilter implements FilenameFilter {
+
+        static final Pattern sStatePattern = Pattern.compile("widgetState-(\\d+)\\.xml");
+        HashSet<Integer> mWidgetIds;
+
+        StateFilter(int[] ids) {
+            mWidgetIds = new HashSet<Integer>();
+            for (int id : ids) {
+                mWidgetIds.add(id);
+            }
+        }
+
+        @Override
+        public boolean accept(File dir, String filename) {
+            Matcher m = sStatePattern.matcher(filename);
+            if (m.matches()) {
+                int id = Integer.parseInt(m.group(1));
+                if (!mWidgetIds.contains(id)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+    }
+
+    static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory {
+        private Cursor mBookmarks;
         private Context mContext;
         private int mWidgetId;
-        private String mAccountType;
-        private String mAccountName;
-        private Stack<Breadcrumb> mBreadcrumbs;
-        private LoadBookmarksTask mLoadTask;
+        private long mCurrentFolder = -1;
+        private long mRootFolder = -1;
+        private SharedPreferences mPreferences = null;
 
         public BookmarkFactory(Context context, int widgetId) {
-            mBreadcrumbs = new Stack<Breadcrumb>();
             mContext = context;
             mWidgetId = widgetId;
         }
 
-        void changeFolder(long folderId) {
-            if (mBookmarks == null) return;
-
-            if (!mBreadcrumbs.empty() && mBreadcrumbs.peek().mId == folderId) {
-                mBreadcrumbs.pop();
-                loadData();
-                return;
+        void syncState() {
+            if (mPreferences == null) {
+                mPreferences = getWidgetState(mContext, mWidgetId);
             }
-
-            for (RenderResult res : mBookmarks) {
-                if (res.mId == folderId) {
-                    mBreadcrumbs.push(new Breadcrumb(res.mId, res.mTitle));
-                    loadData();
-                    break;
-                }
+            long currentFolder = mPreferences.getLong(STATE_CURRENT_FOLDER, -1);
+            mRootFolder = mPreferences.getLong(STATE_ROOT_FOLDER, -1);
+            if (currentFolder != mCurrentFolder) {
+                resetBookmarks();
+                mCurrentFolder = currentFolder;
             }
         }
 
+        void saveState() {
+            if (mPreferences == null) {
+                mPreferences = getWidgetState(mContext, mWidgetId);
+            }
+            mPreferences.edit()
+                .putLong(STATE_CURRENT_FOLDER, mCurrentFolder)
+                .putLong(STATE_ROOT_FOLDER, mRootFolder)
+                .commit();
+        }
+
         @Override
         public int getCount() {
             if (mBookmarks == null)
                 return 0;
-            return mBookmarks.size();
+            return mBookmarks.getCount();
         }
 
         @Override
@@ -231,44 +207,33 @@
 
         @Override
         public RemoteViews getLoadingView() {
-            return null;
+            return new RemoteViews(
+                    mContext.getPackageName(), R.layout.bookmarkthumbnailwidget_item);
         }
 
         @Override
         public RemoteViews getViewAt(int position) {
-            if (position < 0 || position >= getCount()) {
+            if (!mBookmarks.moveToPosition(position)) {
                 return null;
             }
 
-            RenderResult res = mBookmarks.get(position);
-            Breadcrumb folder = mBreadcrumbs.empty() ? null : mBreadcrumbs.peek();
+            long id = mBookmarks.getLong(BOOKMARK_INDEX_ID);
+            String title = mBookmarks.getString(BOOKMARK_INDEX_TITLE);
+            String url = mBookmarks.getString(BOOKMARK_INDEX_URL);
+            boolean isFolder = mBookmarks.getInt(BOOKMARK_INDEX_IS_FOLDER) != 0;
 
             RemoteViews views = new RemoteViews(
                     mContext.getPackageName(), R.layout.bookmarkthumbnailwidget_item);
-            Intent fillin;
-            if (res.mIsFolder) {
-                long nfi = res.mId;
-                fillin = new Intent(ACTION_CHANGE_FOLDER, null,
-                        mContext, BookmarkThumbnailWidgetService.class)
-                        .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
-                        .putExtra(Bookmarks._ID, nfi);
-            } else {
-                fillin = new Intent(Intent.ACTION_VIEW)
-                        .addCategory(Intent.CATEGORY_BROWSABLE);
-                if (!TextUtils.isEmpty(res.mUrl)) {
-                    fillin.setData(Uri.parse(res.mUrl));
-                }
-            }
-            views.setOnClickFillInIntent(R.id.list_item, fillin);
             // Set the title of the bookmark. Use the url as a backup.
-            String displayTitle = res.mTitle;
+            String displayTitle = title;
             if (TextUtils.isEmpty(displayTitle)) {
                 // The browser always requires a title for bookmarks, but jic...
-                displayTitle = res.mUrl;
+                displayTitle = url;
             }
             views.setTextViewText(R.id.label, displayTitle);
-            if (res.mIsFolder) {
-                if (folder != null && res.mId == folder.mId) {
+            if (isFolder) {
+                if (id == mCurrentFolder) {
+                    id = mBookmarks.getLong(BOOKMARK_INDEX_PARENT_ID);
                     views.setImageViewResource(R.id.thumb, R.drawable.thumb_bookmark_widget_folder_back_holo);
                 } else {
                     views.setImageViewResource(R.id.thumb, R.drawable.thumb_bookmark_widget_folder_holo);
@@ -276,20 +241,45 @@
                 views.setImageViewResource(R.id.favicon, R.drawable.ic_bookmark_widget_bookmark_holo_dark);
                 views.setDrawableParameters(R.id.thumb, true, 0, -1, null, -1);
             } else {
+                // RemoteViews require a valid bitmap config
+                Options options = new Options();
+                options.inPreferredConfig = Config.ARGB_8888;
+                Bitmap thumbnail = null, favicon = null;
+                byte[] blob = mBookmarks.getBlob(BOOKMARK_INDEX_THUMBNAIL);
                 views.setDrawableParameters(R.id.thumb, true, 255, -1, null, -1);
-                if (res.mThumbnail != null) {
-                    views.setImageViewBitmap(R.id.thumb, res.mThumbnail);
+                if (blob != null && blob.length > 0) {
+                    thumbnail = BitmapFactory.decodeByteArray(
+                            blob, 0, blob.length, options);
+                    views.setImageViewBitmap(R.id.thumb, thumbnail);
                 } else {
                     views.setImageViewResource(R.id.thumb,
                             R.drawable.browser_thumbnail);
                 }
-                if (res.mIcon != null) {
-                    views.setImageViewBitmap(R.id.favicon, res.mIcon);
+                blob = mBookmarks.getBlob(BOOKMARK_INDEX_FAVICON);
+                if (blob != null && blob.length > 0) {
+                    favicon = BitmapFactory.decodeByteArray(
+                            blob, 0, blob.length, options);
+                    views.setImageViewBitmap(R.id.favicon, favicon);
                 } else {
                     views.setImageViewResource(R.id.favicon,
                             R.drawable.app_web_browser_sm);
                 }
             }
+            Intent fillin;
+            if (isFolder) {
+                fillin = new Intent(ACTION_CHANGE_FOLDER)
+                        .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
+                        .putExtra(Bookmarks._ID, id);
+            } else {
+                if (!TextUtils.isEmpty(url)) {
+                    fillin = new Intent(Intent.ACTION_VIEW)
+                            .addCategory(Intent.CATEGORY_BROWSABLE)
+                            .setData(Uri.parse(url));
+                } else {
+                    fillin = new Intent(BrowserActivity.ACTION_SHOW_BROWSER);
+                }
+            }
+            views.setOnClickFillInIntent(R.id.list_item, fillin);
             return views;
         }
 
@@ -305,187 +295,70 @@
 
         @Override
         public void onCreate() {
-            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
-            mAccountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
-            mAccountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
-            prefs.registerOnSharedPreferenceChangeListener(this);
-            loadData();
         }
 
         @Override
         public void onDestroy() {
-            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
-            prefs.unregisterOnSharedPreferenceChangeListener(this);
-
-            // Workaround known framework bug
-            // This class currently leaks, so free as much memory as we can
-            recycleBitmaps();
-            mBookmarks.clear();
-            mBreadcrumbs.clear();
-            if (mLoadTask != null) {
-                mLoadTask.cancel(false);
-                mLoadTask = null;
+            if (mBookmarks != null) {
+                mBookmarks.close();
+                mBookmarks = null;
             }
+            deleteWidgetState(mContext, mWidgetId);
         }
 
         @Override
         public void onDataSetChanged() {
+            long token = Binder.clearCallingIdentity();
+            syncState();
+            if (mRootFolder < 0 || mCurrentFolder < 0) {
+                // Our state has been zero'd, reset (account change most likely)
+                mRootFolder = getRootFolder();
+                mCurrentFolder = mRootFolder;
+                saveState();
+            }
+            loadBookmarks();
+            Binder.restoreCallingIdentity(token);
         }
 
-        void loadData() {
-            if (mLoadTask != null) {
-                mLoadTask.cancel(false);
-            }
-            mLoadTask = new LoadBookmarksTask();
-            mLoadTask.execute();
-        }
-
-        class LoadBookmarksTask extends AsyncTask<Void, Void, List<RenderResult>> {
-            private Breadcrumb mFolder;
-
-            @Override
-            protected void onPreExecute() {
-                mFolder = mBreadcrumbs.empty() ? null : mBreadcrumbs.peek();
-            }
-
-            @Override
-            protected List<RenderResult> doInBackground(Void... params) {
-                return loadBookmarks(mFolder);
-            }
-
-            @Override
-            protected void onPostExecute(List<RenderResult> result) {
-                if (!isCancelled() && result != null) {
-                    recycleBitmaps();
-                    mBookmarks = result;
-                    AppWidgetManager.getInstance(mContext)
-                            .notifyAppWidgetViewDataChanged(mWidgetId, R.id.bookmarks_list);
-                }
-            }
-        }
-
-        List<RenderResult> loadBookmarks(Breadcrumb folder) {
-            String where = null;
-            Uri uri;
-            if (USE_FOLDERS) {
-                uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
-                if (folder != null) {
-                    uri = ContentUris.withAppendedId(uri, folder.mId);
-                }
-            } else {
-                uri = BrowserContract.Bookmarks.CONTENT_URI;
-                where = Bookmarks.IS_FOLDER + " == 0";
-            }
-            uri = uri.buildUpon()
-                    .appendQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE, mAccountType)
-                    .appendQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME, mAccountName)
-                    .build();
-            Cursor c = null;
-            try {
-                c = mContext.getContentResolver().query(uri, PROJECTION,
-                        where, null, null);
-                if (c != null) {
-                    ArrayList<RenderResult> bookmarks
-                            = new ArrayList<RenderResult>(c.getCount() + 1);
-                    if (folder != null) {
-                        RenderResult res = new RenderResult(
-                                folder.mId, folder.mTitle, null);
-                        res.mIsFolder = true;
-                        bookmarks.add(res);
-                    }
-                    while (c.moveToNext()) {
-                        long id = c.getLong(BOOKMARK_INDEX_ID);
-                        String title = c.getString(BOOKMARK_INDEX_TITLE);
-                        String url = c.getString(BOOKMARK_INDEX_URL);
-                        RenderResult res = new RenderResult(id, title, url);
-                        res.mIsFolder = c.getInt(BOOKMARK_INDEX_IS_FOLDER) != 0;
-                        if (!res.mIsFolder) {
-                            // RemoteViews require a valid bitmap config
-                            Options options = new Options();
-                            options.inPreferredConfig = Config.ARGB_8888;
-                            Bitmap thumbnail = null, favicon = null;
-                            byte[] blob = c.getBlob(BOOKMARK_INDEX_THUMBNAIL);
-                            if (blob != null && blob.length > 0) {
-                                thumbnail = BitmapFactory.decodeByteArray(
-                                        blob, 0, blob.length, options);
-                            }
-                            blob = c.getBlob(BOOKMARK_INDEX_FAVICON);
-                            if (blob != null && blob.length > 0) {
-                                favicon = BitmapFactory.decodeByteArray(
-                                        blob, 0, blob.length, options);
-                            }
-                            res.mThumbnail = thumbnail;
-                            res.mIcon = favicon;
-                        }
-                        bookmarks.add(res);
-                    }
-                    if (bookmarks.size() == 0) {
-                        RenderResult res = new RenderResult(0, "", "");
-                        Bitmap thumbnail = BitmapFactory.decodeResource(
-                                mContext.getResources(),
-                                R.drawable.thumbnail_bookmarks_widget_no_bookmark_holo);
-                        Bitmap favicon = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
-                        res.mThumbnail = thumbnail;
-                        res.mIcon = favicon;
-                        for (int i = 0; i < 6; i++) {
-                            bookmarks.add(res);
-                        }
-                    }
-                    return bookmarks;
-                }
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "update bookmark widget", e);
-            } finally {
-                if (c != null) {
-                    c.close();
-                }
-            }
-            return null;
-        }
-
-        private void recycleBitmaps() {
-            // Do a bit of house cleaning for the system
+        private void resetBookmarks() {
             if (mBookmarks != null) {
-                for (RenderResult res : mBookmarks) {
-                    if (res.mThumbnail != null) {
-                        res.mThumbnail.recycle();
-                        res.mThumbnail = null;
-                    }
-                }
+                mBookmarks.close();
+                mBookmarks = null;
             }
         }
 
-        @Override
-        public void onSharedPreferenceChanged(
-                SharedPreferences prefs, String key) {
-            if (BrowserBookmarksPage.PREF_ACCOUNT_TYPE.equals(key)) {
-                mAccountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
-                mBreadcrumbs.clear();
-                loadData();
-            }
-            if (BrowserBookmarksPage.PREF_ACCOUNT_NAME.equals(key)) {
-                mAccountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
-                mBreadcrumbs.clear();
-                loadData();
+        long getRootFolder() {
+            Uri uri = Uri.withAppendedPath(
+                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, "id");
+            uri = BookmarkUtils.addAccountInfo(mContext, uri.buildUpon()).build();
+            Cursor c = mContext.getContentResolver().query(
+                    uri, null, null, null, null);
+            try {
+                c.moveToFirst();
+                return c.getLong(0);
+            } finally {
+                c.close();
             }
         }
-    }
 
-    // Class containing the rendering information for a specific bookmark.
-    private static class RenderResult {
-        final String mTitle;
-        final String mUrl;
-        Bitmap mThumbnail;
-        Bitmap mIcon;
-        boolean mIsFolder;
-        long mId;
+        void loadBookmarks() {
+            resetBookmarks();
 
-        RenderResult(long id, String title, String url) {
-            mId = id;
-            mTitle = title;
-            mUrl = url;
+            Uri uri = ContentUris.withAppendedId(
+                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER,
+                    mCurrentFolder);
+            uri = BookmarkUtils.addAccountInfo(mContext, uri.buildUpon()).build();
+            mBookmarks = mContext.getContentResolver().query(uri, PROJECTION,
+                    null, null, null);
+            if (mCurrentFolder != mRootFolder) {
+                uri = ContentUris.withAppendedId(
+                        BrowserContract.Bookmarks.CONTENT_URI,
+                        mCurrentFolder);
+                Cursor c = mContext.getContentResolver().query(uri, PROJECTION,
+                        null, null, null);
+                mBookmarks = new MergeCursor(new Cursor[] { c, mBookmarks });
+            }
         }
-
     }
 
 }
diff --git a/src/com/android/browser/widget/BookmarkWidgetProxy.java b/src/com/android/browser/widget/BookmarkWidgetProxy.java
new file mode 100644
index 0000000..8ab57fc
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkWidgetProxy.java
@@ -0,0 +1,53 @@
+/*
+ * 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.widget;
+
+import com.android.browser.BrowserActivity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class BookmarkWidgetProxy extends BroadcastReceiver {
+
+    private static final String TAG = "BookmarkWidgetProxy";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (BookmarkThumbnailWidgetService.ACTION_CHANGE_FOLDER.equals(intent.getAction())) {
+            BookmarkThumbnailWidgetService.changeFolder(context, intent);
+        } else if (BrowserActivity.ACTION_SHOW_BROWSER.equals(intent.getAction())) {
+            startActivity(context,
+                    new Intent(BrowserActivity.ACTION_SHOW_BROWSER,
+                    null, context, BrowserActivity.class));
+        } else {
+            Intent view = new Intent(intent);
+            view.setComponent(null);
+            startActivity(context, view);
+        }
+    }
+
+    void startActivity(Context context, Intent intent) {
+        try {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            context.startActivity(intent);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to start intent activity", e);
+        }
+    }
+}
diff --git a/tests/src/com/android/browser/PopularUrlsTest.java b/tests/src/com/android/browser/PopularUrlsTest.java
index 5e367be..3e7515f 100644
--- a/tests/src/com/android/browser/PopularUrlsTest.java
+++ b/tests/src/com/android/browser/PopularUrlsTest.java
@@ -305,12 +305,14 @@
         private int page;
         private String url;
         private boolean isRecovery;
+        private boolean allClear;
 
         private RunStatus(File file) throws IOException {
             mFile = file;
             FileReader input = null;
             BufferedReader reader = null;
             isRecovery = false;
+            allClear = false;
             iteration = 0;
             page = 0;
             try {
@@ -369,7 +371,9 @@
         }
 
         public void cleanUp() {
-            if (mFile.exists()) {
+            // only perform cleanup when allClear flag is set
+            // i.e. when the test was not interrupted by a Java crash
+            if (mFile.exists() && allClear) {
                 mFile.delete();
             }
         }
@@ -380,6 +384,7 @@
 
         public void incrementPage() {
             ++page;
+            allClear = true;
         }
 
         public void incrementIteration() {
@@ -400,6 +405,7 @@
 
         public void setUrl(String url) {
             this.url = url;
+            allClear = false;
         }
     }
 
diff --git a/tests/src/com/android/browser/tests/BP2ProviderTests.java b/tests/src/com/android/browser/tests/BP2ProviderTests.java
new file mode 100644
index 0000000..a468fcd
--- /dev/null
+++ b/tests/src/com/android/browser/tests/BP2ProviderTests.java
@@ -0,0 +1,46 @@
+/*
+ * 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.tests;
+
+import com.android.browser.tests.utils.BP2TestCaseHelper;
+
+import android.content.ContentValues;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.provider.BrowserContract.Images;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.ByteArrayOutputStream;
+
+@SmallTest
+public class BP2ProviderTests extends BP2TestCaseHelper {
+
+    public void testUpdateImage() {
+        String url = "http://stub1.com";
+        insertBookmark(url, "stub 1");
+        ContentValues values = new ContentValues();
+        values.put(Images.URL, url);
+        Bitmap bitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+        values.put(Images.THUMBNAIL, os.toByteArray());
+        // Use updateBookmarks because the bookmarks URI observer should
+        // be triggered, even though we aren't giving it a bookmarks URI
+        assertTrue(updateBookmark(Images.CONTENT_URI, values));
+    }
+
+}
diff --git a/tests/src/com/android/browser/tests/BP2UriObserverTests.java b/tests/src/com/android/browser/tests/BP2UriObserverTests.java
new file mode 100644
index 0000000..2e84814
--- /dev/null
+++ b/tests/src/com/android/browser/tests/BP2UriObserverTests.java
@@ -0,0 +1,105 @@
+/*
+ * 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.tests;
+
+import com.android.browser.tests.utils.BP2TestCaseHelper;
+
+import android.content.ContentValues;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.net.Uri;
+import android.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.History;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.ByteArrayOutputStream;
+
+@SmallTest
+public class BP2UriObserverTests extends BP2TestCaseHelper {
+
+    public void testInsertBookmark() {
+        Uri insertedUri = insertBookmark("http://stub1.com", "Stub1");
+        TriggeredObserver stubObs = new TriggeredObserver(insertedUri);
+        assertObserversTriggered(false, stubObs);
+        insertBookmark("http://stub2.com", "Stub2");
+        perfIdeallyUntriggered(stubObs);
+    }
+
+    public void testUpdateBookmark() {
+        Uri toUpdate = insertBookmark("http://stub1.com", "Stub1");
+        Uri unchanged = insertBookmark("http://stub2.com", "Stub2");
+        TriggeredObserver updateObs = new TriggeredObserver(toUpdate);
+        TriggeredObserver unchangedObs = new TriggeredObserver(unchanged);
+        assertObserversTriggered(false, updateObs, unchangedObs);
+        assertTrue(updateBookmark(toUpdate, "http://stub1.com", "Stub1: Revenge of the stubs"));
+        assertTrue("Update observer not notified!", updateObs.checkTriggered());
+        perfIdeallyUntriggered(unchangedObs);
+    }
+
+    public void testUpdateBookmarkImages() {
+        Uri toUpdate = insertBookmark("http://stub1.com", "Stub1");
+        Uri unchanged = insertBookmark("http://stub2.com", "Stub2");
+        Bitmap favicon = Bitmap.createBitmap(16, 16, Config.ARGB_8888);
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
+        byte[] rawFavicon = os.toByteArray();
+        ContentValues values = new ContentValues();
+        values.put(Bookmarks.FAVICON, rawFavicon);
+        values.put(Bookmarks.TITLE, "Stub1");
+        TriggeredObserver updateObs = new TriggeredObserver(toUpdate);
+        TriggeredObserver unchangedObs = new TriggeredObserver(unchanged);
+        assertTrue(updateBookmark(toUpdate, values));
+        assertTrue("Update observer not notified!", updateObs.checkTriggered());
+        perfIdeallyUntriggered(unchangedObs);
+    }
+
+    public void testInsertHistory() {
+        Uri insertedUri = insertHistory("http://stub1.com", "Stub1");
+        TriggeredObserver stubObs = new TriggeredObserver(insertedUri);
+        assertObserversTriggered(false, stubObs);
+        insertHistory("http://stub2.com", "Stub2");
+        perfIdeallyUntriggered(stubObs);
+    }
+
+    public void testUpdateHistory() {
+        Uri toUpdate = insertHistory("http://stub1.com", "Stub1");
+        Uri unchanged = insertHistory("http://stub2.com", "Stub2");
+        TriggeredObserver updateObs = new TriggeredObserver(toUpdate);
+        TriggeredObserver unchangedObs = new TriggeredObserver(unchanged);
+        assertObserversTriggered(false, updateObs, unchangedObs);
+        assertTrue(updateHistory(toUpdate, "http://stub1.com", "Stub1: Revenge of the stubs"));
+        assertTrue("Update observer not notified!", updateObs.checkTriggered());
+        perfIdeallyUntriggered(unchangedObs);
+    }
+
+    public void testUpdateHistoryImages() {
+        Uri toUpdate = insertHistory("http://stub1.com", "Stub1");
+        Uri unchanged = insertHistory("http://stub2.com", "Stub2");
+        Bitmap favicon = Bitmap.createBitmap(16, 16, Config.ARGB_8888);
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
+        byte[] rawFavicon = os.toByteArray();
+        ContentValues values = new ContentValues();
+        values.put(History.FAVICON, rawFavicon);
+        values.put(History.TITLE, "Stub1");
+        TriggeredObserver updateObs = new TriggeredObserver(toUpdate);
+        TriggeredObserver unchangedObs = new TriggeredObserver(unchanged);
+        assertTrue(updateHistory(toUpdate, values));
+        assertTrue("Update observer not notified!", updateObs.checkTriggered());
+        perfIdeallyUntriggered(unchangedObs);
+    }
+}
diff --git a/tests/src/com/android/browser/tests/BookmarksTests.java b/tests/src/com/android/browser/tests/BookmarksTests.java
new file mode 100644
index 0000000..bd1a1b2
--- /dev/null
+++ b/tests/src/com/android/browser/tests/BookmarksTests.java
@@ -0,0 +1,64 @@
+/*
+ * 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.tests;
+
+import com.android.browser.Bookmarks;
+import com.android.browser.tests.utils.BP2TestCaseHelper;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Extends from BP2TestCaseHelper for the helper methods
+ * and to get the mock database
+ */
+@SmallTest
+public class BookmarksTests extends BP2TestCaseHelper {
+
+    public void testQueryCombinedForUrl() {
+        // First, add some bookmarks
+        assertNotNull(insertBookmark(
+                "http://google.com/search?q=test", "Test search"));
+        assertNotNull(insertBookmark(
+                "http://google.com/search?q=mustang", "Mustang search"));
+        assertNotNull(insertBookmark(
+                "http://google.com/search?q=aliens", "Aliens search"));
+        ContentResolver cr = getMockContentResolver();
+
+        Cursor c = null;
+        try {
+            // First, search for a match
+            String url = "http://google.com/search?q=test";
+            c = Bookmarks.queryCombinedForUrl(cr, null, url);
+            assertEquals(1, c.getCount());
+            assertTrue(c.moveToFirst());
+            assertEquals(url, c.getString(0));
+            c.close();
+
+            // Next, search for no match
+            url = "http://google.com/search";
+            c = Bookmarks.queryCombinedForUrl(cr, null, url);
+            assertEquals(0, c.getCount());
+            assertFalse(c.moveToFirst());
+            c.close();
+        } finally {
+            if (c != null) c.close();
+        }
+    }
+
+}
diff --git a/tests/src/com/android/browser/tests/utils/BP2TestCaseHelper.java b/tests/src/com/android/browser/tests/utils/BP2TestCaseHelper.java
new file mode 100644
index 0000000..58e5bbe
--- /dev/null
+++ b/tests/src/com/android/browser/tests/utils/BP2TestCaseHelper.java
@@ -0,0 +1,207 @@
+/*
+ * 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.tests.utils;
+
+import com.android.browser.provider.BrowserProvider2;
+
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Browser;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.History;
+import android.util.Log;
+
+/**
+ *  This is a replacement for ProviderTestCase2 that can handle notifyChange testing.
+ *  It also has helper methods specifically for testing BrowserProvider2
+ */
+public abstract class BP2TestCaseHelper extends ProviderTestCase3<BrowserProvider2> {
+
+    // Tag for potential performance impacts
+    private static final String PERFTAG = "BP2-PerfCheck";
+
+    private TriggeredObserver mLegacyObserver;
+    private TriggeredObserver mRootObserver;
+    private TriggeredObserver mBookmarksObserver;
+    private TriggeredObserver mHistoryObserver;
+    private TriggeredObserver mWidgetObserver;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mLegacyObserver = new TriggeredObserver(Browser.BOOKMARKS_URI);
+        mRootObserver = new TriggeredObserver(BrowserContract.AUTHORITY_URI);
+        mBookmarksObserver = new TriggeredObserver(Bookmarks.CONTENT_URI);
+        mHistoryObserver = new TriggeredObserver(History.CONTENT_URI);
+        mWidgetObserver = new TriggeredObserver();
+        // We don't need to worry about setting this back to null since this
+        // is a private instance local to the MockContentResolver
+        getProvider().setWidgetObserver(mWidgetObserver);
+    }
+
+    public BP2TestCaseHelper() {
+        super(BrowserProvider2.class,
+                BrowserContract.AUTHORITY, BrowserProvider2.LEGACY_AUTHORITY);
+    }
+
+    public void perfIdeallyUntriggered(TriggeredObserver... obs) {
+        for (TriggeredObserver ob : obs) {
+            if (ob.checkTriggered()) {
+                // Not ideal, unnecessary notification
+                Log.i(PERFTAG, ob.mUri + " onChange called but content unaltered!");
+            }
+        }
+    }
+
+    public void assertObserversTriggered(boolean triggered,
+            TriggeredObserver... observers) {
+        for (TriggeredObserver obs : observers) {
+            assertEquals(obs.mUri + ", descendents:" + obs.mNotifyForDescendents,
+                    triggered, obs.checkTriggered());
+        }
+    }
+
+    public class TriggeredObserver extends ContentObserver {
+        private boolean mTriggered;
+        Uri mUri;
+        boolean mNotifyForDescendents;
+
+        /**
+         * Creates an unmanaged TriggeredObserver
+         */
+        public TriggeredObserver() {
+            super(null);
+        }
+
+        /**
+         * Same as TriggeredObserver(uri, true);
+         */
+        public TriggeredObserver(Uri uri) {
+            this(uri, true);
+        }
+
+        /**
+         * Creates a managed TriggeredObserver that self-registers with the
+         * mock ContentResolver
+         */
+        public TriggeredObserver(Uri uri, boolean notifyForDescendents) {
+            super(null);
+            mUri = uri;
+            mNotifyForDescendents = notifyForDescendents;
+            registerContentObserver(uri, notifyForDescendents, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            mTriggered = true;
+        }
+
+        public boolean checkTriggered() {
+            boolean ret = mTriggered;
+            mTriggered = false;
+            return ret;
+        }
+    }
+
+    Uri mockInsert(Uri url, ContentValues values) {
+        assertObserversTriggered(false, mLegacyObserver, mRootObserver);
+        Uri ret = getMockContentResolver().insert(url, values);
+        assertObserversTriggered(true, mLegacyObserver, mRootObserver);
+        return ret;
+    }
+
+    int mockUpdate(Uri uri, ContentValues values, String where,
+            String[] selectionArgs) {
+        assertObserversTriggered(false, mLegacyObserver, mRootObserver);
+        int ret = getMockContentResolver().update(uri, values, where, selectionArgs);
+        if (ret > 0) {
+            assertObserversTriggered(true, mLegacyObserver, mRootObserver);
+        } else {
+            perfIdeallyUntriggered(mLegacyObserver);
+            perfIdeallyUntriggered(mRootObserver);
+        }
+        return ret;
+    }
+
+    public Uri insertBookmark(String url, String title) {
+        ContentValues values = new ContentValues();
+        values.put(BrowserContract.Bookmarks.TITLE, title);
+        values.put(BrowserContract.Bookmarks.URL, url);
+        values.put(BrowserContract.Bookmarks.IS_FOLDER, 0);
+        assertObserversTriggered(false, mBookmarksObserver, mWidgetObserver);
+        Uri ret = mockInsert(Bookmarks.CONTENT_URI, values);
+        assertObserversTriggered(true, mBookmarksObserver, mWidgetObserver);
+        perfIdeallyUntriggered(mHistoryObserver);
+        return ret;
+    }
+
+    public boolean updateBookmark(Uri uri, String url, String title) {
+        ContentValues values = new ContentValues();
+        values.put(BrowserContract.Bookmarks.TITLE, title);
+        values.put(BrowserContract.Bookmarks.URL, url);
+        return updateBookmark(uri, values);
+    }
+
+    public boolean updateBookmark(Uri uri, ContentValues values) {
+        assertObserversTriggered(false, mBookmarksObserver, mWidgetObserver);
+        int modifyCount = mockUpdate(uri, values, null, null);
+        assertTrue("UpdatedBookmark modified too much! " + uri, modifyCount <= 1);
+        boolean updated = modifyCount == 1;
+        if (updated) {
+            assertObserversTriggered(updated, mBookmarksObserver, mWidgetObserver);
+        } else {
+            perfIdeallyUntriggered(mBookmarksObserver, mWidgetObserver);
+        }
+        perfIdeallyUntriggered(mHistoryObserver);
+        return updated;
+    }
+
+    public Uri insertHistory(String url, String title) {
+        ContentValues values = new ContentValues();
+        values.put(BrowserContract.History.TITLE, title);
+        values.put(BrowserContract.History.URL, url);
+        assertObserversTriggered(false, mHistoryObserver);
+        Uri ret = mockInsert(History.CONTENT_URI, values);
+        assertObserversTriggered(true, mHistoryObserver);
+        perfIdeallyUntriggered(mBookmarksObserver, mWidgetObserver);
+        return ret;
+    }
+
+    public boolean updateHistory(Uri uri, String url, String title) {
+        ContentValues values = new ContentValues();
+        values.put(BrowserContract.History.TITLE, title);
+        values.put(BrowserContract.History.URL, url);
+        return updateHistory(uri, values);
+    }
+
+    public boolean updateHistory(Uri uri, ContentValues values) {
+        assertObserversTriggered(false, mHistoryObserver);
+        int modifyCount = mockUpdate(uri, values, null, null);
+        assertTrue("UpdatedHistory modified too much! " + uri, modifyCount <= 1);
+        boolean updated = modifyCount == 1;
+        if (updated) {
+            assertObserversTriggered(updated, mHistoryObserver);
+        } else {
+            perfIdeallyUntriggered(mHistoryObserver);
+        }
+        perfIdeallyUntriggered(mBookmarksObserver, mWidgetObserver);
+        return updated;
+    }
+}
diff --git a/tests/src/com/android/browser/tests/utils/MockContentResolver2.java b/tests/src/com/android/browser/tests/utils/MockContentResolver2.java
new file mode 100644
index 0000000..20f5521
--- /dev/null
+++ b/tests/src/com/android/browser/tests/utils/MockContentResolver2.java
@@ -0,0 +1,102 @@
+/*
+ * 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.tests.utils;
+
+import com.google.android.collect.Maps;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.database.ContentObserver;
+import android.net.Uri;
+
+import java.util.Map;
+
+public class MockContentResolver2 extends ContentResolver {
+
+    Map<String, ContentProvider> mProviders;
+    private final MockObserverNode mRootNode = new MockObserverNode("");
+
+    /*
+     * Creates a local map of providers. This map is used instead of the global map when an
+     * API call tries to acquire a provider.
+     */
+    public MockContentResolver2() {
+        super(null);
+        mProviders = Maps.newHashMap();
+    }
+
+    /**
+     * Adds access to a provider based on its authority
+     *
+     * @param name The authority name associated with the provider.
+     * @param provider An instance of {@link android.content.ContentProvider} or one of its
+     * subclasses, or null.
+     */
+    public void addProvider(String name, ContentProvider provider) {
+
+        /*
+         * Maps the authority to the provider locally.
+         */
+        mProviders.put(name, provider);
+    }
+
+    /** @hide */
+    @Override
+    protected IContentProvider acquireProvider(Context context, String name) {
+        return acquireExistingProvider(context, name);
+    }
+
+    /** @hide */
+    @Override
+    protected IContentProvider acquireExistingProvider(Context context, String name) {
+
+        /*
+         * Gets the content provider from the local map
+         */
+        final ContentProvider provider = mProviders.get(name);
+
+        if (provider != null) {
+            return provider.getIContentProvider();
+        } else {
+            return null;
+        }
+    }
+
+    /** @hide */
+    @Override
+    public boolean releaseProvider(IContentProvider provider) {
+        return true;
+    }
+
+    @Override
+    public void notifyChange(Uri uri, ContentObserver observer,
+            boolean syncToNetwork) {
+        mRootNode.notifyMyObservers(uri, 0, observer, false);
+    }
+
+    public void safeRegisterContentObserver(Uri uri, boolean notifyForDescendents,
+            ContentObserver observer) {
+        mRootNode.addObserver(uri, observer, notifyForDescendents);
+    }
+
+    public void safeUnregisterContentObserver(ContentObserver observer) {
+        mRootNode.removeObserver(observer);
+    }
+
+}
diff --git a/tests/src/com/android/browser/tests/utils/MockObserverNode.java b/tests/src/com/android/browser/tests/utils/MockObserverNode.java
new file mode 100644
index 0000000..edcffd4
--- /dev/null
+++ b/tests/src/com/android/browser/tests/utils/MockObserverNode.java
@@ -0,0 +1,169 @@
+/*
+ * 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.tests.utils;
+
+import android.database.ContentObserver;
+import android.net.Uri;
+
+import java.util.ArrayList;
+
+public final class MockObserverNode {
+    private class MockObserverEntry {
+        public final ContentObserver observer;
+        public final boolean notifyForDescendents;
+
+        public MockObserverEntry(ContentObserver o, boolean n) {
+            observer = o;
+            notifyForDescendents = n;
+        }
+    }
+
+    public static final int INSERT_TYPE = 0;
+    public static final int UPDATE_TYPE = 1;
+    public static final int DELETE_TYPE = 2;
+
+    private String mName;
+    private ArrayList<MockObserverNode> mChildren = new ArrayList<MockObserverNode>();
+    private ArrayList<MockObserverEntry> mObservers = new ArrayList<MockObserverEntry>();
+
+    public MockObserverNode(String name) {
+        mName = name;
+    }
+
+    private String getUriSegment(Uri uri, int index) {
+        if (uri != null) {
+            if (index == 0) {
+                return uri.getAuthority();
+            } else {
+                return uri.getPathSegments().get(index - 1);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    private int countUriSegments(Uri uri) {
+        if (uri == null) {
+            return 0;
+        }
+        return uri.getPathSegments().size() + 1;
+    }
+
+    public void addObserver(Uri uri, ContentObserver observer,
+            boolean notifyForDescendents) {
+        addObserver(uri, 0, observer, notifyForDescendents);
+    }
+
+    private void addObserver(Uri uri, int index, ContentObserver observer,
+            boolean notifyForDescendents) {
+        // If this is the leaf node add the observer
+        if (index == countUriSegments(uri)) {
+            mObservers.add(new MockObserverEntry(observer, notifyForDescendents));
+            return;
+        }
+
+        // Look to see if the proper child already exists
+        String segment = getUriSegment(uri, index);
+        if (segment == null) {
+            throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
+        }
+        int N = mChildren.size();
+        for (int i = 0; i < N; i++) {
+            MockObserverNode node = mChildren.get(i);
+            if (node.mName.equals(segment)) {
+                node.addObserver(uri, index + 1, observer, notifyForDescendents);
+                return;
+            }
+        }
+
+        // No child found, create one
+        MockObserverNode node = new MockObserverNode(segment);
+        mChildren.add(node);
+        node.addObserver(uri, index + 1, observer, notifyForDescendents);
+    }
+
+    public boolean removeObserver(ContentObserver observer) {
+        int size = mChildren.size();
+        for (int i = 0; i < size; i++) {
+            boolean empty = mChildren.get(i).removeObserver(observer);
+            if (empty) {
+                mChildren.remove(i);
+                i--;
+                size--;
+            }
+        }
+
+        size = mObservers.size();
+        for (int i = 0; i < size; i++) {
+            MockObserverEntry entry = mObservers.get(i);
+            if (entry.observer == observer) {
+                mObservers.remove(i);
+                break;
+            }
+        }
+
+        if (mChildren.size() == 0 && mObservers.size() == 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private void notifyMyObservers(boolean leaf, ContentObserver observer,
+            boolean selfNotify) {
+        int N = mObservers.size();
+        for (int i = 0; i < N; i++) {
+            MockObserverEntry entry = mObservers.get(i);
+
+            // Don't notify the observer if it sent the notification and isn't interesed
+            // in self notifications
+            if (entry.observer == observer && !selfNotify) {
+                continue;
+            }
+
+            // Make sure the observer is interested in the notification
+            if (leaf || (!leaf && entry.notifyForDescendents)) {
+                entry.observer.onChange(selfNotify);
+            }
+        }
+    }
+
+    public void notifyMyObservers(Uri uri, int index, ContentObserver observer,
+            boolean selfNotify) {
+        String segment = null;
+        int segmentCount = countUriSegments(uri);
+        if (index >= segmentCount) {
+            // This is the leaf node, notify all observers
+            notifyMyObservers(true, observer, selfNotify);
+        } else if (index < segmentCount){
+            segment = getUriSegment(uri, index);
+            // Notify any observers at this level who are interested in descendents
+            notifyMyObservers(false, observer, selfNotify);
+        }
+
+        int N = mChildren.size();
+        for (int i = 0; i < N; i++) {
+            MockObserverNode node = mChildren.get(i);
+            if (segment == null || node.mName.equals(segment)) {
+                // We found the child,
+                node.notifyMyObservers(uri, index + 1, observer, selfNotify);
+                if (segment != null) {
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/browser/tests/utils/ProviderTestCase3.java b/tests/src/com/android/browser/tests/utils/ProviderTestCase3.java
new file mode 100644
index 0000000..5799b0f
--- /dev/null
+++ b/tests/src/com/android/browser/tests/utils/ProviderTestCase3.java
@@ -0,0 +1,156 @@
+/*
+ * 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.tests.utils;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+import android.test.IsolatedContext;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContext;
+
+import java.io.File;
+
+/**
+ * Replacement for ProviderTestCase2 that keeps calls to ContentResolver.notifyChanged
+ * internal to observers registered with ProviderTestCase3.registerContentObserver
+ */
+public abstract class ProviderTestCase3<T extends ContentProvider> extends AndroidTestCase {
+
+    Class<T> mProviderClass;
+    String[] mProviderAuthority;
+
+    private IsolatedContext mProviderContext;
+    private MockContentResolver2 mResolver;
+
+    private class MockContext2 extends MockContext {
+
+        @Override
+        public Resources getResources() {
+            return getContext().getResources();
+        }
+
+        @Override
+        public File getDir(String name, int mode) {
+            // name the directory so the directory will be separated from
+            // one created through the regular Context
+            return getContext().getDir("mockcontext2_" + name, mode);
+        }
+
+        @Override
+        public Context getApplicationContext() {
+            return this;
+        }
+    }
+    /**
+     * Constructor.
+     *
+     * @param providerClass The class name of the provider under test
+     * @param providerAuthorities The provider's authority string
+     */
+    public ProviderTestCase3(Class<T> providerClass, String... providerAuthorities) {
+        mProviderClass = providerClass;
+        mProviderAuthority = providerAuthorities;
+    }
+
+    private T mProvider;
+
+    /**
+     * Returns the content provider created by this class in the {@link #setUp()} method.
+     * @return T An instance of the provider class given as a parameter to the test case class.
+     */
+    public T getProvider() {
+        return mProvider;
+    }
+
+    /**
+     * Sets up the environment for the test fixture.
+     * <p>
+     * Creates a new
+     * {@link com.android.browser.tests.utils.MockContentResolver2}, a new IsolatedContext
+     * that isolates the provider's file operations, and a new instance of
+     * the provider under test within the isolated environment.
+     * </p>
+     *
+     * @throws Exception
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mResolver = new MockContentResolver2();
+        final String filenamePrefix = "test.";
+        RenamingDelegatingContext targetContextWrapper = new
+                RenamingDelegatingContext(
+                new MockContext2(), // The context that most methods are
+                                    //delegated to
+                getContext(), // The context that file methods are delegated to
+                filenamePrefix);
+        mProviderContext = new IsolatedContext(mResolver, targetContextWrapper);
+
+        mProvider = mProviderClass.newInstance();
+        mProvider.attachInfo(mProviderContext, null);
+        assertNotNull(mProvider);
+        for (String auth : mProviderAuthority) {
+            mResolver.addProvider(auth, getProvider());
+        }
+    }
+
+    /**
+     * Tears down the environment for the test fixture.
+     * <p>
+     * Calls {@link android.content.ContentProvider#shutdown()} on the
+     * {@link android.content.ContentProvider} represented by mProvider.
+     */
+    @Override
+    protected void tearDown() throws Exception {
+        mProvider.shutdown();
+        super.tearDown();
+    }
+
+    /**
+     * Gets the {@link MockContentResolver2} created by this class during initialization. You
+     * must use the methods of this resolver to access the provider under test.
+     *
+     * @return A {@link MockContentResolver2} instance.
+     */
+    public MockContentResolver2 getMockContentResolver() {
+        return mResolver;
+    }
+
+    /**
+     * Gets the {@link IsolatedContext} created by this class during initialization.
+     * @return The {@link IsolatedContext} instance
+     */
+    public IsolatedContext getMockContext() {
+        return mProviderContext;
+    }
+
+    public void registerContentObserver(Uri uri, boolean notifyForDescendents,
+            ContentObserver observer) {
+        mResolver.safeRegisterContentObserver(uri, notifyForDescendents, observer);
+    }
+
+    public void unregisterContentObserver(ContentObserver observer) {
+        mResolver.safeUnregisterContentObserver(observer);
+    }
+
+}