Merge "Adds a "open all in tabs" to folder context menu" into honeycomb
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5b844e0..9ceaf82 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -223,6 +223,13 @@
                 <action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"/>
             </intent-filter>
         </receiver>
+
+        <!-- For custom home pages (like most visited) -->
+        <provider
+            android:name=".homepages.HomeProvider"
+            android:authorities="com.android.browser.home"
+            android:readPermission="com.android.browser.permission.READ_HISTORY_BOOKMARKS"
+            android:exported="false" />
     </application>
 
 </manifest>
diff --git a/res/drawable-mdpi/ic_pie_web.png b/res/drawable-mdpi/ic_pie_web.png
new file mode 100644
index 0000000..86e41ff
--- /dev/null
+++ b/res/drawable-mdpi/ic_pie_web.png
Binary files differ
diff --git a/res/layout/new_folder_layout.xml b/res/layout/new_folder_layout.xml
index 4ce0ade..ecc730f 100644
--- a/res/layout/new_folder_layout.xml
+++ b/res/layout/new_folder_layout.xml
@@ -27,12 +27,29 @@
         android:gravity="center_vertical"
         android:minHeight="?android:attr/listPreferredItemHeight"
         android:src="@drawable/ic_folder_bookmark_widget_holo_dark" />
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:background="@*android:drawable/edit_text_holo_dark"
+      android:gravity="center_vertical"
+      android:paddingBottom="5dip"
+      android:orientation="horizontal">
     <EditText
         android:id="@+id/folder_namer"
-        android:layout_width="match_parent"
+        android:layout_width="0dip"
         android:layout_height="wrap_content"
+        android:layout_weight="1"
         android:textAppearance="?android:attr/textAppearanceMedium"
+        android:background="@null"
         android:gravity="center_vertical"
         android:paddingLeft="6dip"
-        android:minHeight="?android:attr/listPreferredItemHeight" />
+        />
+    <ImageView
+        android:id="@+id/close"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:src="@drawable/ic_tab_close"
+        />
+  </LinearLayout>
 </LinearLayout>
diff --git a/res/raw/most_visited.ktpl b/res/raw/most_visited.ktpl
new file mode 100644
index 0000000..04b9eee
--- /dev/null
+++ b/res/raw/most_visited.ktpl
@@ -0,0 +1,85 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

+

+<html>

+<head>

+<title><%@ string/new_tab %></title>

+<meta name="viewport" content="width=device-width; initial-scale=1.0;" />

+

+<style type="text/css">

+

+* {

+    padding: 0;

+    margin: 0;

+}

+

+body {

+    text-align: center;

+    margin: 16px auto;

+    padding: 0 8px 0 8px;

+    max-width: <%@ dimen/mv_max_width %>px;

+}

+

+#most_visited h3 {

+    text-align: center;

+    padding: 0;

+    margin: 5px 0 5px 0px;

+}

+

+.thumbwrap li {

+    display: inline-block;

+    margin: 0 7px 15px 7px;

+    padding: 0;

+}

+

+@media all and (orientation:portrait) {

+.thumbwrap li {

+    width: <%@ dimen/mv_item_width_portrait %>px;

+}

+}

+

+@media all and (orientation:landscape) {

+.thumbwrap li {

+    width: <%@ dimen/mv_item_width %>px;

+}

+}

+

+.thumbwrap a {

+    display: block;

+    text-decoration: none;

+    color: #000;

+}

+

+.thumbwrap img {

+    border: <%@ dimen/mv_border_width %>px solid #e0e0e0;

+    border-radius: 5px;

+    width: 100%;

+}

+

+.thumbwrap .caption {

+    margin-top: 2px;

+    margin-left: 4px;

+    white-space: nowrap;

+    overflow: hidden;

+    text-overflow: ellipsis;

+    display: block;

+    font-size: .8em;

+    text-align: left;

+}

+

+</style>

+

+</head>

+<body>

+    <h3><%@ string/tab_most_visited %></h3>

+    <ul class="thumbwrap">

+        <%{ most_visited %>

+            <li>

+                <a href="<%= url %>">

+                    <img class="wrimg" src="<%= thumbnail %>" />

+                    <span class="caption"><%= title %></span>

+                </a>

+            </li>

+        <%} most_visited %>

+    </ul>

+</body>

+</html>

diff --git a/res/values-xlarge/dimensions.xml b/res/values-xlarge/dimensions.xml
index 5b86c86..9f5a602 100644
--- a/res/values-xlarge/dimensions.xml
+++ b/res/values-xlarge/dimensions.xml
@@ -14,4 +14,9 @@
     <dimen name="bookmarkThumbnailWidth">180dip</dimen>
     <dimen name="bookmarkThumbnailHeight">120dip</dimen>
     <dimen name="favicon_padded_size">24dip</dimen>
