Saved Pages tab

 Bug: 4982126
 Add saved pages tab
 Remove "save page" menu option
 Add "save for offline reading" menu option
 Smooth animation to combo view

Change-Id: Ia67552a6f6a5474a6dfcff6790a341d4d36d5a77
diff --git a/res/layout/snapshot_item.xml b/res/layout/snapshot_item.xml
new file mode 100644
index 0000000..76cd501
--- /dev/null
+++ b/res/layout/snapshot_item.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/combo_horizontalSpacing">
+    <ImageView
+        android:id="@+id/thumb"
+        android:src="@drawable/thumbnail_bookmarks_widget_no_bookmark_holo"
+        android:layout_width="@dimen/bookmarkThumbnailWidth"
+        android:layout_height="@dimen/bookmarkThumbnailHeight"
+        android:scaleType="centerCrop"
+        android:cropToPadding="true"
+        android:background="@drawable/border_thumb_bookmarks_widget_holo" />
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBottom="@id/thumb"
+        android:layout_alignLeft="@id/thumb"
+        android:layout_alignRight="@id/thumb"
+        android:background="@drawable/overlay_url_bookmark_widget_holo"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:textSize="12sp"
+        android:typeface="sans"
+        android:textColor="@android:color/white"
+        android:paddingLeft="2dip"
+        android:paddingRight="2dip" />
+    <ImageView
+        android:id="@+id/divider"
+        android:src="?android:attr/dividerVertical"
+        android:layout_width="wrap_content"
+        android:layout_height="24dip"
+        android:layout_below="@+id/thumb"
+        android:layout_alignLeft="@+id/thumb"
+        android:scaleType="fitXY"
+        android:layout_marginTop="12dip" />
+    <TextView android:id="@+id/date"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/divider"
+        android:layout_alignTop="@id/divider"
+        android:layout_alignBottom="@id/divider"
+        android:paddingLeft="8dip"
+        android:gravity="center_vertical"
+        android:typeface="sans"
+        android:textSize="14sp"
+        android:textColor="#AAAAAA" />
+    <TextView android:id="@+id/size"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignTop="@id/divider"
+        android:layout_alignBottom="@id/divider"
+        android:layout_alignRight="@+id/thumb"
+        android:paddingRight="2dip"
+        android:gravity="center_vertical"
+        android:typeface="sans"
+        android:textSize="14sp"
+        android:textColor="#AAAAAA" />
+</RelativeLayout>
diff --git a/res/layout/snapshots.xml b/res/layout/snapshots.xml
new file mode 100644
index 0000000..48d2883
--- /dev/null
+++ b/res/layout/snapshots.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="@dimen/combo_paddingTop">
+
+    <GridView
+        android:id="@+id/grid"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_horizontal"
+        android:numColumns="auto_fit"
+        android:stretchMode="none"
+        android:gravity="center" />
+    <TextView
+        android:id="@android:id/empty"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="@string/empty_bookmarks_folder"
+        android:visibility="gone" />
+
+</FrameLayout>
diff --git a/res/menu-sw600dp/browser.xml b/res/menu-sw600dp/browser.xml
index 23366e7..29b6117 100644
--- a/res/menu-sw600dp/browser.xml
+++ b/res/menu-sw600dp/browser.xml
@@ -32,11 +32,8 @@
             android:title="@string/share_page"
             android:icon="@drawable/ic_share_holo_dark"
             android:alphabeticShortcut="s" />
-        <item android:id="@+id/save_webarchive_menu_id"
-            android:title="@string/menu_save_webarchive" />
-        <item
-            android:id="@+id/freeze_tab_menu_id"
-            android:title="@string/menu_freeze_tab" />
+        <item android:id="@+id/save_snapshot_menu_id"
+            android:title="@string/menu_save_snapshot" />
         <item android:id="@+id/page_info_menu_id"
             android:title="@string/page_info"
             android:icon="@drawable/ic_pageinfo_holo_dark"
diff --git a/res/menu/browser.xml b/res/menu/browser.xml
index 6eadcba..630bb87 100644
--- a/res/menu/browser.xml
+++ b/res/menu/browser.xml
@@ -62,11 +62,8 @@
             android:icon="@drawable/ic_share_holo_dark"
             android:alphabeticShortcut="s" />
         <item
