Restore overhaul

 Bug: 5069192
 Store thumbnails in a database restored async for each tab
 Fix restoring a tab not restoring its current state

Change-Id: I2c14e352638aac0ef766fb3bf4036ff220c53ecd
diff --git a/res/values-sw600dp/dimensions.xml b/res/values-sw600dp/dimensions.xml
index 2c4aaae..bc87333 100644
--- a/res/values-sw600dp/dimensions.xml
+++ b/res/values-sw600dp/dimensions.xml
@@ -37,4 +37,6 @@
     <dimen name="suggest_item_padding">16dp</dimen>
     <dimen name="toolbar_height">48dip</dimen>
     <dimen name="progress_bar_margin">-11dip</dimen>
+    <dimen name="tab_thumbnail_width">@dimen/qc_thumb_width</dimen>
+    <dimen name="tab_thumbnail_height">@dimen/qc_thumb_height</dimen>
 </resources>
diff --git a/res/values/dimensions.xml b/res/values/dimensions.xml
index 3b57f19..8fca68a 100644
--- a/res/values/dimensions.xml
+++ b/res/values/dimensions.xml
@@ -67,7 +67,8 @@
     <dimen name="menu_width">240dip</dimen>
     <dimen name="menu_height">32dip</dimen>
     <dimen name="toolbar_height">52dip</dimen>
-    <dimen name="tab_capture_size">160dp</dimen>
+    <dimen name="tab_thumbnail_width">@dimen/nav_tab_width</dimen>
+    <dimen name="tab_thumbnail_height">@dimen/nav_tab_height</dimen>
     <dimen name="nav_tab_width">240dip</dimen>
     <dimen name="nav_tab_height">160dip</dimen>
     <dimen name="nav_tab_text_normal">18sp</dimen>
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index b270dea..091126c 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -738,20 +738,6 @@
         warning.show();
     }
 