+    <!-- For the most visited page -->
+    <dimen name="mv_max_width">1010dp</dimen>
+    <dimen name="mv_item_width">231dp</dimen>
+    <dimen name="mv_item_width_portrait">213dp</dimen>
+    <dimen name="mv_border_width">3dp</dimen>
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index cb1b754..834bead 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -29,7 +29,7 @@
     <color name="bookmarkWidgetDivider">#383847</color>
     <color name="bookmarkWidgetItemBackground">#2b2b3c</color>
     <color name="bookmarkWidgetFolderBackground">#A0383847</color>
-    <color name="qc_slice_normal">#C0A0A0A0</color>
-    <color name="qc_slice_active">#C02090FF</color>
+    <color name="qc_slice_normal">#E0A0A0A0</color>
+    <color name="qc_slice_active">#E02090FF</color>
     <color name="bookmarkWidgetFaviconBackground">#23ffffff</color>
 </resources>
diff --git a/res/values/dimensions.xml b/res/values/dimensions.xml
index 03127dd..d50ce13 100644
--- a/res/values/dimensions.xml
+++ b/res/values/dimensions.xml
@@ -33,4 +33,9 @@
     <dimen name="qc_slop">15dip</dimen>
     <dimen name="bookmark_widget_thumb_size">32dip</dimen>
     <dimen name="bookmark_widget_favicon_size">26dip</dimen>
+    <!-- For the most visited page -->
+    <dimen name="mv_max_width">830dp</dimen>
+    <dimen name="mv_item_width">96dp</dimen>
+    <dimen name="mv_item_width_portrait">96dp</dimen>
+    <dimen name="mv_border_width">3dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e676bb3..4464370 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -605,12 +605,17 @@
     </string-array>
     <string name="pref_default_text_encoding_default" translatable="false">Latin-1</string>
     <!-- Title for lab settings [CHAR LIMIT=25] -->
-    <string name="pref_lab_title">Lab</string>
+    <string name="pref_lab_title">Labs</string>
     <!-- Title for lab quick controls feature [CHAR LIMIT=40] -->
     <string name="pref_lab_quick_controls">Quick Controls</string>
     <!-- Summary for lab quick controls feature [CHAR LIMIT=80] -->
     <string name="pref_lab_quick_controls_summary">
         Swipe thumb from left or right edge to access quick controls</string>
+    <!-- Title for lab "Most Visited" homepage feature [CHAR LIMIT=40] -->
+    <string name="pref_lab_most_visited_homepage">Most Visited Homepage</string>
+    <!-- Summary for lab "Most Visited" homepage feature [CHAR LIMIT=80] -->
+    <string name="pref_lab_most_visited_homepage_summary">
+        Sets your homepage to show the most visited pages.</string>
     <!-- Title for a dialog displayed when the browser has a data connectivity
             problem -->
     <string name="browserFrameNetworkErrorLabel">Data connectivity problem</string>
diff --git a/res/xml/lab_preferences.xml b/res/xml/lab_preferences.xml
index 2168471..16a5169 100644
--- a/res/xml/lab_preferences.xml
+++ b/res/xml/lab_preferences.xml
@@ -23,4 +23,10 @@
         android:title="@string/pref_lab_quick_controls"
         android:summary="@string/pref_lab_quick_controls_summary" />
 
+    <CheckBoxPreference
+        android:key="use_most_visited_homepage"
+        android:defaultValue="false"
+        android:title="@string/pref_lab_most_visited_homepage"
+        android:summary="@string/pref_lab_most_visited_homepage_summary" />
+
 </PreferenceScreen>
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index 9d8c73c..5e389ea 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -98,6 +98,7 @@
     private View mDefaultView;
     private View mFolderSelector;
     private EditText mFolderNamer;
+    private View mFolderCancel;
     private boolean mIsFolderNamerShowing;
     private View mFolderNamerHolder;
     private View mAddNewFolder;
@@ -243,6 +244,8 @@
             } else {
                 finish();
             }