-            android:id="@+id/save_webarchive_menu_id"
-            android:title="@string/menu_save_webarchive" />
-        <item
-            android:id="@+id/freeze_tab_menu_id"
-            android:title="@string/menu_freeze_tab" />
+            android:id="@+id/save_snapshot_menu_id"
+            android:title="@string/menu_save_snapshot" />
         <item
             android:id="@+id/page_info_menu_id"
             android:title="@string/page_info"
diff --git a/res/menu/snapshots_context.xml b/res/menu/snapshots_context.xml
new file mode 100644
index 0000000..01880dd
--- /dev/null
+++ b/res/menu/snapshots_context.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:id="@+id/CONTEXT_MENU">
+        <item
+            android:id="@+id/delete_context_menu_id"
+            android:title="@string/remove_bookmark"/>
+    </group>
+</menu>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9dbaea4..624f763 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -34,6 +34,8 @@
     <string name="tab_most_visited">Most visited</string>
     <!-- Name of tab containing the user's complete history, organized by time of last visit -->
     <string name="tab_history">History</string>
+    <!-- Name of tab containing the user's saved pages, organized by time created [CHAR LIMIT=20] -->
+    <string name="tab_snapshots">Saved Pages</string>
     <!-- Toast shown when a history item's star is clicked, converting it to a bookmark -->
     <string name="added_to_bookmarks">Added to bookmarks</string>
     <!-- Toast shown when a history item's star is clicked off, removing its bookmark -->
@@ -213,15 +215,10 @@
     <string name="copy_page_url">Copy page url</string>
     <!-- Menu item -->
     <string name="share_page">Share page</string>
-    <!-- Menu item to freeze a tab. This will make a view-only
-            snapshot of the page in a new tab. [CHAR LIMIT=50] -->
-    <string name="menu_freeze_tab">Freeze tab</string>
-    <!-- Menu item for saving a page. [CHAR LIMIT=30] -->
-    <string name="menu_save_webarchive">Save page</string>
-    <!-- Toast informing the user that the page has been saved. [CHAR LIMIT=50] -->
-    <string name="webarchive_saved">Page saved.</string>
-    <!-- Toast informing the user that saving the page has failed. [CHAR LIMIT=50] -->
-    <string name="webarchive_failed">Failed to save page.</string>
+    <!-- Menu item for saving a page for offline reading. This is a view-only snapshot of the page. [CHAR LIMIT=50] -->
+    <string name="menu_save_snapshot">Save for offline reading</string>
+    <!-- Toast informing the user that saving the page for offline reading has failed. [CHAR LIMIT=50] -->
+    <string name="snapshot_failed">Failed to save for offline reading.</string>
     <!-- The number of bookmarks in a folder [CHAR LIMT=50] -->
     <string name="contextheader_folder_bookmarkcount"><xliff:g id="bookmark_count">%d</xliff:g> bookmarks</string>
     <!-- No bookmarks in the folder [CHAR LIMIT=50] -->
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index e577b1b..e2ad329 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -16,7 +16,6 @@
 
 package com.android.browser;
 
-import android.animation.ObjectAnimator;
 import android.app.Activity;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
@@ -477,15 +476,13 @@
     }
 
     @Override