-    protected void captureTab(final Tab tab) {
-        captureTab(tab,
-                (int) mActivity.getResources()
-                        .getDimension(R.dimen.qc_thumb_width),
-                (int) mActivity.getResources()
-                        .getDimension(R.dimen.qc_thumb_height));
-    }
-
-    protected void captureTab(final Tab tab, int width, int height) {
-        if ((tab == null) || (tab.getWebView() == null)) return;
-        Bitmap sshot = Controller.createScreenshot(tab, width, height);
-        tab.setScreenshot(sshot);
-    }
-
     protected WebView getWebView() {
         if (mActiveTab != null) {
             return mActiveTab.getWebView();
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 0df2e94..6ec6071 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -112,7 +112,7 @@
     protected void onNewIntent(Intent intent) {
         if (ACTION_RESTART.equals(intent.getAction())) {
             Bundle outState = new Bundle();
-            mController.onSaveInstanceState(outState, true);
+            mController.onSaveInstanceState(outState);
             finish();
             getApplicationContext().startActivity(
                     new Intent(getApplicationContext(), BrowserActivity.class)
@@ -163,7 +163,7 @@
         if (LOGV_ENABLED) {
             Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
         }
-        mController.onSaveInstanceState(outState, true);
+        mController.onSaveInstanceState(outState);
     }
 
     @Override
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index b3be618..92f448c 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -80,6 +80,7 @@
 import com.android.browser.UI.ComboViews;
 import com.android.browser.UI.DropdownChangeListener;
 import com.android.browser.provider.BrowserProvider;
+import com.android.browser.provider.BrowserProvider2.Thumbnails;
 import com.android.browser.provider.SnapshotProvider.Snapshots;
 import com.android.browser.search.SearchEngine;
 import com.android.common.Search;
@@ -87,6 +88,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.net.URLEncoder;
+import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.HashMap;
 import java.util.List;
@@ -309,6 +311,7 @@
     private void onPreloginFinished(Bundle icicle, Intent intent, long currentTabId,
             boolean restoreIncognitoTabs) {
         if (currentTabId == -1) {
+            BackgroundHandler.execute(new PruneThumbnails(mActivity, null));
             final Bundle extra = intent.getExtras();
             // Create an initial tab.
             // If the intent is ACTION_VIEW and data is not null, the Browser is
@@ -335,7 +338,13 @@
         } else {
             mTabControl.restoreState(icicle, currentTabId, restoreIncognitoTabs,
                     mUi.needsRestoreAllTabs());
-            mUi.updateTabs(mTabControl.getTabs());
+            List<Tab> tabs = mTabControl.getTabs();
+            ArrayList<Long> restoredTabs = new ArrayList<Long>(tabs.size());
+            for (Tab t : tabs) {
+                restoredTabs.add(t.getId());
+            }
+            BackgroundHandler.execute(new PruneThumbnails(mActivity, restoredTabs));
+            mUi.updateTabs(tabs);
             // TabControl.restoreState() will create a new tab even if
             // restoring the state fails.
             setActiveTab(mTabControl.getCurrentTab());
@@ -357,6 +366,38 @@
         }
     }
 
+    private static class PruneThumbnails implements Runnable {
+        private Context mContext;
+        private List<Long> mIds;
+
+        PruneThumbnails(Context context, List<Long> preserveIds) {
+            mContext = context.getApplicationContext();
+            mIds = preserveIds;
+        }
+
+        @Override
+        public void run() {
+            ContentResolver cr = mContext.getContentResolver();
+            if (mIds == null || mIds.size() == 0) {
+                cr.delete(Thumbnails.CONTENT_URI, null, null);
+            } else {
+                int length = mIds.size();
+                StringBuilder where = new StringBuilder();
+                where.append(Thumbnails._ID);
+                where.append(" not in (");
+                for (int i = 0; i < length; i++) {
+                    where.append(mIds.get(i));
+                    if (i < (length - 1)) {
+                        where.append(",");
+                    }
+                }
+                where.append(")");
+                cr.delete(Thumbnails.CONTENT_URI, where.toString(), null);
+            }
+        }
+
+    }
+
     @Override
     public WebViewFactory getWebViewFactory() {
         return mFactory;
@@ -612,7 +653,7 @@
 
     }
 
-    void onSaveInstanceState(Bundle outState, boolean saveImages) {
+    void onSaveInstanceState(Bundle outState) {
         // the default implementation requires each view to have an id. As the
         // browser handles the state itself and it doesn't use id for the views,
         // don't call the default implementation. Otherwise it will trigger the
@@ -620,7 +661,7 @@
         // focused view XXX has no id".
 
         // Save all the tabs
-        mTabControl.saveState(outState, false);
+        mTabControl.saveState(outState);
         if (!outState.isEmpty()) {
             // Save time so that we know how old incognito tabs (if any) are.
             outState.putSerializable("lastActiveDate", Calendar.getInstance());
@@ -1902,13 +1943,6 @@
                 R.dimen.bookmarkThumbnailHeight);
     }
 
-    static Bitmap createScreenshot(Tab tab, int width, int height) {
-        if ((tab != null) && (tab.getWebView() != null)) {
-            return createScreenshot(tab.getWebView(), width, height);
-        }
-        return null;
-    }
-
     static Bitmap createScreenshot(WebView view, int width, int height) {
         // We render to a bitmap 2x the desired size so that we can then
         // re-scale it with filtering since canvas.scale doesn't filter
@@ -2646,4 +2680,9 @@
         return true;
     }
 
+    @Override
+    public boolean shouldCaptureThumbnails() {
+        return mUi.shouldCaptureThumbnails();
+    }
+
 }
diff --git a/src/com/android/browser/CrashRecoveryHandler.java b/src/com/android/browser/CrashRecoveryHandler.java
index 02636c0..fdcdbc6 100644
--- a/src/com/android/browser/CrashRecoveryHandler.java
+++ b/src/com/android/browser/CrashRecoveryHandler.java
@@ -133,7 +133,7 @@
         public void run() {
             try {
                 final Bundle state = new Bundle();
-                mController.onSaveInstanceState(state, false);
+                mController.onSaveInstanceState(state);
                 Message.obtain(mBackgroundHandler, MSG_WRITE_STATE, state)
                         .sendToTarget();
                 // Remove any queued up saves
diff --git a/src/com/android/browser/PhoneUi.java b/src/com/android/browser/PhoneUi.java
index 28db6f0..1c9d5a0 100644
--- a/src/com/android/browser/PhoneUi.java
+++ b/src/com/android/browser/PhoneUi.java
@@ -276,4 +276,9 @@
         }
     }
 
+    @Override
+    public boolean shouldCaptureThumbnails() {
+        return true;
+    }
+
 }
diff --git a/src/com/android/browser/PieControlPhone.java b/src/com/android/browser/PieControlPhone.java
index c4b28fa..0b62cef 100644
--- a/src/com/android/browser/PieControlPhone.java
+++ b/src/com/android/browser/PieControlPhone.java
@@ -72,7 +72,7 @@
 
     private void buildTabs() {
         final List<Tab> tabs = mUiController.getTabs();
-        mUi.captureTab(mUi.getActiveTab());
+        mUi.getActiveTab().capture();
         mTabAdapter.setTabs(tabs);
         PieStackView sym = (PieStackView) mShowTabs.getPieView();
         sym.setCurrent(mUiController.getTabControl().getCurrentPosition());
diff --git a/src/com/android/browser/PieControlXLarge.java b/src/com/android/browser/PieControlXLarge.java
index a036e0d..95f586e 100644
--- a/src/com/android/browser/PieControlXLarge.java
+++ b/src/com/android/browser/PieControlXLarge.java
@@ -112,7 +112,7 @@
 
     private void buildTabs() {
         final List<Tab> tabs = mUiController.getTabs();
-        mUi.captureTab(mUi.getActiveTab());
+        mUi.getActiveTab().capture();
         mTabAdapter.setTabs(tabs);
         PieStackView sym = (PieStackView) mShowTabs.getPieView();
         sym.setCurrent(mUiController.getTabControl().getCurrentPosition());
diff --git a/src/com/android/browser/PreloadController.java b/src/com/android/browser/PreloadController.java
index 652ea8e..08e223f 100644
--- a/src/com/android/browser/PreloadController.java
+++ b/src/com/android/browser/PreloadController.java
@@ -276,4 +276,9 @@
         if (LOGD_ENABLED) Log.d(LOGTAG, "hideAutoLogin()");
     }
 
+    @Override
+    public boolean shouldCaptureThumbnails() {
+        return false;
+    }
+
 }
diff --git a/src/com/android/browser/SnapshotTab.java b/src/com/android/browser/SnapshotTab.java
index e57502f..bd6dd5b 100644
--- a/src/com/android/browser/SnapshotTab.java
+++ b/src/com/android/browser/SnapshotTab.java
@@ -22,6 +22,7 @@
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Bundle;
 import android.util.Log;
 import android.webkit.WebView;
 
@@ -44,7 +45,7 @@
     private boolean mIsLive;
 
     public SnapshotTab(WebViewController wvcontroller, long snapshotId) {
-        super(wvcontroller, null);
+        super(wvcontroller, null, null);
         mSnapshotId = snapshotId;
         mWebViewFactory = mWebViewController.getWebViewFactory();
         WebView web = mWebViewFactory.createWebView(false);
@@ -98,8 +99,8 @@
     }
 
     @Override
-    boolean saveState() {
-        return false;
+    Bundle saveState() {
+        return null;
     }
 
     public long getDateCreated() {
@@ -198,4 +199,15 @@
         }
 
     }
+
+    @Override
+    protected void persistThumbnail() {
+        // Nope
+    }
+
+    @Override
+    protected void deleteThumbnail() {
+        // Nope
+    }
+
 }
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index beac2ff..a4bcc99 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -20,11 +20,13 @@
 import android.app.AlertDialog;
 import android.app.SearchManager;
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnCancelListener;
 import android.content.Intent;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
@@ -53,7 +55,6 @@
 import android.webkit.SslErrorHandler;
 import android.webkit.URLUtil;
 import android.webkit.ValueCallback;
-import android.webkit.WebBackForwardList;
 import android.webkit.WebBackForwardListClient;
 import android.webkit.WebChromeClient;
 import android.webkit.WebHistoryItem;
@@ -68,10 +69,12 @@
 import android.widget.Toast;
 
 import com.android.browser.homepages.HomeProvider;
+import com.android.browser.provider.BrowserProvider2.Thumbnails;
 import com.android.browser.provider.SnapshotProvider.Snapshots;
 import com.android.common.speech.LoggingEvents;
 
 import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -95,6 +98,8 @@
     private static final int MSG_CAPTURE = 42;
     private static final int CAPTURE_DELAY = 500;
 
+    private static Bitmap sDefaultFavicon;
+
     public enum LockIcon {
         LOCK_ICON_UNSECURE,
         LOCK_ICON_SECURE,
@@ -161,6 +166,13 @@
     private Bitmap mCapture;
     private Handler mHandler;
 
+    private static synchronized Bitmap getDefaultFavicon(Context context) {
+        if (sDefaultFavicon == null) {
+            sDefaultFavicon = BitmapFactory.decodeResource(
+                    context.getResources(), R.drawable.app_web_browser_sm);
+        }
+        return sDefaultFavicon;
+    }
 
     // All the state needed for a page
     protected static class PageState {
@@ -179,8 +191,7 @@
                 mOriginalUrl = mUrl = "";
                 mTitle = c.getString(R.string.new_tab);
             }
-            mFavicon = BitmapFactory.decodeResource(
-                    c.getResources(), R.drawable.app_web_browser_sm);
+            mFavicon = null;
             mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
         }
 
@@ -192,13 +203,9 @@
             } else {
                 mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
             }
-            if (favicon != null) {
-                mFavicon = favicon;
-            } else {
-                mFavicon = BitmapFactory.decodeResource(
-                        c.getResources(), R.drawable.app_web_browser_sm);
-            }
+            mFavicon = favicon;
         }