+        } else if (v == mFolderCancel) {
+            completeOrCancelFolderNaming(true);
         } else if (v == mAddNewFolder) {
             setShowFolderNamer(true);
             mFolderNamer.setText(R.string.new_folder);
@@ -286,6 +289,11 @@
     public void onNothingSelected(AdapterView<?> parent) {
     }
 
+    /**
+     * Finish naming a folder, and close the IME
+     * @param cancel If true, the new folder is not created.  If false, the new
+     *      folder is created and the user is taken inside it.
+     */
     private void completeOrCancelFolderNaming(boolean cancel) {
         if (!cancel && !TextUtils.isEmpty(mFolderNamer.getText())) {
             String name = mFolderNamer.getText().toString();
@@ -588,6 +596,8 @@
         mFolderNamerHolder = getLayoutInflater().inflate(R.layout.new_folder_layout, null);
         mFolderNamer = (EditText) mFolderNamerHolder.findViewById(R.id.folder_namer);
         mFolderNamer.setOnEditorActionListener(this);
+        mFolderCancel = mFolderNamerHolder.findViewById(R.id.close);
+        mFolderCancel.setOnClickListener(this);
 
         mAddNewFolder = findViewById(R.id.add_new_folder);
         mAddNewFolder.setOnClickListener(this);
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 267056e..ca8091f 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -17,6 +17,7 @@
 
 package com.android.browser;
 
+import com.android.browser.homepages.HomeProvider;
 import com.android.browser.search.SearchEngine;
 import com.android.browser.search.SearchEngines;
 
@@ -119,6 +120,7 @@
 
     // Lab settings
     private boolean quickControls = false;
+    private boolean useMostVisitedHomepage = false;
 
     // By default the error console is shown once the user navigates to about:debug.
     // The setting can be then toggled from the settings menu.
@@ -171,6 +173,7 @@
     public final static String PREF_USER_AGENT = "user_agent";
 
     public final static String PREF_QUICK_CONTROLS = "enable_quick_controls";
+    public final static String PREF_MOST_VISITED_HOMEPAGE = "use_most_visited_homepage";
 
     private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (Macintosh; " +
             "U; Intel Mac OS X 10_6_3; en-us) AppleWebKit/533.16 (KHTML, " +
@@ -496,6 +499,7 @@
         }
 
         quickControls = p.getBoolean(PREF_QUICK_CONTROLS, quickControls);
+        useMostVisitedHomepage = p.getBoolean(PREF_MOST_VISITED_HOMEPAGE, useMostVisitedHomepage);
 
         // Only set these on startup if it is a dev build
         if (DEV_BUILD) {
@@ -525,6 +529,9 @@
     }
 
     public String getHomePage() {
+        if (useMostVisitedHomepage) {
+            return HomeProvider.MOST_VISITED;
+        }
         return homeUrl;
     }
 
@@ -584,6 +591,10 @@
         return quickControls;
     }
 
+    public boolean useMostVisitedHomepage() {
+        return useMostVisitedHomepage;
+    }
+
     public boolean showDebugSettings() {
         return showDebugSettings;
     }
@@ -847,6 +858,8 @@
             update();
         } else if (PREF_QUICK_CONTROLS.equals(key)) {
             quickControls = p.getBoolean(PREF_QUICK_CONTROLS, quickControls);
+        } else if (PREF_MOST_VISITED_HOMEPAGE.equals(key)) {
+            useMostVisitedHomepage = p.getBoolean(PREF_MOST_VISITED_HOMEPAGE, useMostVisitedHomepage);
         }
     }
 }
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index e707e36..23594f2 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -733,7 +733,7 @@
     // WebViewController
 
     @Override