-    public void showComboView(boolean startWithHistory, Bundle extras) {
+    public void showComboView(ComboViews startingView, Bundle extras) {
         if (mComboView != null) {
             return;
         }
         mComboView = new CombinedBookmarkHistoryView(mActivity,
                 mUiController,
-                startWithHistory ?
-                        CombinedBookmarkHistoryView.FRAGMENT_ID_HISTORY
-                        : CombinedBookmarkHistoryView.FRAGMENT_ID_BOOKMARKS,
+                startingView,
                 extras);
         FrameLayout wrapper =
             (FrameLayout) mContentView.findViewById(R.id.webview_wrapper);
@@ -496,11 +493,6 @@
         if (mActiveTab != null) {
             mActiveTab.putInBackground();
         }
-        mComboView.setAlpha(0f);
-        ObjectAnimator anim = ObjectAnimator.ofFloat(mComboView, "alpha", 0f, 1f);
-        Resources res = mActivity.getResources();
-        anim.setDuration(res.getInteger(R.integer.comboViewFadeInDuration));
-        anim.start();
         mContentView.addView(mComboView, COVER_SCREEN_PARAMS);
     }
 
diff --git a/src/com/android/browser/BrowserSnapshotPage.java b/src/com/android/browser/BrowserSnapshotPage.java
new file mode 100644
index 0000000..06b2e42
--- /dev/null
+++ b/src/com/android/browser/BrowserSnapshotPage.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.browser;
+
+import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import com.android.browser.provider.BrowserProvider2.Snapshots;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+public class BrowserSnapshotPage extends Fragment implements
+        LoaderCallbacks<Cursor>, OnItemClickListener {
+
+    public static final String EXTRA_ANIMATE_ID = "animate_id";
+
+    private static final int LOADER_SNAPSHOTS = 1;
+    private static final String[] PROJECTION = new String[] {
+        Snapshots._ID,
+        Snapshots.TITLE,
+        "length(" + Snapshots.VIEWSTATE + ")",
+        Snapshots.THUMBNAIL,
+        Snapshots.FAVICON,
+        Snapshots.URL,
+    };
+    private static final int SNAPSHOT_TITLE = 1;
+    private static final int SNAPSHOT_VIEWSTATE_LENGTH = 2;
+    private static final int SNAPSHOT_THUMBNAIL = 3;
+    private static final int SNAPSHOT_FAVICON = 4;
+    private static final int SNAPSHOT_URL = 5;
+
+    GridView mGrid;
+    View mEmpty;
+    SnapshotAdapter mAdapter;
+    UiController mUiController;
+
+    public static BrowserSnapshotPage newInstance(UiController uiController,
+            Bundle extras) {
+        BrowserSnapshotPage instance = new BrowserSnapshotPage();
+        instance.mUiController = uiController;
+        instance.setArguments(extras);
+        return instance;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.snapshots, container, false);
+        mEmpty = view.findViewById(android.R.id.empty);
+        mGrid = (GridView) view.findViewById(R.id.grid);
+        setupGrid(inflater);
+        getLoaderManager().initLoader(LOADER_SNAPSHOTS, null, this);
+        return view;
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        getLoaderManager().destroyLoader(LOADER_SNAPSHOTS);
+        mAdapter.changeCursor(null);
+        mAdapter = null;
+    }
+
+    void setupGrid(LayoutInflater inflater) {
+        View item = inflater.inflate(R.layout.snapshot_item, mGrid, false);
+        int mspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        item.measure(mspec, mspec);
+        int width = item.getMeasuredWidth();
+        mGrid.setColumnWidth(width);
+        mGrid.setOnItemClickListener(this);
+        mGrid.setOnCreateContextMenuListener(this);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        if (id == LOADER_SNAPSHOTS) {
+            // TODO: Sort by date created
+            return new CursorLoader(getActivity(),
+                    Snapshots.CONTENT_URI, PROJECTION,
+                    null, null, null);
+        }
+        return null;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        if (loader.getId() == LOADER_SNAPSHOTS) {
+            if (mAdapter == null) {
+                mAdapter = new SnapshotAdapter(getActivity(), data);
+                mGrid.setAdapter(mAdapter);
+            } else {
+                mAdapter.changeCursor(data);
+            }
+            boolean empty = mAdapter.isEmpty();
+            mGrid.setVisibility(empty ? View.GONE : View.VISIBLE);
+            mEmpty.setVisibility(empty ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v,
+            ContextMenuInfo menuInfo) {
+        MenuInflater inflater = getActivity().getMenuInflater();
+        inflater.inflate(R.menu.snapshots_context, menu);
+        // Create the header, re-use BookmarkItem (has the layout we want)
+        BookmarkItem header = new BookmarkItem(getActivity());
+        AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
+        populateBookmarkItem(mAdapter.getItem(info.position), header);
+        menu.setHeaderView(header);
+    }
+
+    private void populateBookmarkItem(Cursor cursor, BookmarkItem item) {
+        item.setName(cursor.getString(SNAPSHOT_TITLE));
+        item.setUrl(cursor.getString(SNAPSHOT_URL));
+        item.setFavicon(getBitmap(cursor, SNAPSHOT_FAVICON));
+    }
+
+    static Bitmap getBitmap(Cursor cursor, int columnIndex) {
+        byte[] data = cursor.getBlob(columnIndex);
+        if (data == null) {
+            return null;
+        }
+        return BitmapFactory.decodeByteArray(data, 0, data.length);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.delete_context_menu_id) {
+            AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+            deleteSnapshot(info.id);
+            return true;
+        }
+        return super.onContextItemSelected(item);
+    }
+
+    void deleteSnapshot(long id) {
+        final Uri uri = ContentUris.withAppendedId(Snapshots.CONTENT_URI, id);
+        final ContentResolver cr = getActivity().getContentResolver();
+        new Thread() {
+            @Override
+            public void run() {
+                cr.delete(uri, null, null);
+            }
+        }.start();
+
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position,
+            long id) {
+        mUiController.removeComboView();
+        mUiController.createNewSnapshotTab(id, true);
+    }
+
+    private static class SnapshotAdapter extends ResourceCursorAdapter {
+
+        public SnapshotAdapter(Context context, Cursor c) {
+            super(context, R.layout.snapshot_item, c, 0);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            ImageView thumbnail = (ImageView) view.findViewById(R.id.thumb);
+            byte[] thumbBlob = cursor.getBlob(SNAPSHOT_THUMBNAIL);
+            if (thumbBlob == null) {
+                thumbnail.setImageResource(R.drawable.browser_thumbnail);
+            } else {
+                Bitmap thumbBitmap = BitmapFactory.decodeByteArray(
+                        thumbBlob, 0, thumbBlob.length);
+                thumbnail.setImageBitmap(thumbBitmap);
+            }
+            TextView title = (TextView) view.findViewById(R.id.title);
+            title.setText(cursor.getString(SNAPSHOT_TITLE));
+            TextView size = (TextView) view.findViewById(R.id.size);
+            int stateLen = cursor.getInt(SNAPSHOT_VIEWSTATE_LENGTH);
+            size.setText(String.format("%.1fMB", stateLen / 1024f / 1024f));
+            // We don't actually have the date in the database yet
+            // Use the current date as a placeholder
+            TextView date = (TextView) view.findViewById(R.id.date);
+            DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
+            date.setText(dateFormat.format(new Date()));
+        }
+
+        @Override
+        public Cursor getItem(int position) {
+            return (Cursor) super.getItem(position);
+        }
+    }
+
+}
diff --git a/src/com/android/browser/CombinedBookmarkHistoryView.java b/src/com/android/browser/CombinedBookmarkHistoryView.java
index 785b2cd..184314e 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryView.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryView.java
@@ -17,6 +17,8 @@
 package com.android.browser;
 
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.app.ActionBar;
 import android.app.ActionBar.Tab;
 import android.app.ActionBar.TabListener;
@@ -28,7 +30,6 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.Browser;
@@ -45,6 +46,8 @@
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
+import com.android.browser.UI.ComboViews;
+
 import java.util.HashMap;
 import java.util.Vector;
 
@@ -61,6 +64,7 @@
     final static int INVALID_ID = 0;
     final static int FRAGMENT_ID_BOOKMARKS = 1;
     final static int FRAGMENT_ID_HISTORY = 2;
+    final static int FRAGMENT_ID_SNAPSHOTS = 3;
 
     private UiController mUiController;
     private Activity mActivity;
@@ -72,10 +76,13 @@
 
     ActionBar.Tab mTabBookmarks;
     ActionBar.Tab mTabHistory;
+    ActionBar.Tab mTabSnapshots;
     ViewGroup mBookmarksHeader;
 
     BrowserBookmarksPage mBookmarks;
     BrowserHistoryPage mHistory;
+    BrowserSnapshotPage mSnapshots;
+    boolean mIsAnimating;
 
     static class IconListenerSet implements IconListener {
         // Used to store favicons as we get them from the database
@@ -115,7 +122,7 @@
     }
 
     public CombinedBookmarkHistoryView(Activity activity, UiController controller,
-            int startingFragment, Bundle extras) {
+            ComboViews startingView, Bundle extras) {
         super(activity);
         mUiController = controller;
         mActivity = activity;
@@ -124,7 +131,6 @@
 
         View v = LayoutInflater.from(activity).inflate(R.layout.bookmarks_history, this);
         v.setOnTouchListener(this);
-        Resources res = activity.getResources();
 
 //        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
@@ -152,11 +158,28 @@
             }
         }).execute();
 