+
     }
 
     // The current/loading page's state
@@ -211,7 +218,6 @@
     static final String PARENTTAB = "parentTab";
     static final String APPID = "appid";
     static final String INCOGNITO = "privateBrowsingEnabled";
-    static final String SCREENSHOT = "screenshot";
     static final String USERAGENT = "useragent";
 
     // -------------------------------------------------------------------------
@@ -576,19 +582,7 @@
                         url, SystemClock.uptimeMillis() - mLoadStartTime);
             }
             mInPageLoad = false;
-            // Sync state (in case of stop/timeout)
-            mCurrentState.mUrl = view.getUrl();
-            if (mCurrentState.mUrl == null) {
-                mCurrentState.mUrl = url != null ? url : "";
-            }
-            mCurrentState.mOriginalUrl = view.getOriginalUrl();
-            mCurrentState.mTitle = view.getTitle();
-            mCurrentState.mFavicon = view.getFavicon();
-            if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) {
-                // In case we stop when loading an HTTPS page from an HTTP page
-                // but before a provisional load occurred
-                mCurrentState.mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
-            }
+            syncCurrentState(view, url);
             mWebViewController.onPageFinished(Tab.this);
         }
 
@@ -894,6 +888,22 @@
 
     };
 
+    private void syncCurrentState(WebView view, String url) {
+        // Sync state (in case of stop/timeout)
+        mCurrentState.mUrl = view.getUrl();
+        if (mCurrentState.mUrl == null) {
+            mCurrentState.mUrl = url != null ? url : "";
+        }
+        mCurrentState.mOriginalUrl = view.getOriginalUrl();
+        mCurrentState.mTitle = view.getTitle();
+        mCurrentState.mFavicon = view.getFavicon();
+        if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) {
+            // In case we stop when loading an HTTPS page from an HTTP page
+            // but before a provisional load occurred
+            mCurrentState.mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
+        }
+    }
+
     // Called by DeviceAccountLogin when the Tab needs to have the auto-login UI
     // displayed.
     void setDeviceAccountLogin(DeviceAccountLogin login) {
@@ -1355,11 +1365,16 @@
 
     // -------------------------------------------------------------------------
 
-    // TODO temporarily use activity here
-    // remove later
-
     // Construct a new tab
     Tab(WebViewController wvcontroller, WebView w) {
+        this(wvcontroller, w, null);
+    }
+
+    Tab(WebViewController wvcontroller, Bundle state) {
+        this(wvcontroller, null, state);
+    }
+
+    Tab(WebViewController wvcontroller, WebView w, Bundle state) {
         mWebViewController = wvcontroller;
         mContext = mWebViewController.getContext();
         mSettings = BrowserSettings.getInstance();
@@ -1393,21 +1408,46 @@
             }
         };
 
