Use a grid view for bookmarks page.

The BrowserProvider now stores another blob
for a screenshot of the page.  If the current
page is a bookmark, store a screenshot.  When
viewing bookmarks, show a gridview of screenshots
of the bookmarks.

Requires a change to framework to add THUMBNAIL
to Browser.BookmarkColumns and to the HISTORY_PROJECTION
diff --git a/src/com/android/browser/BookmarkGridPage.java b/src/com/android/browser/BookmarkGridPage.java
new file mode 100644
index 0000000..a9db7ac
--- /dev/null
+++ b/src/com/android/browser/BookmarkGridPage.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.browser;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.ContentObserver;
+import android.database.DataSetObserver;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Browser;
+import android.provider.Browser.BookmarkColumns;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class BookmarkGridPage extends Activity {
+    private final static int SPACING = 10;
+    private BookmarkGrid mGridView;
+    private BookmarkGridAdapter mAdapter;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mGridView = new BookmarkGrid(this);
+        mGridView.setNumColumns(3);
+        mAdapter = new BookmarkGridAdapter(this);
+        mGridView.setAdapter(mAdapter);
+        mGridView.setFocusable(true);
+        mGridView.setFocusableInTouchMode(true);
+        mGridView.setSelector(android.R.drawable.gallery_thumb);
+        mGridView.setVerticalSpacing(SPACING);
+        mGridView.setHorizontalSpacing(SPACING);
+        setContentView(mGridView);
+        mGridView.requestFocus();
+    }
+
+    private class BookmarkGrid extends GridView {
+        public BookmarkGrid(Context context) {
+            super(context);
+        }
+        @Override
+        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+            int thumbHeight = (h - 2 * (SPACING + getListPaddingTop()
+                    + getListPaddingBottom())) / 3;
+            mAdapter.heightChanged(thumbHeight);
+            super.onSizeChanged(w, h, oldw, oldh);
+        }
+    }
+
+    private class BookmarkGridAdapter implements ListAdapter {
+        private ArrayList<DataSetObserver> mDataObservers;
+        private Context mContext;  // Context to use to inflate views
+        private Cursor  mCursor;
+        private int mThumbHeight;
+
+        public BookmarkGridAdapter(Context context) {
+            mContext = context;
+            mDataObservers = new ArrayList<DataSetObserver>();
+            String whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0";
+            String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
+            mCursor = managedQuery(Browser.BOOKMARKS_URI,
+                    Browser.HISTORY_PROJECTION, whereClause, null, orderBy);
+            mCursor.registerContentObserver(new ChangeObserver());
+            mGridView.setOnItemClickListener(
+                    new AdapterView.OnItemClickListener() {
+                public void onItemClick(AdapterView parent, View v,
+                        int position, long id) {
+                    mCursor.moveToPosition(position);
+                    String url = mCursor.getString(
+                            Browser.HISTORY_PROJECTION_URL_INDEX);
+                    Intent intent = (new Intent()).setAction(url);
+                    getParent().setResult(RESULT_OK, intent);
+                    finish();
+                }});
+        }
+
+        void heightChanged(int newHeight) {
+            mThumbHeight = newHeight;
+        }
+
+        private class ChangeObserver extends ContentObserver {
+            public ChangeObserver() {
+                super(new Handler());
+            }
+
+            @Override
+            public boolean deliverSelfNotifications() {
+                return true;
+            }
+
+            @Override
+            public void onChange(boolean selfChange) {
+                BookmarkGridAdapter.this.refreshData();
+            }
+        }
+
+        void refreshData() {
+            mCursor.requery();
+            for (DataSetObserver o : mDataObservers) {
+                o.onChanged();
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see android.widget.ListAdapter#areAllItemsSelectable()
+         */
+        public boolean areAllItemsEnabled() {
+            return true;
+        }
+
+        /* (non-Javadoc)
+         * @see android.widget.ListAdapter#isSelectable(int)
+         */
+        public boolean isEnabled(int position) {
+            if (position >= 0 && position < mCursor.getCount()) {
+                return true;
+            }
+            return false;
+        }
+
+        /* (non-Javadoc)
+         * @see android.widget.Adapter#getCount()
+         */
+        public int getCount() {
+            return mCursor.getCount();
+        }
+
+        /* (non-Javadoc)
+         * @see android.widget.Adapter#getItem(int)
+         */
+        public Object getItem(int position) {
+            return null;
+        }
+
+        /* (non-Javadoc)
+         * @see android.widget.Adapter#getItemId(int)
+         */
+        public long getItemId(int position) {
+            return position;
+        }
+
+        /* (non-Javadoc)
+         * @see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)
+         */
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View v = null;
+            if (convertView != null) {
+                v = convertView;
+            } else {
+                LayoutInflater factory = LayoutInflater.from(mContext);
+                v = factory.inflate(R.layout.bookmark_thumbnail, null);
+            }
+            ImageView thumb = (ImageView) v.findViewById(R.id.thumb);
+            TextView tv = (TextView) v.findViewById(R.id.label);
+
+            mCursor.moveToPosition(position);
+            tv.setText(mCursor.getString(
+                    Browser.HISTORY_PROJECTION_TITLE_INDEX));
+            byte[] data = mCursor.getBlob(
+                    Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX);
+            if (data == null) {
+                // Backup is to show the favicon
+                data = mCursor.getBlob(
+                        Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+                thumb.setScaleType(ImageView.ScaleType.CENTER);
+            } else {
+                thumb.setScaleType(ImageView.ScaleType.FIT_XY);
+            }
+            if (data != null) {
+                thumb.setImageBitmap(
+                        BitmapFactory.decodeByteArray(data, 0, data.length));
+            } else {
+                thumb.setImageResource(R.drawable.app_web_browser_sm);
+                thumb.setScaleType(ImageView.ScaleType.CENTER);
+            }
+            ViewGroup.LayoutParams lp = thumb.getLayoutParams();
+            if (lp.height != mThumbHeight) {
+                lp.height = mThumbHeight;
+                thumb.requestLayout();
+            }
+            return v;
+        }
+
+        /* (non-Javadoc)
+         * @see android.widget.Adapter#registerDataSetObserver(android.database.DataSetObserver)
+         */
+        public void registerDataSetObserver(DataSetObserver observer) {
+            mDataObservers.add(observer);
+        }
+
+        /* (non-Javadoc)
+         * @see android.widget.Adapter#hasStableIds()
+         */
+        public boolean hasStableIds() {
+            return true;
+        }
+
+        /* (non-Javadoc)
+         * @see android.widget.Adapter#unregisterDataSetObserver(android.database.DataSetObserver)
+         */
+        public void unregisterDataSetObserver(DataSetObserver observer) {
+            mDataObservers.remove(observer);
+        }
+
+        public int getItemViewType(int position) {
+            return 0;
+        }
+
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        public boolean isEmpty() {
+            return getCount() == 0;
+        }
+    }
+}
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 363c6d7..d1d8d15 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -29,6 +29,7 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -130,6 +131,7 @@
 import android.widget.Toast;
 
 import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -2989,6 +2991,47 @@
             // Update the lock icon image only once we are done loading
             updateLockIconImage(mLockIconType);
 
+            // If this is a bookmarked site, add a screenshot to the database.
+            // FIXME: When should we update?  Every time?
+            if (url != null) {
+                // copied from BrowserBookmarksAdapter
+                int query = url.indexOf('?');
+                String noQuery = url;
+                if (query != -1) {
+                    noQuery = url.substring(0, query);
+                }
+                String URL = noQuery + '?';
+                String[] selArgs = new String[] { noQuery, URL };
+                final String where = "(url == ? OR url GLOB ? || '*') AND bookmark == 1";
+                final String[] projection = new String[] { Browser.BookmarkColumns._ID };
+                ContentResolver cr = getContentResolver();
+                final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs, null);
+                boolean succeed = c.moveToFirst();
+                ContentValues values = null;
+                while (succeed) {
+                    if (values == null) {
+                        final ByteArrayOutputStream os = new ByteArrayOutputStream();
+                        Picture thumbnail = view.capturePicture();
+                        // Height was arbitrarily chosen
+                        Bitmap bm = Bitmap.createBitmap(100, 100,
+                                Bitmap.Config.ARGB_4444);
+                        Canvas canvas = new Canvas(bm);
+                        // Scale chosen to be about one third, since we want
+                        // roughly three rows/columns for bookmark page
+                        canvas.scale(.3f, .3f);
+                        thumbnail.draw(canvas);
+                        bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+                        values = new ContentValues();
+                        values.put(Browser.BookmarkColumns.THUMBNAIL,
+                                os.toByteArray());
+                    }
+                    cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
+                            c.getInt(0)), values, null, null);
+                    succeed = c.moveToNext();
+                }
+                c.close();
+            }
+
             // Performance probe
             if (false) {
                 long[] sysCpu = new long[7];
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index 8743254..a3f249b 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -145,7 +145,8 @@
     // 15 -> 17 Set it up for the SearchManager
     // 17 -> 18 Added favicon in bookmarks table for Home shortcuts
     // 18 -> 19 Remove labels table
-    private static final int DATABASE_VERSION = 19;
+    // 19 -> 20 Added thumbnail
+    private static final int DATABASE_VERSION = 20;
 
     // Regular expression which matches http://, followed by some stuff, followed by
     // optionally a trailing slash, all matched as separate groups.
@@ -214,7 +215,8 @@
                     "created LONG," +
                     "description TEXT," +
                     "bookmark INTEGER," +
-                    "favicon BLOB DEFAULT NULL" +
+                    "favicon BLOB DEFAULT NULL," +
+                    "thumbnail BLOB DEFAULT NULL" +
                     ");");
 
             final CharSequence[] bookmarks = mContext.getResources()
@@ -241,9 +243,12 @@
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
-                    + newVersion + ", which will destroy all old data");
+                    + newVersion);
             if (oldVersion == 18) {
                 db.execSQL("DROP TABLE IF EXISTS labels");
+            }
+            if (oldVersion <= 19) {
+                db.execSQL("ALTER TABLE bookmarks ADD COLUMN thumbnail BLOB DEFAULT NULL;");
             } else {
                 db.execSQL("DROP TABLE IF EXISTS bookmarks");
                 db.execSQL("DROP TABLE IF EXISTS searches");
diff --git a/src/com/android/browser/CombinedBookmarkHistoryActivity.java b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
index 963f179..6926c8f 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryActivity.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
@@ -84,7 +84,7 @@
         Resources resources = getResources();
 
         getIconListenerSet(getContentResolver());
-        Intent bookmarksIntent = new Intent(this, BrowserBookmarksPage.class);
+        Intent bookmarksIntent = new Intent(this, BookmarkGridPage.class);
         bookmarksIntent.putExtras(extras);
         tabHost.addTab(tabHost.newTabSpec(BOOKMARKS_TAB)
                 .setIndicator(resources.getString(R.string.tab_bookmarks),