-        setupActionBar(startingFragment);
+        mIsAnimating = true;
+        setAlpha(0f);
+        Resources res = mActivity.getResources();
+        animate().alpha(1f)
+                .setDuration(res.getInteger(R.integer.comboViewFadeInDuration))
+                .setListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mIsAnimating = false;
+                FragmentManager fm = mActivity.getFragmentManager();
+                FragmentTransaction ft = fm.beginTransaction();
+                onTabSelected(mActionBar.getSelectedTab(), ft);
+                ft.commit();
+            }
+        });
+
+        setupActionBar(startingView);
         mUiController.registerOptionsMenuHandler(this);
     }
 
-    void setupActionBar(int startingFragment) {
+    void setupActionBar(ComboViews startingView) {
         if (BrowserActivity.isTablet(mContext)) {
             mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
                     | ActionBar.DISPLAY_USE_LOGO);
@@ -168,15 +191,32 @@
         mTabBookmarks = mActionBar.newTab();
         mTabBookmarks.setText(R.string.tab_bookmarks);
         mTabBookmarks.setTabListener(this);
-        mActionBar.addTab(mTabBookmarks, FRAGMENT_ID_BOOKMARKS == startingFragment);
+        mActionBar.addTab(mTabBookmarks, ComboViews.Bookmarks == startingView);
         mTabHistory = mActionBar.newTab();
         mTabHistory.setText(R.string.tab_history);
         mTabHistory.setTabListener(this);
-        mActionBar.addTab(mTabHistory, FRAGMENT_ID_HISTORY == startingFragment);
+        mActionBar.addTab(mTabHistory, ComboViews.History == startingView);
+        mTabSnapshots = mActionBar.newTab();
+        mTabSnapshots.setText(R.string.tab_snapshots);
+        mTabSnapshots.setTabListener(this);
+        mActionBar.addTab(mTabSnapshots, ComboViews.Snapshots == startingView);
         mActionBar.setCustomView(mBookmarksHeader);
         mActionBar.show();
     }
 
+    void tearDownActionBar() {
+        if (mActionBar != null) {
+            mActionBar.removeAllTabs();
+            mTabBookmarks.setTabListener(null);
+            mTabHistory.setTabListener(null);
+            mTabSnapshots.setTabListener(null);
+            mTabBookmarks = null;
+            mTabHistory = null;
+            mTabSnapshots = null;
+            mActionBar = null;
+        }
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -210,6 +250,7 @@
         mBookmarks = BrowserBookmarksPage.newInstance(mBookmarkCallbackWrapper,
                 extras, mBookmarksHeader);
         mHistory = BrowserHistoryPage.newInstance(mUiController, extras);
+        mSnapshots = BrowserSnapshotPage.newInstance(mUiController, extras);
     }
 
     private void loadFragment(int id, FragmentTransaction ft) {
@@ -222,6 +263,9 @@
             case FRAGMENT_ID_HISTORY:
                 ft.replace(R.id.fragment, mHistory);
                 break;
+            case FRAGMENT_ID_SNAPSHOTS:
+                ft.replace(R.id.fragment, mSnapshots);
+                break;
             default:
                 throw new IllegalArgumentException();
         }
@@ -231,6 +275,7 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
+        tearDownActionBar();
         if (mCurrentFragment != INVALID_ID) {
             try {
                 FragmentManager fm = mActivity.getFragmentManager();
@@ -239,6 +284,8 @@
                     transaction.remove(mBookmarks);
                 } else if (mCurrentFragment == FRAGMENT_ID_HISTORY) {
                     transaction.remove(mHistory);
+                } else if (mCurrentFragment == FRAGMENT_ID_SNAPSHOTS) {
+                    transaction.remove(mSnapshots);
                 }
                 transaction.commit();
             } catch (IllegalStateException ex) {
@@ -256,6 +303,9 @@
      * callback for back key presses
      */
     boolean onBackPressed() {
+        if (mIsAnimating) {
+            return true;
+        }
         if (mCurrentFragment == FRAGMENT_ID_BOOKMARKS) {
             return mBookmarks.onBackPressed();
         }
@@ -278,10 +328,19 @@
 
     @Override
     public void onTabSelected(Tab tab, FragmentTransaction ft) {
+        if (mIsAnimating) {
+            // We delay set while animating (smooth animations)
+            // TODO: Signal to the fragment in advance so that it can start
+            // loading its data asynchronously
+            return;
+        }
+
         if (tab == mTabBookmarks) {
             loadFragment(FRAGMENT_ID_BOOKMARKS, ft);
         } else if (tab == mTabHistory) {
             loadFragment(FRAGMENT_ID_HISTORY, ft);
+        } else if (tab == mTabSnapshots) {
+            loadFragment(FRAGMENT_ID_SNAPSHOTS, ft);
         }
     }
 
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 9046745..88fcbd6 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -40,7 +40,6 @@
 import android.net.http.SslError;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
@@ -77,6 +76,7 @@
 import android.widget.Toast;
 
 import com.android.browser.IntentHandler.UrlData;
+import com.android.browser.UI.ComboViews;
 import com.android.browser.UI.DropdownChangeListener;
 import com.android.browser.provider.BrowserProvider;
 import com.android.browser.provider.BrowserProvider2.Snapshots;
@@ -330,7 +330,6 @@
                     webView.setInitialScale(scale);
                 }
             }
-            mTabControl.loadSnapshotTabs();
             mUi.updateTabs(mTabControl.getTabs());
         } else {
             mTabControl.restoreState(icicle, currentTabId, restoreIncognitoTabs,
@@ -1179,7 +1178,8 @@
         // Disable opening in a new window if we have maxed out the windows
         extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW,
                 !mTabControl.canCreateNewTab());
-        mUi.showComboView(startWithHistory, extras);
+        mUi.showComboView(startWithHistory
+                ? ComboViews.History : ComboViews.Bookmarks, extras);
     }
 
     // combo view callbacks