+        mCaptureWidth = mContext.getResources().getDimensionPixelSize(
+                R.dimen.tab_thumbnail_width);
+        mCaptureHeight = mContext.getResources().getDimensionPixelSize(
+                R.dimen.tab_thumbnail_height);
+        updateShouldCaptureThumbnails();
+        restoreState(state);
         setWebView(w);
-        mCaptureWidth = mContext.getResources().getDimensionPixelSize(R.dimen.nav_tab_width);
-        mCaptureHeight = mContext.getResources().getDimensionPixelSize(R.dimen.nav_tab_height);
-        mCapture = Bitmap.createBitmap(mCaptureWidth, mCaptureHeight,
-                Bitmap.Config.RGB_565);
         mHandler = new Handler() {
             public void handleMessage(Message m) {
-                Tab.this.capture();
+                switch (m.what) {
+                case MSG_CAPTURE:
+                    capture();
+                    break;
+                }
             }
         };
+    }
 
+    public void updateShouldCaptureThumbnails() {
+        if (mWebViewController.shouldCaptureThumbnails()) {
+            synchronized (Tab.this) {
+                if (mCapture == null) {
+                    mCapture = Bitmap.createBitmap(mCaptureWidth, mCaptureHeight,
+                            Bitmap.Config.RGB_565);
+                    if (mInForeground) {
+                        postCapture();
+                    }
+                }
+            }
+        } else {
+            synchronized (Tab.this) {
+                mCapture = null;
+                deleteThumbnail();
+            }
+        }
     }
 
     public void setController(WebViewController ctl) {
         mWebViewController = ctl;
+        updateShouldCaptureThumbnails();
     }
 
     public void setId(long id) {
@@ -1435,6 +1475,13 @@
 
         mWebViewController.onSetWebView(this, w);
 
+        if (mMainView != null) {
+            if (w != null) {
+                syncCurrentState(w, null);
+            } else {
+                mCurrentState = new PageState(mContext, false);
+            }
+        }
         // set the new one
         mMainView = w;
         // attach the WebViewClient, WebChromeClient and DownloadListener
@@ -1448,6 +1495,10 @@
             mMainView.setDownloadListener(mDownloadListener);
             mMainView.setWebBackForwardListClient(mWebBackForwardListClient);
             mMainView.setPictureListener(this);
+            if (mSavedState != null) {
+                mMainView.restoreState(mSavedState);
+                mSavedState = null;
+            }
         }
     }
 