-    public void onPageStarted(Tab tab, WebView view, String url, Bitmap favicon) {
+    public void onPageStarted(Tab tab, WebView view, Bitmap favicon) {
 
         // We've started to load a new page. If there was a pending message
         // to save a screenshot then we will now take the new page and save
@@ -764,6 +764,7 @@
 
         mUi.onTabDataChanged(tab);
 
+        String url = tab.getUrl();
         // update the bookmark database for favicon
         maybeUpdateFavicon(tab, null, url, favicon);
 
@@ -777,9 +778,10 @@
     }
 
     @Override
-    public void onPageFinished(Tab tab, String url) {
+    public void onPageFinished(Tab tab) {
         mUi.onTabDataChanged(tab);
-        if (!tab.isPrivateBrowsingEnabled()) {
+        if (!tab.isPrivateBrowsingEnabled()
+                && !TextUtils.isEmpty(tab.getUrl())) {
             if (tab.inForeground() && !didUserStopLoading()
                     || !tab.inForeground()) {
                 // Only update the bookmark screenshot if the user did not
@@ -799,7 +801,7 @@
         }
         // Performance probe
         if (false) {
-            Performance.onPageFinished(url);
+            Performance.onPageFinished(tab.getUrl());
          }
 
         Performance.tracePageFinished();
@@ -843,7 +845,7 @@
     public void onReceivedTitle(Tab tab, final String title) {
         mUi.onTabDataChanged(tab);
         final String pageUrl = tab.getUrl();
-        if (pageUrl == null || pageUrl.length()
+        if (TextUtils.isEmpty(pageUrl) || pageUrl.length()
                 >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
             return;
         }
@@ -887,12 +889,13 @@
     }
 
     @Override
-    public void doUpdateVisitedHistory(Tab tab, String url,
-            boolean isReload) {
+    public void doUpdateVisitedHistory(Tab tab, boolean isReload) {
         // Don't save anything in private browsing mode
         if (tab.isPrivateBrowsingEnabled()) return;
+        String url = tab.getUrl();
 
-        if (url.regionMatches(true, 0, "about:", 0, 6)) {
+        if (TextUtils.isEmpty(url)
+                || url.regionMatches(true, 0, "about:", 0, 6)) {
             return;
         }
         mDataController.updateVisitedHistory(url);
@@ -1026,6 +1029,7 @@
         intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
                 mActivity.getComponentName().flattenToString());
         intent.putExtra(SEND_APP_ID_EXTRA, false);
+        intent.putExtra(RecognizerIntent.EXTRA_WEB_SEARCH_ONLY, true);
         mActivity.startActivity(intent);
     }
 
diff --git a/src/com/android/browser/PieControl.java b/src/com/android/browser/PieControl.java
index 210e9ea..6326f2e 100644
--- a/src/com/android/browser/PieControl.java
+++ b/src/com/android/browser/PieControl.java
@@ -70,7 +70,7 @@
             mPie.addItem(mRefresh);
             mBack = makeMenuView(R.drawable.ic_pie_back);
             mPie.addItem(mBack);
-            mUrl = makeMenuView(R.drawable.ic_pie_search);
+            mUrl = makeMenuView(R.drawable.ic_pie_web);
             mPie.addItem(mUrl);
             mBookmarks = makeMenuView(R.drawable.ic_pie_bookmarks);
             mPie.addItem(mBookmarks);
@@ -133,6 +133,7 @@
             mUiController.bookmarksOrHistoryPicker(false);
         } else if (mNewTab == v) {
             mUiController.openTabToHomePage();
+            mUi.showFakeTitleBarAndEdit();
         } else if (mClose == v) {
             mUiController.closeCurrentTab();
         } else {
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 320d3b3..d70b0ef 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -532,7 +532,7 @@
             }
 
             // finally update the UI in the activity if it is in the foreground
-            mWebViewController.onPageStarted(Tab.this, view, url, favicon);
+            mWebViewController.onPageStarted(Tab.this, view, favicon);
 
             updateBookmarkedStatus();
         }
@@ -556,7 +556,7 @@
                 // but before a provisional load occurred
                 mCurrentState.mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
             }
-            mWebViewController.onPageFinished(Tab.this, url);
+            mWebViewController.onPageFinished(Tab.this);
         }
 
         // return true if want to hijack the url to let another app to handle it
@@ -687,7 +687,7 @@
         @Override
         public void doUpdateVisitedHistory(WebView view, String url,
                 boolean isReload) {
-            mWebViewController.doUpdateVisitedHistory(Tab.this, url, isReload);
+            mWebViewController.doUpdateVisitedHistory(Tab.this, isReload);
         }
 
         /**
@@ -1568,7 +1568,7 @@
     }
 
     String getUrl() {
-        return mCurrentState.mUrl;
+        return UrlUtils.filteredUrl(mCurrentState.mUrl);
     }
 
     /**
diff --git a/src/com/android/browser/TabBar.java b/src/com/android/browser/TabBar.java
index 502512a..ba123d0 100644
--- a/src/com/android/browser/TabBar.java
+++ b/src/com/android/browser/TabBar.java
@@ -16,7 +16,6 @@
 
 package com.android.browser;
 
-import android.graphics.Matrix;
 import com.android.browser.ScrollWebView.ScrollListener;
 
 import android.app.Activity;
@@ -26,6 +25,7 @@
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Shader;
@@ -33,6 +33,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.PaintDrawable;
+import android.util.Log;
 import android.view.ContextMenu;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -140,6 +141,8 @@
 
     void setUseQuickControls(boolean useQuickControls) {
         mUseQuickControls = useQuickControls;
+        mNewTab.setVisibility(mUseQuickControls ? View.GONE
+                : View.VISIBLE);
     }
 
     int getTabCount() {
@@ -160,7 +163,9 @@
         super.onMeasure(hspec, vspec);
         int w = getMeasuredWidth();
         // adjust for new tab overlap
-        w -= mTabOverlap;
+        if (!mUseQuickControls) {
+            w -= mTabOverlap;
+        }
         setMeasuredDimension(w, getMeasuredHeight());
     }
 
@@ -188,8 +193,14 @@
         if (mNewTab == view) {
             mUiController.openTabToHomePage();
         } else if (mTabs.getSelectedTab() == view) {
-            if (mUseQuickControls) return;
-            if (mUi.isFakeTitleBarShowing() && !isLoading()) {
+            if (mUseQuickControls) {
+                if (mUi.isFakeTitleBarShowing() && !isLoading()) {
+                    mUi.hideFakeTitleBar();
+                } else {
+                    mUi.stopWebViewScrolling();
+                    mUi.showFakeTitleBarAndEdit();
+                }
+            } else if (mUi.isFakeTitleBarShowing() && !isLoading()) {
                 mUi.hideFakeTitleBar();
             } else {
                 showUrlBar();
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
index 57cc724..8be4df5 100644
--- a/src/com/android/browser/TitleBarXLarge.java
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -17,6 +17,7 @@
 package com.android.browser;
 
 import com.android.browser.UrlInputView.UrlInputListener;
+import com.android.browser.search.SearchEngine;
 
 import android.app.Activity;
 import android.app.SearchManager;
@@ -373,7 +374,11 @@
     }
 
     private void setSearchMode(boolean voiceSearchEnabled) {
-        mVoiceSearch.setVisibility(voiceSearchEnabled ? View.VISIBLE :
+        SearchEngine searchEngine = BrowserSettings.getInstance()
+                .getSearchEngine();
+        boolean showvoicebutton = voiceSearchEnabled &&
+                (searchEngine != null && searchEngine.supportsVoiceSearch());
+        mVoiceSearch.setVisibility(showvoicebutton ? View.VISIBLE :
                 View.GONE);
         mGoButton.setVisibility(voiceSearchEnabled ? View.GONE :
                 View.VISIBLE);
diff --git a/src/com/android/browser/UrlUtils.java b/src/com/android/browser/UrlUtils.java
index 2df0a61..72ac37b 100644
--- a/src/com/android/browser/UrlUtils.java
+++ b/src/com/android/browser/UrlUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.browser;
 
+import com.android.browser.homepages.HomeProvider;
+
 import android.net.Uri;
 import android.util.Patterns;
 import android.webkit.URLUtil;
@@ -145,4 +147,15 @@
         return inUrl;
     }
 
+    // Returns the filtered URL. Cannot return null, but can return an empty string
+    /* package */ static String filteredUrl(String inUrl) {
+        if (inUrl == null) {
+            return "";
+        }
+        if (inUrl.startsWith(HomeProvider.MOST_VISITED)) {
+            return "";
+        }
+        return inUrl;
+    }
+
 }
diff --git a/src/com/android/browser/WebViewController.java b/src/com/android/browser/WebViewController.java
index a187af0..813b63b 100644
--- a/src/com/android/browser/WebViewController.java
+++ b/src/com/android/browser/WebViewController.java
@@ -46,9 +46,9 @@
 
     void createSubWindow(Tab tab);
 
-    void onPageStarted(Tab tab, WebView view, String url, Bitmap favicon);
+    void onPageStarted(Tab tab, WebView view, Bitmap favicon);
 
-    void onPageFinished(Tab tab, String url);
+    void onPageFinished(Tab tab);
 
     void onProgressChanged(Tab tab);
 
@@ -62,7 +62,7 @@
 
     void onUnhandledKeyEvent(KeyEvent event);
 
-    void doUpdateVisitedHistory(Tab tab, String url, boolean isReload);
+    void doUpdateVisitedHistory(Tab tab, boolean isReload);
 
     void getVisitedHistory(final ValueCallback<String[]> callback);
 
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index ac121cc..1e607a7 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -276,6 +276,7 @@
     @Override
     protected void hideFakeTitleBar() {
         if (isFakeTitleBarShowing()) {
+            mFakeTitleBar.setUrlMode(false);
             mContentView.removeView(mFakeTitleBar);
             mTabBar.onHideTitleBar();
         }
diff --git a/src/com/android/browser/homepages/HomeProvider.java b/src/com/android/browser/homepages/HomeProvider.java
new file mode 100644
index 0000000..5c368eb
--- /dev/null
+++ b/src/com/android/browser/homepages/HomeProvider.java
@@ -0,0 +1,81 @@
+

+/*

+ * 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.homepages;

+

+import android.content.ContentProvider;

+import android.content.ContentValues;

+import android.content.res.AssetFileDescriptor;

+import android.database.Cursor;

+import android.net.Uri;

+import android.os.ParcelFileDescriptor;

+import android.util.Log;

+

+import java.io.IOException;

+

+public class HomeProvider extends ContentProvider {

+

+    private static final String TAG = "HomeProvider";

+    public static final String AUTHORITY = "com.android.browser.home";

+    public static final String MOST_VISITED = "content://" + AUTHORITY + "/";

+

+    @Override

+    public int delete(Uri uri, String selection, String[] selectionArgs) {

+        return 0;

+    }

+

+    @Override

+    public String getType(Uri uri) {

+        return null;

+    }

+

+    @Override

+    public Uri insert(Uri uri, ContentValues values) {

+        return null;

+    }

+

+    @Override

+    public boolean onCreate() {

+        return false;

+    }

+

+    @Override

+    public Cursor query(Uri uri, String[] projection, String selection,

+            String[] selectionArgs, String sortOrder) {

+        return null;

+    }

+

+    @Override

+    public int update(Uri uri, ContentValues values, String selection,

+            String[] selectionArgs) {

+        return 0;

+    }

+

+    @Override

+    public ParcelFileDescriptor openFile(Uri uri, String mode) {

+        try {

+            ParcelFileDescriptor[] pipes = ParcelFileDescriptor.createPipe();

+            final ParcelFileDescriptor write = pipes[1];

+            AssetFileDescriptor afd = new AssetFileDescriptor(write, 0, -1);

+            new RequestHandler(getContext(), uri, afd.createOutputStream()).start();

+            return pipes[0];

+        } catch (IOException e) {

+            Log.e(TAG, "Failed to handle request: " + uri, e);

+            return null;

+        }

+    }

+

+}

diff --git a/src/com/android/browser/homepages/RequestHandler.java b/src/com/android/browser/homepages/RequestHandler.java
new file mode 100644
index 0000000..a53fb52
--- /dev/null
+++ b/src/com/android/browser/homepages/RequestHandler.java
@@ -0,0 +1,145 @@
+

+/*

+ * 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.homepages;

+

+import com.android.browser.R;

+

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.OutputStream;

+import java.util.regex.Matcher;

+import java.util.regex.Pattern;

+

+import android.content.Context;

+import android.content.UriMatcher;

+import android.content.res.Resources;

+import android.database.Cursor;

+import android.net.Uri;

+import android.provider.Browser;

+import android.util.Base64;

+import android.util.Log;

+

+public class RequestHandler extends Thread {

+

+    private static final String TAG = "RequestHandler";

+    private static final int INDEX = 1;

+    private static final int RESOURCE = 2;

+    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

+

+    Uri mUri;

+    Context mContext;

+    OutputStream mOutput;

+

+    static {

+        sUriMatcher.addURI(HomeProvider.AUTHORITY, "/", INDEX);

+        sUriMatcher.addURI(HomeProvider.AUTHORITY, "res/*/*", RESOURCE);

+    }

+

+    public RequestHandler(Context context, Uri uri, OutputStream out) {

+        mUri = uri;

+        mContext = context;

+        mOutput = out;

+    }

+

+    @Override

+    public void run() {

+        super.run();

+        try {

+            doHandleRequest();

+        } catch (Exception e) {

+            Log.e(TAG, "Failed to handle request: " + mUri, e);

+        } finally {

+            cleanup();

+        }

+    }

+

+    void doHandleRequest() throws IOException {

+        int match = sUriMatcher.match(mUri);

+        switch (match) {

+        case INDEX:

+            writeTemplatedIndex();

+            break;

+        case RESOURCE:

+            writeResource(getUriResourcePath());

+            break;

+        }

+    }

+

+    void writeTemplatedIndex() throws IOException {

+        Template t = Template.getCachedTemplate(mContext, R.raw.most_visited);

+        Cursor cursor = mContext.getContentResolver().query(Browser.BOOKMARKS_URI,

+                new String[] { "DISTINCT url", "title", "thumbnail" },

+                "(visits > 0 OR bookmark = 1) AND url NOT LIKE 'content:%' AND thumbnail IS NOT NULL", null, "visits DESC LIMIT 12");

+

+        t.assignLoop("most_visited", new Template.CursorListEntityWrapper(cursor) {

+            @Override

+            public void writeValue(OutputStream stream, String key) throws IOException {

+                Cursor cursor = getCursor();

+                if (key.equals("url")) {

+                    stream.write(cursor.getString(0).getBytes());

+                } else if (key.equals("title")) {

+                    stream.write(cursor.getString(1).getBytes());

+                } else if (key.equals("thumbnail")) {

+                    stream.write("data:image/png;base64,".getBytes());

+                    byte[] thumb = cursor.getBlob(2);

+                    stream.write(Base64.encode(thumb, Base64.DEFAULT));

+                }

+            }

+        });

+        t.write(mOutput);

+    }

+

+    String getUriResourcePath() {

+        final Pattern pattern = Pattern.compile("/?res/([\\w/]+)");

+        Matcher m = pattern.matcher(mUri.getPath());

+        if (m.matches()) {

+            return m.group(1);

+        } else {

+            return mUri.getPath();

+        }

+    }

+

+    void writeResource(String fileName) throws IOException {

+        Resources res = mContext.getResources();

+        int id = res.getIdentifier(fileName, null, mContext.getPackageName());

+        if (id != 0) {

+            InputStream in = res.openRawResource(id);

+            byte[] buf = new byte[4096];

+            int read;

+            while ((read = in.read(buf)) > 0) {

+                mOutput.write(buf, 0, read);

+            }

+        }

+    }

+

+    void writeString(String str) throws IOException {

+        mOutput.write(str.getBytes());

+    }

+

+    void writeString(String str, int offset, int count) throws IOException {

+        mOutput.write(str.getBytes(), offset, count);

+    }

+

+    void cleanup() {

+        try {

+            mOutput.close();

+        } catch (Exception e) {

+            Log.e(TAG, "Failed to close pipe!", e);

+        }

+    }

+

+}

diff --git a/src/com/android/browser/homepages/Template.java b/src/com/android/browser/homepages/Template.java
new file mode 100644
index 0000000..c1a6b0e
--- /dev/null
+++ b/src/com/android/browser/homepages/Template.java
@@ -0,0 +1,279 @@
+

+/*

+ * 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.homepages;

+

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.OutputStream;

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.regex.Matcher;

+import java.util.regex.Pattern;

+

+import android.content.Context;

+import android.content.res.Resources;

+import android.database.Cursor;

+import android.util.TypedValue;

+

+public class Template {

+

+    private static HashMap<Integer, Template> sCachedTemplates = new HashMap<Integer, Template>();

+

+    public static Template getCachedTemplate(Context context, int id) {

+        synchronized (sCachedTemplates) {

+            Template template = sCachedTemplates.get(id);

+            if (template == null) {

+                template = new Template(context, id);

+                sCachedTemplates.put(id, template);

+            }

+            // Return a copy so that we don't share data

+            return template.copy();

+        }

+    }

+

+    interface Entity {

+        void write(OutputStream stream, EntityData params) throws IOException;

+    }

+

+    interface EntityData {

+        void writeValue(OutputStream stream, String key) throws IOException;

+        ListEntityIterator getListIterator(String key);

+    }

+

+    interface ListEntityIterator extends EntityData {

+        void reset();

+        boolean moveToNext();

+    }

+

+    static class StringEntity implements Entity {

+

+        byte[] mValue;

+

+        public StringEntity(String value) {

+            mValue = value.getBytes();

+        }

+

+        @Override

+        public void write(OutputStream stream, EntityData params) throws IOException {

+            stream.write(mValue);

+        }

+

+    }

+

+    static class SimpleEntity implements Entity {

+

+        String mKey;

+

+        public SimpleEntity(String key) {

+            mKey = key;

+        }

+

+        @Override

+        public void write(OutputStream stream, EntityData params) throws IOException {

+            params.writeValue(stream, mKey);

+        }

+

+    }

+

+    static class ListEntity implements Entity {

+

+        String mKey;

+        Template mSubTemplate;

+

+        public ListEntity(Context context, String key, String subTemplate) {

+            mKey = key;

+            mSubTemplate = new Template(context, subTemplate);

+        }

+

+        @Override

+        public void write(OutputStream stream, EntityData params) throws IOException {

+            ListEntityIterator iter = params.getListIterator(mKey);

+            iter.reset();

+            while (iter.moveToNext()) {

+                mSubTemplate.write(stream, iter);

+            }

+        }

+

+    }

+

+    public abstract static class CursorListEntityWrapper implements ListEntityIterator {

+

+        private Cursor mCursor;

+

+        public CursorListEntityWrapper(Cursor cursor) {

+            mCursor = cursor;

+        }

+

+        @Override

+        public boolean moveToNext() {

+            return mCursor.moveToNext();

+        }

+

+        @Override

+        public void reset() {

+            mCursor.moveToPosition(-1);

+        }

+

+        @Override

+        public ListEntityIterator getListIterator(String key) {

+            return null;

+        }

+

+        public Cursor getCursor() {

+            return mCursor;

+        }

+

+    }

+

+    static class HashMapEntityData implements EntityData {

+

+        HashMap<String, Object> mData;

+

+        public HashMapEntityData(HashMap<String, Object> map) {

+            mData = map;

+        }

+

+        @Override

+        public ListEntityIterator getListIterator(String key) {

+            return (ListEntityIterator) mData.get(key);

+        }

+

+        @Override

+        public void writeValue(OutputStream stream, String key) throws IOException {

+            stream.write((byte[]) mData.get(key));

+        }

+

+    }

+

+    private List<Entity> mTemplate;

+    private HashMap<String, Object> mData = new HashMap<String, Object>();

+    private Template(Context context, int tid) {

+        this(context, readRaw(context, tid));

+    }

+

+    private Template(Context context, String template) {

+        mTemplate = new ArrayList<Entity>();

+        template = replaceConsts(context, template);

+        parseTemplate(context, template);

+    }

+

+    private Template(Template copy) {

+        mTemplate = copy.mTemplate;

+    }

+

+    Template copy() {

+        return new Template(this);

+    }

+

+    void parseTemplate(Context context, String template) {

+        final Pattern pattern = Pattern.compile("<%([=\\{])\\s*(\\w+)\\s*%>");

+        Matcher m = pattern.matcher(template);

+        int start = 0;

+        while (m.find()) {

+            String static_part = template.substring(start, m.start());

+            if (static_part.length() > 0) {

+                mTemplate.add(new StringEntity(static_part));

+            }

+            String type = m.group(1);

+            String name = m.group(2);

+            if (type.equals("=")) {

+                mTemplate.add(new SimpleEntity(name));

+            } else if (type.equals("{")) {

+                Pattern p = Pattern.compile("<%\\}\\s*" + Pattern.quote(name) + "\\s*%>");

+                Matcher end_m = p.matcher(template);

+                if (end_m.find(m.end())) {

+                    start = m.end();

+                    m.region(end_m.end(), template.length());

+                    String subTemplate = template.substring(start, end_m.start());

+                    mTemplate.add(new ListEntity(context, name, subTemplate));

+                    start = end_m.end();

+                    continue;

+                }

+            }

+            start = m.end();

+        }

+        String static_part = template.substring(start, template.length());

+        if (static_part.length() > 0) {

+            mTemplate.add(new StringEntity(static_part));

+        }

+    }

+

+    public void assign(String name, String value) {

+        mData.put(name, value.getBytes());

+    }

+

+    public void assignLoop(String name, ListEntityIterator iter) {

+        mData.put(name, iter);

+    }

+

+    public void write(OutputStream stream) throws IOException {

+        write(stream, new HashMapEntityData(mData));

+    }

+

+    public void write(OutputStream stream, EntityData data) throws IOException {

+        for (Entity ent : mTemplate) {

+            ent.write(stream, data);

+        }

+    }

+

+    private static String replaceConsts(Context context, String template) {

+        final Pattern pattern = Pattern.compile("<%@\\s*(\\w+/\\w+)\\s*%>");

+        final Resources res = context.getResources();

+        final String packageName = context.getPackageName();

+        Matcher m = pattern.matcher(template);

+        StringBuffer sb = new StringBuffer();

+        while (m.find()) {

+            String name = m.group(1);

+            if (name.startsWith("drawable/")) {

+                m.appendReplacement(sb, "res/" + name);

+            } else {

+                int id = res.getIdentifier(name, null, packageName);

+                if (id != 0) {

+                    TypedValue value = new TypedValue();

+                    res.getValue(id, value, true);

+                    String replacement;

+                    if (value.type == TypedValue.TYPE_DIMENSION) {

+                        float dimen = res.getDimension(id);

+                        int dimeni = (int) dimen;

+                        if (dimeni == dimen)

+                            replacement = Integer.toString(dimeni);

+                        else

+                            replacement = Float.toString(dimen);

+                    } else {

+                        replacement = value.coerceToString().toString();

+                    }

+                    m.appendReplacement(sb, replacement);

+                }

+            }

+        }

+        m.appendTail(sb);

+        return sb.toString();

+    }

+

+    private static String readRaw(Context context, int id) {

+        InputStream ins = context.getResources().openRawResource(id);

+        try {

+            byte[] buf = new byte[ins.available()];

+            ins.read(buf);

+            return new String(buf, "utf-8");

+        } catch (IOException ex) {

+            return "<html><body>Error</body></html>";

+        }

+    }

+

+}

diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index 919a135..ff00c56 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -402,6 +402,7 @@
 
         @Override
         public void onOpen(SQLiteDatabase db) {
+            db.enableWriteAheadLogging();
             mSyncHelper.onDatabaseOpened(db);
         }
 
@@ -478,6 +479,9 @@
         }
 
         private byte[] readRaw(Resources res, int id) throws IOException {
+            if (id == 0) {
+                return null;
+            }
             InputStream is = res.openRawResource(id);
             try {
                 ByteArrayOutputStream bos = new ByteArrayOutputStream();