@@ -1520,11 +1520,9 @@
                 final MenuItem newtab = menu.findItem(R.id.new_tab_menu_id);
                 newtab.setEnabled(getTabControl().canCreateNewTab());
 
-                MenuItem archive = menu.findItem(R.id.save_webarchive_menu_id);
-                Tab tab = getTabControl().getCurrentTab();
-                String url = tab != null ? tab.getUrl() : null;
-                archive.setVisible(!TextUtils.isEmpty(url)
-                        && !url.endsWith(".webarchivexml"));
+                MenuItem saveSnapshot = menu.findItem(R.id.save_snapshot_menu_id);
+                Tab tab = getCurrentTab();
+                saveSnapshot.setVisible(tab != null && !tab.isSnapshot());
                 break;
         }
         mCurrentMenuState = mMenuState;
@@ -1619,83 +1617,32 @@
                 getCurrentTopWebView().showFindDialog(null, true);
                 break;
 
-            case R.id.freeze_tab_menu_id:
-                // TODO: Show error messages
-                Tab source = getTabControl().getCurrentTab();
+            case R.id.save_snapshot_menu_id:
+                final Tab source = getTabControl().getCurrentTab();
                 if (source == null) break;
                 final ContentResolver cr = mActivity.getContentResolver();
                 final ContentValues values = source.createSnapshotValues();