@@ -1480,6 +1531,7 @@
         if (mParent != null) {
             mParent.mChildren.remove(this);
         }
+        deleteThumbnail();
     }
 
     /**
@@ -1739,7 +1791,10 @@
      * Get the favicon of this tab.
      */
     Bitmap getFavicon() {
-        return mCurrentState.mFavicon;
+        if (mCurrentState.mFavicon != null) {
+            return mCurrentState.mFavicon;
+        }
+        return getDefaultFavicon(mContext);
     }
 
     public boolean isBookmarkedSite() {
@@ -1796,43 +1851,19 @@
     }
 
     /**
-     * Get the cached saved state bundle.
-     * @return cached state bundle
+     * @return The Bundle with the tab's state if it can be saved, otherwise null
      */
-    Bundle getSavedState() {
-        return mSavedState;
-    }
-
-    Bundle getSavedState(boolean saveImages) {
-        if (saveImages && mCapture != null) {
-            Bundle b = new Bundle(mSavedState);
-            b.putParcelable(SCREENSHOT, mCapture);
-            return b;
-        }
-        return mSavedState;
-    }
-
-    /**
-     * Set the saved state.
-     */
-    void setSavedState(Bundle state) {
-        mSavedState = state;
-    }
-
-    /**
-     * @return TRUE if succeed in saving the state.
-     */
-    boolean saveState() {
+    public Bundle saveState() {
         // If the WebView is null it means we ran low on memory and we already
         // stored the saved state in mSavedState.
         if (mMainView == null) {
-            return mSavedState != null;
+            return mSavedState;
         }
         // If the tab is the homepage or has no URL, don't save it
         String homepage = BrowserSettings.getInstance().getHomePage();
         if (TextUtils.equals(homepage, mCurrentState.mUrl)
                 || TextUtils.isEmpty(mCurrentState.mUrl)) {
-            return false;
+            return null;
         }
 
         mSavedState = new Bundle();
@@ -1841,6 +1872,7 @@
         mSavedState.putLong(ID, mId);
         mSavedState.putString(CURRURL, mCurrentState.mUrl);
         mSavedState.putString(CURRTITLE, mCurrentState.mTitle);
+        mSavedState.putBoolean(INCOGNITO, mMainView.isPrivateBrowsingEnabled());
         if (mAppId != null) {
             mSavedState.putString(APPID, mAppId);
         }
@@ -1850,35 +1882,35 @@
         }
         mSavedState.putBoolean(USERAGENT,
                 mSettings.hasDesktopUseragent(getWebView()));
-        return true;
+        return mSavedState;
     }
 
     /*
      * Restore the state of the tab.
      */