-                new AsyncTask<Tab, Void, Long>() {
-                    @Override
-                    protected Long doInBackground(Tab... params) {
-                        Tab t = params[0];
-                        if (values == null) {
-                            return t.isSnapshot()
-                                    ? ((SnapshotTab)t).getSnapshotId()
-                                    : -1;
-                        }
-                        Uri result = cr.insert(Snapshots.CONTENT_URI, values);
-                        long id = ContentUris.parseId(result);
-                        return id;
-                    }
+                if (values != null) {
+                    new AsyncTask<Tab, Void, Long>() {
 
-                    @Override
-                    protected void onPostExecute(Long id) {
-                        if (id > 0) {
-                            createNewSnapshotTab(id, true);
+                        @Override
+                        protected Long doInBackground(Tab... params) {
+                            Uri result = cr.insert(Snapshots.CONTENT_URI, values);
+                            long id = ContentUris.parseId(result);
+                            return id;
                         }
-                    };
-                }.execute(source);
-                break;
 
-            case R.id.save_webarchive_menu_id:
-                String state = Environment.getExternalStorageState();
-                if (!Environment.MEDIA_MOUNTED.equals(state)) {
-                    Log.e(LOGTAG, "External storage not mounted");
-                    Toast.makeText(mActivity, R.string.webarchive_failed,
+                        @Override
+                        protected void onPostExecute(Long id) {
+                            Bundle b = new Bundle();
+                            b.putLong(BrowserSnapshotPage.EXTRA_ANIMATE_ID, id);
+                            mUi.showComboView(ComboViews.Snapshots, b);
+                        };
+                    }.execute(source);
+                } else {
+                    Toast.makeText(mActivity, R.string.snapshot_failed,
                             Toast.LENGTH_SHORT).show();
-                    break;
                 }
-                final String directory = Environment.getExternalStoragePublicDirectory(
-                        Environment.DIRECTORY_DOWNLOADS) + File.separator;
-                File dir = new File(directory);
-                if (!dir.exists() && !dir.mkdirs()) {
-                    Log.e(LOGTAG, "Save as Web Archive: mkdirs for " + directory + " failed!");
-                    Toast.makeText(mActivity, R.string.webarchive_failed,
-                            Toast.LENGTH_SHORT).show();
-                    break;
-                }
-                final WebView topWebView = getCurrentTopWebView();
-                final String title = topWebView.getTitle();
-                final String url = topWebView.getUrl();
-                topWebView.saveWebArchive(directory, true,
-                        new ValueCallback<String>() {
-                    @Override
-                    public void onReceiveValue(final String value) {
-                        if (value != null) {
-                            File file = new File(value);
-                            final long length = file.length();
-                            if (file.exists() && length > 0) {
-                                Toast.makeText(mActivity, R.string.webarchive_saved,
-                                        Toast.LENGTH_SHORT).show();
-                                final DownloadManager manager = (DownloadManager) mActivity
-                                        .getSystemService(Context.DOWNLOAD_SERVICE);
-                                new Thread("Add WebArchive to download manager") {
-                                    @Override
-                                    public void run() {
-                                        manager.addCompletedDownload(
-                                                null == title ? value : title,
-                                                value, true, "application/x-webarchive-xml",
-                                                value, length, true);
-                                    }
-                                }.start();
-                                return;
-                            }
-                        }
-                        DownloadHandler.onDownloadStartNoStream(mActivity,
-                                url, null, null, null, topWebView.isPrivateBrowsingEnabled());
-                    }
-                });
                 break;
 
             case R.id.page_info_menu_id:
@@ -2166,18 +2113,6 @@
         mUi.removeTab(tab);
         mTabControl.removeTab(tab);
         mCrashRecoveryHandler.backupState();
-        if (tab.isSnapshot()) {
-            SnapshotTab st = (SnapshotTab) tab;
-            final Uri uri = ContentUris.withAppendedId(
-                    Snapshots.CONTENT_URI, st.getSnapshotId());
-            final ContentResolver cr = mActivity.getContentResolver();
-            new Thread() {
-                @Override
-                public void run() {
-                    cr.delete(uri, null, null);
-                }
-            }.start();
-        }
     }
 
     @Override
@@ -2326,11 +2261,17 @@
         return tab;
     }
 
-    private SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive) {
-        SnapshotTab tab = mTabControl.createSnapshotTab(snapshotId);
-        addTab(tab);
-        if (setActive) {
-            setActiveTab(tab);
+    @Override
+    public SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive) {
+        SnapshotTab tab = null;
+        if (mTabControl.canCreateNewTab()) {
+            tab = mTabControl.createSnapshotTab(snapshotId);
+            addTab(tab);
+            if (setActive) {
+                setActiveTab(tab);
+            }
+        } else {
+            mUi.showMaxTabsWarning();
         }
         return tab;
     }
diff --git a/src/com/android/browser/PhoneUi.java b/src/com/android/browser/PhoneUi.java
index aedd712..0c236af 100644
--- a/src/com/android/browser/PhoneUi.java
+++ b/src/com/android/browser/PhoneUi.java
@@ -206,11 +206,11 @@
     }
 
     @Override
-    public void showComboView(boolean startWithHistory, Bundle extras) {
+    public void showComboView(ComboViews startWith, Bundle extras) {
         if (mNavScreen != null) {
             hideNavScreen(false);
         }
-        super.showComboView(startWithHistory, extras);
+        super.showComboView(startWith, extras);
     }
 
     @Override
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 334bd9d..a38c5f3 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -1875,6 +1875,15 @@
 
     public ContentValues createSnapshotValues() {
         if (mMainView == null) return null;
+        /*
+         * TODO: Compression
+         * Some quick tests indicate GZIPing the stream will result in
+         * some decent savings. There is little overhead for sites with mostly
+         * images (such as the "Most Visited" page), dropping from 235kb
+         * to 200kb. Sites with a decent amount of text (hardocp.com), the size
+         * drops from 522kb to 381kb. Do this as part of the switch to saving
+         * to the SD card.
+         */
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
         if (!mMainView.saveViewState(stream)) {
             return null;
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 1110bda..5f3995f 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -16,14 +16,10 @@
 
 package com.android.browser;
 
-import android.content.ContentResolver;
-import android.database.Cursor;
 import android.os.Bundle;
 import android.util.Log;
 import android.webkit.WebView;
 
-import com.android.browser.provider.BrowserProvider2.Snapshots;
-
 import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -215,7 +211,6 @@
     }
 
     SnapshotTab createSnapshotTab(long snapshotId) {
-        // TODO: Don't count this against the limit
         SnapshotTab t = new SnapshotTab(mController, snapshotId);
         t.setId(getNextId());
         mTabs.add(t);
@@ -435,21 +430,6 @@
                 }
             }
         }