-    boolean restoreState(Bundle b) {
-        if (b == null) {
-            return false;
+    private void restoreState(Bundle b) {
+        mSavedState = b;
+        if (mSavedState == null) {
+            return;
         }
         // Restore the internal state even if the WebView fails to restore.
         // This will maintain the app id, original url and close-on-exit values.
-        mSavedState = null;
         mId = b.getLong(ID);
         mAppId = b.getString(APPID);
-        final Bitmap sshot = b.getParcelable(SCREENSHOT);
-        if (sshot != null) {
-            mCapture = sshot;
-        }
         if (b.getBoolean(USERAGENT)
                 != mSettings.hasDesktopUseragent(getWebView())) {
             mSettings.toggleDesktopUseragent(getWebView());
         }
-
-        final WebBackForwardList list = mMainView.restoreState(b);
-        if (list == null) {
-            return false;
+        String url = b.getString(CURRURL);
+        String title = b.getString(CURRTITLE);
+        boolean incognito = b.getBoolean(INCOGNITO);
+        mCurrentState = new PageState(mContext, incognito, url, null);
+        mCurrentState.mTitle = title;
+        synchronized (Tab.this) {
+            if (mCapture != null) {
+                BackgroundHandler.execute(mLoadThumbnail);
+            }
         }
-        return true;
     }
 
     public void updateBookmarkedStatus() {
@@ -1896,12 +1928,10 @@
         }
     };
 
-    public void setScreenshot(Bitmap screenshot) {
-        mCapture = screenshot;
-    }
-
     public Bitmap getScreenshot() {
-        return mCapture;
+        synchronized (Tab.this) {
+            return mCapture;
+        }
     }
 
     public boolean isSnapshot() {
@@ -1963,11 +1993,16 @@
         float scale = mCaptureWidth / (float) mMainView.getWidth();
         c.scale(scale, scale, left, top);
         mMainView.draw(c);
+        persistThumbnail();
     }
 
     @Override
     public void onNewPicture(WebView view, Picture picture) {
         //update screenshot
+        postCapture();
+    }
+
+    private void postCapture() {
         if (!mHandler.hasMessages(MSG_CAPTURE)) {
             mHandler.sendEmptyMessageDelayed(MSG_CAPTURE, CAPTURE_DELAY);
         }
@@ -1993,4 +2028,84 @@
         }
     }
 
+    protected void persistThumbnail() {
+        BackgroundHandler.execute(mSaveThumbnail);
+    }
+
+    protected void deleteThumbnail() {
+        BackgroundHandler.execute(mDeleteThumbnail);
+    }
+
+    private void updateCaptureFromBlob(byte[] blob) {
+        synchronized (Tab.this) {
+            if (mCapture == null) {
+                return;
+            }
+            mCapture.copyPixelsFromBuffer(ByteBuffer.wrap(blob));
+        }
+    }
+
+    private byte[] getCaptureBlob() {
+        synchronized (Tab.this) {
+            if (mCapture == null) {
+                return null;
+            }
+            ByteBuffer buffer = ByteBuffer.allocate(mCapture.getByteCount());
+            mCapture.copyPixelsToBuffer(buffer);
+            return buffer.array();
+        }
+    }
+
+    private Runnable mSaveThumbnail = new Runnable() {
+
+        @Override
+        public void run() {
+            byte[] blob = getCaptureBlob();
+            if (blob == null) {
+                return;
+            }
+            ContentResolver cr = mContext.getContentResolver();
+            ContentValues values = new ContentValues();
+            values.put(Thumbnails._ID, mId);
+            values.put(Thumbnails.THUMBNAIL, blob);
+            cr.insert(Thumbnails.CONTENT_URI, values);
+        }
+    };
+
+    private Runnable mDeleteThumbnail = new Runnable() {
+
+        @Override
+        public void run() {
+            ContentResolver cr = mContext.getContentResolver();
+            try {
+                cr.delete(ContentUris.withAppendedId(Thumbnails.CONTENT_URI, mId),
+                        null, null);
+            } catch (Throwable t) {}
+        }
+    };
+
+    private Runnable mLoadThumbnail = new Runnable() {
+
+        @Override
+        public void run() {
+            ContentResolver cr = mContext.getContentResolver();
+            Cursor c = null;
+            try {
+                Uri uri = ContentUris.withAppendedId(Thumbnails.CONTENT_URI, mId);
+                c = cr.query(uri, new String[] {Thumbnails._ID,
+                        Thumbnails.THUMBNAIL}, null, null, null);
+                if (c.moveToFirst()) {
+                    byte[] data = c.getBlob(1);
+                    if (data != null && data.length > 0) {
+                        updateCaptureFromBlob(data);
+                    }
+                }
+            } finally {
+                if (c != null) {
+                    c.close();
+                }
+            }
+        }
+    };
+
 }
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index cd8da2e..b708841 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -186,6 +186,12 @@
      *         number of open tabs.
      */
     Tab createNewTab(boolean privateBrowsing) {
+        return createNewTab(null, privateBrowsing);
+    }
+
+    Tab createNewTab(Bundle state, boolean privateBrowsing) {
+        int size = mTabs.size();
+        // Return false if we have maxed out on tabs
         if (!canCreateNewTab()) {
             return null;
         }
@@ -193,7 +199,7 @@
         final WebView w = createNewWebView(privateBrowsing);
 
         // Create a new tab and add it to the tab list
-        Tab t = new Tab(mController, w);
+        Tab t = new Tab(mController, w, state);
         t.setId(getNextId());
         mTabs.add(t);
         // Initially put the tab in the background.
@@ -288,7 +294,7 @@
      * @param outState
      * @param saveImages
      */
-    void saveState(Bundle outState, boolean saveImages) {
+    void saveState(Bundle outState) {
         final int numTabs = getTabCount();
         if (numTabs == 0) {
             return;
@@ -296,10 +302,10 @@
         long[] ids = new long[numTabs];
         int i = 0;
         for (Tab tab : mTabs) {
-            if (tab.saveState()) {
+            Bundle tabState = tab.saveState();
+            if (tabState != null) {
                 ids[i++] = tab.getId();
-                outState.putBundle(Long.toString(tab.getId()),
-                        tab.getSavedState(saveImages));
+                outState.putBundle(Long.toString(tab.getId()), tabState);
             } else {
                 ids[i++] = -1;
             }
@@ -329,7 +335,7 @@
         final long oldcurrent = inState.getLong(CURRENT);
         long current = -1;
         if (restoreIncognitoTabs || (hasState(oldcurrent, inState) && !isIncognito(oldcurrent, inState))) {
-                current = oldcurrent;
+            current = oldcurrent;
         } else {
             // pick first non incognito tab
             for (long id : ids) {
@@ -363,8 +369,6 @@
      * @param restoreIncognitoTabs Restoring private browsing tabs
      * @param restoreAll All webviews get restored, not just the current tab
      *        (this does not override handling of incognito tabs)
-     * @return True if there were previous tabs that were restored. False if
-     *         there was no saved state or restoring the state failed.
      */
     void restoreState(Bundle inState, long currentId,
             boolean restoreIncognitoTabs, boolean restoreAll) {
@@ -387,7 +391,7 @@
                     && state.getBoolean(Tab.INCOGNITO)) {
                 // ignore tab
             } else if (id == currentId || restoreAll) {
-                Tab t = createNewTab();
+                Tab t = createNewTab(state, false);
                 if (t == null) {
                     // We could "break" at this point, but we want
                     // sNextId to be set correctly.
@@ -399,23 +403,12 @@
                 if (id == currentId) {
                     setCurrentTab(t);
                 }
-                if (!t.restoreState(state)) {
-                    Log.w(LOGTAG, "Fail in restoreState, load home page.");
-                    t.getWebView().loadUrl(BrowserSettings.getInstance()
-                            .getHomePage());
-                }
             } else {
                 // Create a new tab and don't restore the state yet, add it
                 // to the tab list
-                Tab t = new Tab(mController, null);
+                Tab t = new Tab(mController, state);
                 t.setId(id);
                 tabMap.put(id, t);
-                if (state != null) {
-                    t.setSavedState(state);
-                    // Need to maintain the app id and original url so we
-                    // can possibly reuse this tab.
-                    t.setAppId(state.getString(Tab.APPID));
-                }
                 mTabs.add(t);
                 // added the tab to the front as they are not current
                 mTabQueue.add(0, t);
@@ -619,8 +612,6 @@
         if (getCurrentTab() == t) {
             setCurrentTab(t, true);
         }
-        // Clear the saved state and picker data
-        t.setSavedState(null);
     }
 
     /**
@@ -681,12 +672,6 @@
             newTab.setWebView(mainView);
         }
         newTab.putInForeground();
-        if (needRestore) {
-            // Have to finish setCurrentTab work before calling restoreState
-            if (!newTab.restoreState(newTab.getSavedState())) {
-                mainView.loadUrl(BrowserSettings.getInstance().getHomePage());
-            }
-        }
         return true;
     }
 
diff --git a/src/com/android/browser/UI.java b/src/com/android/browser/UI.java
index 23897f7..2984d5c 100644
--- a/src/com/android/browser/UI.java
+++ b/src/com/android/browser/UI.java
@@ -147,4 +147,6 @@
 
     void setUseQuickControls(boolean enabled);
 
+    public boolean shouldCaptureThumbnails();
+
 }
diff --git a/src/com/android/browser/WebViewController.java b/src/com/android/browser/WebViewController.java
index 175cbf8..f4ff764 100644
--- a/src/com/android/browser/WebViewController.java
+++ b/src/com/android/browser/WebViewController.java
@@ -122,4 +122,6 @@
     void showAutoLogin(Tab tab);
 
     void hideAutoLogin(Tab tab);
+
+    boolean shouldCaptureThumbnails();
 }
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index 73821bf..aeba7cf 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -101,6 +101,10 @@
             setTitleGravity(Gravity.NO_GRAVITY);
         }
         mTabBar.setUseQuickControls(mUseQuickControls);
+        // We need to update the tabs with this change
+        for (Tab t : mTabControl.getTabs()) {
+            t.updateShouldCaptureThumbnails();
+        }
     }
 
     private void checkTabCount() {
@@ -334,4 +338,9 @@
         return mTabBar;
     }
 
+    @Override
+    public boolean shouldCaptureThumbnails() {
+        return mUseQuickControls;
+    }
+
 }
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index b974c0e..e40a882 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -74,6 +74,13 @@
     static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder()
             .authority(LEGACY_AUTHORITY).scheme("content").build();
 
+    public static interface Thumbnails {
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                BrowserContract.AUTHORITY_URI, "thumbnails");
+        public static final String _ID = "_id";
+        public static final String THUMBNAIL = "thumbnail";
+    }
+
     static final String TABLE_BOOKMARKS = "bookmarks";
     static final String TABLE_HISTORY = "history";
     static final String TABLE_IMAGES = "images";
@@ -81,6 +88,7 @@
     static final String TABLE_SYNC_STATE = "syncstate";
     static final String TABLE_SETTINGS = "settings";
     static final String TABLE_SNAPSHOTS = "snapshots";
+    static final String TABLE_THUMBNAILS = "thumbnails";
 
     static final String TABLE_BOOKMARKS_JOIN_IMAGES = "bookmarks LEFT OUTER JOIN images " +
             "ON bookmarks.url = images." + Images.URL;
@@ -111,6 +119,9 @@
             "WHERE url IS NOT NULL AND deleted == 0) AND url_key NOT IN " +
             "(SELECT url FROM history WHERE url IS NOT NULL)";
 
+    static final int THUMBNAILS = 10;
+    static final int THUMBNAILS_ID = 11;
+
     static final int BOOKMARKS = 1000;
     static final int BOOKMARKS_ID = 1001;
     static final int BOOKMARKS_FOLDER = 1002;
@@ -187,6 +198,8 @@
         matcher.addURI(authority, "combined", COMBINED);
         matcher.addURI(authority, "combined/#", COMBINED_ID);
         matcher.addURI(authority, "settings", SETTINGS);
+        matcher.addURI(authority, "thumbnails", THUMBNAILS);
+        matcher.addURI(authority, "thumbnails/#", THUMBNAILS_ID);
 
         // Legacy
         matcher.addURI(LEGACY_AUTHORITY, "searches", SEARCHES);
@@ -333,7 +346,7 @@
 
     final class DatabaseHelper extends SQLiteOpenHelper {
         static final String DATABASE_NAME = "browser2.db";
-        static final int DATABASE_VERSION = 30;
+        static final int DATABASE_VERSION = 31;
         public DatabaseHelper(Context context) {
             super(context, DATABASE_NAME, null, DATABASE_VERSION);
         }
@@ -396,6 +409,7 @@
                     ");");
 
             createAccountsView(db);
+            createThumbnails(db);
 
             mSyncHelper.createDatabase(db);
 
@@ -406,6 +420,13 @@
             enableSync(db);
         }
 
+        void createThumbnails(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_THUMBNAILS + " (" +
+                    Thumbnails._ID + " INTEGER PRIMARY KEY," +
+                    Thumbnails.THUMBNAIL + " BLOB NOT NULL" +
+                    ");");
+        }
+
         void enableSync(SQLiteDatabase db) {
             ContentValues values = new ContentValues();
             values.put(Settings.KEY, Settings.KEY_SYNC_ENABLED);
@@ -500,6 +521,9 @@
 
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            if (oldVersion < 31) {
+                createThumbnails(db);
+            }
             if (oldVersion < 30) {
                 db.execSQL("DROP VIEW IF EXISTS " + VIEW_SNAPSHOTS_COMBINED);
                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
@@ -974,6 +998,18 @@
                 break;
             }
 
+            case THUMBNAILS_ID: {
+                selection = DatabaseUtils.concatenateWhere(
+                        selection, Thumbnails._ID + " = ?");
+                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+                        new String[] { Long.toString(ContentUris.parseId(uri)) });
+                // fall through
+            }
+            case THUMBNAILS: {
+                qb.setTables(TABLE_THUMBNAILS);
+                break;
+            }
+
             default: {
                 throw new UnsupportedOperationException("Unknown URL " + uri.toString());
             }
@@ -1173,6 +1209,17 @@
                 }
                 break;
             }
+            case THUMBNAILS_ID: {
+                selection = DatabaseUtils.concatenateWhere(
+                        selection, Thumbnails._ID + " = ?");
+                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+                        new String[] { Long.toString(ContentUris.parseId(uri)) });
+                // fall through
+            }
+            case THUMBNAILS: {
+                deleted = db.delete(TABLE_THUMBNAILS, selection, selectionArgs);
+                break;
+            }
             default: {
                 throw new UnsupportedOperationException("Unknown delete URI " + uri);
             }
@@ -1310,6 +1357,11 @@
                 break;
             }
 
+            case THUMBNAILS: {
+                id = db.replaceOrThrow(TABLE_THUMBNAILS, null, values);
+                break;
+            }
+
             default: {
                 throw new UnsupportedOperationException("Unknown insert URI " + uri);
             }
@@ -1552,6 +1604,12 @@
                 break;
             }
 
+            case THUMBNAILS: {
+                modified = db.update(TABLE_THUMBNAILS, values,
+                        selection, selectionArgs);
+                break;
+            }
+
             default: {
                 throw new UnsupportedOperationException("Unknown update URI " + uri);
             }