-        loadSnapshotTabs();
-
-    }
-
-    void loadSnapshotTabs() {
-        ContentResolver cr = mController.getActivity().getContentResolver();
-        Cursor c = cr.query(Snapshots.CONTENT_URI, new String[] { "_id" },
-                null, null, null);
-        try {
-            while (c.moveToNext()) {
-                createSnapshotTab(c.getLong(0));
-            }
-        } finally {
-            c.close();
-        }
     }
 
     /**
diff --git a/src/com/android/browser/UI.java b/src/com/android/browser/UI.java
index a6356a3..93f6e63 100644
--- a/src/com/android/browser/UI.java
+++ b/src/com/android/browser/UI.java
@@ -33,6 +33,12 @@
  */
 public interface UI {
 
+    public static enum ComboViews {
+        History,
+        Bookmarks,
+        Snapshots,
+    }
+
     public void onPause();
 
     public void onResume();
@@ -77,7 +83,7 @@
 
     public void removeActiveTabsPage();
 
-    public void showComboView(boolean startWithHistory, Bundle extra);
+    public void showComboView(ComboViews startingView, Bundle extra);
 
     public void hideComboView();
 
diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java
index 05eaf8e..4550a8a 100644
--- a/src/com/android/browser/UiController.java
+++ b/src/com/android/browser/UiController.java
@@ -97,4 +97,6 @@
 
     boolean onOptionsItemSelected(MenuItem item);
 
+    SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive);
+
 }
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index 8455e74..81a6edb 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -77,9 +77,8 @@
         }
     }
 
-    @Override
-    public void showComboView(boolean startWithHistory, Bundle extras) {
-        super.showComboView(startWithHistory, extras);
+    public void showComboView(ComboViews startWith, Bundle extras) {
+        super.showComboView(startWith, extras);
         if (mUseQuickControls) {
             mActionBar.show();
         }
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index 06e4e4a..32fa172 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -78,6 +78,7 @@
         public static final String TITLE = History.TITLE;
         public static final String URL = History.URL;
         public static final String FAVICON = History.FAVICON;
+        public static final String THUMBNAIL = History.THUMBNAIL;
     }
 
     public static final String PARAM_GROUP_BY = "groupBy";