Use tiles for bookmarks

- Use tile based bitmap for bookmarks and history
- Settings UI cleanup

Change-Id: If959cb0b8f110035b8dd2fefe8106e9c5d30f4f1
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index 884cdc4..a50a7d6 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -657,6 +657,7 @@
 
         String title = null;
         String url = null;
+        mTouchIconUrl = null;
 
         mFakeTitle = (TextView) findViewById(R.id.fake_title);
         if (mMap != null) {
@@ -838,7 +839,7 @@
                 Bookmarks.addBookmark(AddBookmarkPage.this, false, url,
                         title, thumbnail, mCurrentFolder);
                 if (touchIconUrl != null) {
-                    new DownloadTouchIcon(mContext, cr, url).execute(mTouchIconUrl);
+                    new DownloadTouchIcon(mContext, cr, url).execute(touchIconUrl);
                 }
                 mMessage.arg1 = 1;
             } catch (IllegalStateException e) {
@@ -1083,7 +1084,9 @@
             } else {
                 bundle.putParcelable(BrowserContract.Bookmarks.THUMBNAIL, thumbnail);
                 bundle.putBoolean(REMOVE_THUMBNAIL, !urlUnmodified);
-                bundle.putString(TOUCH_ICON_URL, mTouchIconUrl);
+                if (mTouchIconUrl != null) {
+                    bundle.putString(TOUCH_ICON_URL, mTouchIconUrl);
+                }
                 // Post a message to write to the DB.
                 Message msg = Message.obtain(mHandler, SAVE_BOOKMARK);
                 msg.setData(bundle);
diff --git a/src/com/android/browser/BookmarkItem.java b/src/com/android/browser/BookmarkItem.java
index b230de6..12b2b70 100644
--- a/src/com/android/browser/BookmarkItem.java
+++ b/src/com/android/browser/BookmarkItem.java
@@ -16,18 +16,14 @@
 
 package com.android.browser;
 
-import com.android.browser.R;
-
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
-import android.text.method.ScrollingMovementMethod;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ScrollView;
-import android.widget.ImageView;
 import android.widget.TextView;
 
 /**
@@ -37,13 +33,14 @@
 
     final static int MAX_TEXTVIEW_LEN = 80;
 
-    protected TextView    mTextView;
-    protected TextView    mUrlText;
-    protected ImageView   mImageView;
-    protected String      mUrl;
-    protected String      mTitle;
+    protected TextView     mTextView;
+    protected TextView     mUrlText;
+    protected SiteTileView mTileView;
+    protected String       mUrl;
+    protected String       mTitle;
     protected boolean mEnableScrolling = false;
 
+    protected Bitmap  mBitmap;
     /**
      *  Instantiate a bookmark item, including a default favicon.
      *
@@ -58,22 +55,12 @@
         factory.inflate(R.layout.history_item, this);
         mTextView = (TextView) findViewById(R.id.title);
         mUrlText = (TextView) findViewById(R.id.url);
-        mImageView = (ImageView) findViewById(R.id.favicon);
+        mTileView = (SiteTileView) findViewById(R.id.favicon);
         View star = findViewById(R.id.star);
         star.setVisibility(View.GONE);
     }
 
     /**
-     *  Copy this BookmarkItem to item.
-     *  @param item BookmarkItem to receive the info from this BookmarkItem.
-     */
-    /* package */ void copyTo(BookmarkItem item) {
-        item.mTextView.setText(mTextView.getText());
-        item.mUrlText.setText(mUrlText.getText());
-        item.mImageView.setImageDrawable(mImageView.getDrawable());
-    }
-
-    /**
      * Return the name assigned to this bookmark item.
      */
     /* package */ String getName() {
@@ -92,18 +79,17 @@
      */
     /* package */ void setFavicon(Bitmap b) {
         if (b != null) {
-            mImageView.setImageBitmap(b);
-        } else {
-            mImageView.setImageResource(R.drawable.ic_deco_favicon_normal);
+            mTileView.replaceFavicon(b);
+            mBitmap = b;
         }
     }
 
     public int getFavIconIntrinsicWidth() {
-        return mImageView.getDrawable().getIntrinsicWidth();
+        return mTileView.getMeasuredWidth();
     }
 
     void setFaviconBackground(Drawable d) {
-        mImageView.setBackgroundDrawable(d);
+        mTileView.setBackgroundDrawable(d);
     }
 
     /**
diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java
index 38e07e0..cef6e62 100644
--- a/src/com/android/browser/BrowserBookmarksAdapter.java
+++ b/src/com/android/browser/BrowserBookmarksAdapter.java
@@ -24,6 +24,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.browser.mdm.EditBookmarksRestriction;
@@ -85,7 +86,7 @@
                 .getDimensionPixelSize(R.dimen.combo_horizontalSpacing);
         view.setPadding(padding, view.getPaddingTop(),
                 padding, view.getPaddingBottom());
-        BookmarkThumbImageView thumb = (BookmarkThumbImageView) view.findViewById(R.id.thumb_image);
+        SiteTileView thumb =  (SiteTileView) view.findViewById(R.id.thumb_image);
         TextView tv = (TextView) view.findViewById(R.id.label);
         tv.setText(item.title);
         int containerWidth = thumb.getWidth() - thumb.getPaddingLeft() - thumb.getPaddingRight();
@@ -94,7 +95,8 @@
 
         if (item.is_folder) {
             b = BitmapFactory.decodeResource(mContext.getResources(),
-                    R.drawable.thumb_bookmark_widget_folder_holo);
+                    R.drawable.ic_deco_folder_normal);
+            thumb.setFloating(true);
         }
         else if (item.thumbnail == null || !item.has_thumbnail) {
             b = BitmapFactory.decodeResource(mContext.getResources(),
@@ -105,7 +107,8 @@
         }
 
         // If the item is managed by mdm or edit bookmark restriction enabled
-        if (containerWidth != 0 && (item.is_mdm_managed || EditBookmarksRestriction.getInstance().isEnabled())) {
+        if (containerWidth != 0 && (item.is_mdm_managed ||
+                EditBookmarksRestriction.getInstance().isEnabled())) {
             int iconResId;
             float overlayScale, overlayVertPos;
 
@@ -122,11 +125,12 @@
             float willScale = (float) containerWidth / (float) b.getWidth();
             Bitmap bm = BrowserBookmarksPage.overlayBookmarkBitmap(b, iconResId, mContext,
                     overlayScale / willScale, (int) (overlayVertPos / willScale));
-            thumb.setImageBitmap(bm);
+            thumb.replaceFavicon(bm);
         }
         else {
-            thumb.setImageBitmap(b);
+            thumb.replaceFavicon(b);
         }
+        thumb.setLongClickable(true);
     }
 
     @Override
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java
index f0cec84..80e1da7 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -652,8 +652,14 @@
                         item.getPaddingRight(),
                         item.getPaddingBottom());
                 item.setFaviconBackground(mFaviconBackground);
+                item.setTag(R.id.group_position, groupPosition);
+                item.setTag(R.id.child_position, childPosition);
+                item.setTag(R.id.combo_view_container, mHistoryList);
             } else {
                 item = (HistoryItem) convertView;
+                item.setTag(R.id.group_position, groupPosition);
+                item.setTag(R.id.child_position, childPosition);
+                item.setTag(R.id.combo_view_container, mHistoryList);
             }
 
             // Bail early if the Cursor is closed.
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index c7ceb68..5563b62 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -2028,10 +2028,12 @@
         switch (item.getItemId()) {
             // -- Main menu
             case R.id.new_tab_menu_id:
+                getCurrentTab().capture();
                 openTabToHomePage();
                 break;
 
             case R.id.incognito_menu_id:
+                getCurrentTab().capture();
                 openIncognitoTab();
                 break;
 
@@ -2543,7 +2545,7 @@
                 AddBookmarkPage.class);
         i.putExtra(BrowserContract.Bookmarks.URL, w.getUrl());
         i.putExtra(BrowserContract.Bookmarks.TITLE, w.getTitle());
-        String touchIconUrl = w.getTouchIconUrl();
+        String touchIconUrl = getCurrentTab().getTouchIconUrl();
         if (touchIconUrl != null) {
             i.putExtra(AddBookmarkPage.TOUCH_ICON_URL, touchIconUrl);
             WebSettings settings = w.getSettings();
diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java
index 20efcb1..5153d9c 100644
--- a/src/com/android/browser/HistoryItem.java
+++ b/src/com/android/browser/HistoryItem.java
@@ -20,14 +20,14 @@
 import android.view.View;
 import android.widget.CompoundButton;
 import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ExpandableListView;
 
-import com.android.browser.R;
 import com.android.browser.platformsupport.Browser;
 /**
  *  Layout representing a history item in the classic history viewer.
  */
 /* package */ class HistoryItem extends BookmarkItem
-        implements OnCheckedChangeListener {
+        implements OnCheckedChangeListener, View.OnClickListener {
 
     private CompoundButton  mStar;      // Star for bookmarking
     /**
@@ -48,13 +48,15 @@
         } else {
             mStar.setVisibility(View.GONE);
         }
+
+        mTileView.setOnClickListener(this);
     }
     
     /* package */ void copyTo(HistoryItem item) {
         item.mTextView.setText(mTextView.getText());
         item.mUrlText.setText(mUrlText.getText());
         item.setIsBookmark(mStar.isChecked());
-        item.mImageView.setImageDrawable(mImageView.getDrawable());
+        item.mTileView.replaceFavicon(mBitmap);
     }
 
     /**
@@ -87,4 +89,21 @@
                     getContext().getContentResolver(), mUrl, getName());
         }
     }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mTileView) {
+            ExpandableListView list = (ExpandableListView) getTag(R.id.combo_view_container);
+            int group = (int) getTag(R.id.group_position);
+            int pos = (int) getTag(R.id.child_position);
+            if (list != null) {
+                long packedPos = list.getPackedPositionForChild(group, pos);
+                int flatPos = list.getFlatListPosition(packedPos);
+                list.performItemClick(
+                        list.getAdapter().getView(flatPos, null, null),
+                        flatPos, list.getAdapter().getItemId(flatPos));
+            }
+            performClick();
+        }
+    }
 }
diff --git a/src/com/android/browser/SiteTileView.java b/src/com/android/browser/SiteTileView.java
new file mode 100644
index 0000000..36f21c9
--- /dev/null
+++ b/src/com/android/browser/SiteTileView.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+package com.android.browser;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+
+/**
+ * This represents a WebSite Tile that is created from a Drawable and will scale across any
+ * area this is externally layouted to. There are 3 possible looks:
+ *   - just the favicon (TYPE_SMALL)
+ *   - drop-shadow plus a thin overlay border (1dp) (TYPE_MEDIUM)
+ *   - centered favicon, extended color, rounded base (TYPE_LARGE)
+ *
+ * By centralizing everything in this class we make customization of looks much easier.
+ *
+ * NOTES:
+ *   - do not set a background from the outside; this overrides it automatically
+ */
+public class SiteTileView extends View {
+
+    // external configuration constants
+    public static final int TYPE_SMALL = 1;
+    public static final int TYPE_MEDIUM = 2;
+    public static final int TYPE_LARGE = 3;
+    private static final int TYPE_AUTO = 0;
+    private static final int COLOR_AUTO = 0;
+
+
+    // static configuration
+    private static final int THRESHOLD_MEDIUM_DP = 32;
+    private static final int THRESHOLD_LARGE_DP = 64;
+    private static final int LARGE_FAVICON_SIZE_DP = 48;
+    private static final int BACKGROUND_DRAWABLE_RES = R.drawable.img_tile_background;
+    private static final float FILLER_RADIUS_DP = 2f; // sync with the bg image radius
+    private static final int FILLER_FALLBACK_COLOR = Color.WHITE; // in case there is no favicon
+    private static final int OVERLINE_WIDTH_RES = R.dimen.SiteTileOverline;
+    private static final int OVERLINE_COLOR_RES = R.color.SiteTileOverline;
+
+
+    // configuration
+    private Bitmap mFaviconBitmap = null;
+    private Paint mFundamentalPaint = null;
+    private int mFaviconWidth = 0;
+    private int mFaviconHeight = 0;
+    private int mForcedType = TYPE_AUTO;
+    private int mForcedFundamentalColor = COLOR_AUTO;
+
+    // static objects, to be recycled amongst instances (this is an optimization)
+    private static int sMediumPxThreshold = -1;
+    private static int sLargePxThreshold = -1;
+    private static int sLargeFaviconPx = -1;
+    private static float sRoundedRadius = -1;
+    private static Paint sBitmapPaint = null;
+    private static Rect sSrcRect = new Rect();
+    private static Rect sDstRect = new Rect();
+    private static RectF sRectF = new RectF();
+    private static Paint sOverlineOutlinePaint = null;
+    private static Drawable sBackgroundDrawable = null;
+    private static Rect sBackgroundDrawablePadding = new Rect();
+
+    // runtime params set on Layout
+    private int mCurrentWidth = 0;
+    private int mCurrentHeight = 0;
+    private int mCurrentType = TYPE_MEDIUM;
+    private boolean mCurrentBackgroundDrawn = false;
+    private boolean mFloating = false;
+    private int mPaddingLeft = 0;
+    private int mPaddingTop = 0;
+    private int mPaddingRight = 0;
+    private int mPaddingBottom = 0;
+
+
+
+    /* XML constructors */
+
+    public SiteTileView(Context context) {
+        super(context);
+        xmlInit(null, 0);
+    }
+
+    public SiteTileView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        xmlInit(attrs, 0);
+    }
+
+    public SiteTileView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        xmlInit(attrs, defStyle);
+    }
+
+
+    /* Programmatic Constructors */
+
+    public SiteTileView(Context context, Bitmap favicon) {
+        super(context);
+        init(favicon, COLOR_AUTO);
+    }
+
+    public SiteTileView(Context context, Bitmap favicon, int fundamentalColor) {
+        super(context);
+        init(favicon, fundamentalColor);
+    }
+
+
+    /**
+     * Changes the current favicon (and associated fundamental color) on the fly
+     */
+    public void replaceFavicon(Bitmap favicon) {
+        replaceFavicon(favicon, COLOR_AUTO);
+    }
+
+    /**
+     * Changes the current favicon (and associated fundamental color) on the fly
+     * @param favicon the new favicon
+     * @param fundamentalColor the new fudamental color, or COLOR_AUTO
+     */
+    public void replaceFavicon(Bitmap favicon, int fundamentalColor) {
+        init(favicon, fundamentalColor);
+        requestLayout();
+    }
+
+    /**
+     * Disables the automatic background and filling. Useful for things that are not really
+     * "Website Tiles", like folders.
+     * @param floating true to disable the background (defaults to false)
+     */
+    public void setFloating(boolean floating) {
+        mFloating = floating;
+        invalidate();
+    }
+
+
+    /**
+     * @return The fundamental color representing the site.
+     */
+    public int getFundamentalColor() {
+        if (mForcedFundamentalColor != COLOR_AUTO)
+            return mForcedFundamentalColor;
+        if (mFundamentalPaint == null)
+            mFundamentalPaint = createFundamentalPaint(mFaviconBitmap, COLOR_AUTO);
+        return mFundamentalPaint.getColor();
+    }
+
+
+    /*** private stuff ahead ***/
+
+    private void xmlInit(AttributeSet attrs, int defStyle) {
+        // load attributes
+        final TypedArray a = getContext().obtainStyledAttributes(attrs,
+                R.styleable.SiteTileView, defStyle, 0);
+
+        // fetch the drawable, if defined - then just extract and use the bitmap
+        final Drawable drawable = a.getDrawable(R.styleable.SiteTileView_android_src);
+        final Bitmap favicon = drawable instanceof BitmapDrawable ?
+                ((BitmapDrawable) drawable).getBitmap() : null;
+
+        // check if we disable shading (plain favicon)
+        if (a.getBoolean(R.styleable.SiteTileView_flat, false))
+            mForcedType = TYPE_SMALL;
+
+        // check if we want it floating (disable shadow and filler)
+        if (a.getBoolean(R.styleable.SiteTileView_floating, false))
+            mFloating = true;
+
+        // delete attribute resolution
+        a.recycle();
+
+        // proceed with real initialization
+        init(favicon, COLOR_AUTO);
+    }
+
+    private void init(Bitmap favicon, int fundamentalColor) {
+        mFaviconBitmap = favicon;
+        if (mFaviconBitmap != null) {
+            mFaviconWidth = mFaviconBitmap.getWidth();
+            mFaviconHeight = mFaviconBitmap.getHeight();
+        }
+
+        // don't compute the paint right now, just save any hint for later
+        mFundamentalPaint = null;
+        mForcedFundamentalColor = fundamentalColor;
+
+        // shared (static) resources initialization; except for background, inited on-demand
+        if (sMediumPxThreshold < 0) {
+            final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+
+            // heuristics thresholds
+            sMediumPxThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    THRESHOLD_MEDIUM_DP, displayMetrics);
+            sLargePxThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    THRESHOLD_LARGE_DP, displayMetrics);
+            sLargeFaviconPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    LARGE_FAVICON_SIZE_DP, displayMetrics);
+
+            // rounded radius
+            sRoundedRadius = FILLER_RADIUS_DP > 0 ? TypedValue.applyDimension(
+                    TypedValue.COMPLEX_UNIT_DIP, FILLER_RADIUS_DP, displayMetrics) : 0;
+
+            // bitmap paint (copy, smooth scale)
+            sBitmapPaint = new Paint();
+            sBitmapPaint.setColor(Color.BLACK);
+            sBitmapPaint.setFilterBitmap(true);
+
+            // overline configuration (null if we don't need it)
+            int ovlColor = getResources().getColor(OVERLINE_COLOR_RES);
+            float ovlWidthPx = getResources().getDimension(OVERLINE_WIDTH_RES);
+            if (ovlWidthPx > 0.5 && ovlColor != Color.TRANSPARENT) {
+                sOverlineOutlinePaint = new Paint();
+                sOverlineOutlinePaint.setColor(ovlColor);
+                sOverlineOutlinePaint.setStrokeWidth(ovlWidthPx);
+                sOverlineOutlinePaint.setStyle(Paint.Style.STROKE);
+            }
+        }
+
+        // change when clicked
+        setClickable(true);
+        // disable by default the long click
+        setLongClickable(false);
+    }
+
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        mCurrentWidth = right - left;
+        mCurrentHeight = bottom - top;
+
+        // auto-determine the "TYPE_" from the physical size of the layout
+        if (mForcedType == TYPE_AUTO) {
+            if (mCurrentWidth < sMediumPxThreshold && mCurrentHeight < sMediumPxThreshold)
+                mCurrentType = TYPE_SMALL;
+            else if (mCurrentWidth < sLargePxThreshold && mCurrentHeight < sLargePxThreshold)
+                mCurrentType = TYPE_MEDIUM;
+            else
+                mCurrentType = TYPE_LARGE;
+        } else {
+            // or use the forced one, if defined
+            mCurrentType = mForcedType;
+        }
+
+        // set or remove the background (if the need changed!)
+        boolean requiresBackground = mCurrentType >= TYPE_MEDIUM;
+        if (requiresBackground && !mCurrentBackgroundDrawn) {
+            // draw the background
+            mCurrentBackgroundDrawn = true;
+
+            // load the background just the first time, on demand (it may fail too)
+            if (sBackgroundDrawable == null) {
+                sBackgroundDrawable = getResources().getDrawable(BACKGROUND_DRAWABLE_RES);
+                if (sBackgroundDrawable != null)
+                    sBackgroundDrawable.getPadding(sBackgroundDrawablePadding);
+            }
+
+            // background -> padding
+            mPaddingLeft = sBackgroundDrawablePadding.left;
+            mPaddingTop = sBackgroundDrawablePadding.top;
+            mPaddingRight = sBackgroundDrawablePadding.right;
+            mPaddingBottom = sBackgroundDrawablePadding.bottom;
+        } else if (!requiresBackground && mCurrentBackgroundDrawn) {
+            // turn off background drawing
+            mCurrentBackgroundDrawn = false;
+
+            // no background -> no padding
+            mPaddingLeft = 0;
+            mPaddingTop = 0;
+            mPaddingRight = 0;
+            mPaddingBottom = 0;
+        }
+
+        // just proceed, do nothing here
+        super.onLayout(changed, left, top, right, bottom);
+    }
+
+    @Override
+    public void setPressed(boolean pressed) {
+        super.setPressed(pressed);
+        // schedule a repaint to show pressed/released
+        invalidate();
+    }
+
+    @Override
+    public void setSelected(boolean selected) {
+        super.setSelected(selected);
+        // schedule a repaint to show selected
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // Selection State: make everything smaller
+        if (isSelected()) {
+            float scale = 0.8f;
+            canvas.translate(mCurrentWidth * (1 - scale) / 2, mCurrentHeight * (1 - scale) / 2);
+            canvas.scale(scale, scale);
+        }
+
+        // Pressed state: make the button reach the finger
+        if (isPressed()) {
+            float scale = 1.1f;
+            canvas.translate(mCurrentWidth * (1 - scale) / 2, mCurrentHeight * (1 - scale) / 2);
+            canvas.scale(scale, scale);
+        }
+
+        final int left = mPaddingLeft;
+        final int top = mPaddingTop;
+        final int right = mCurrentWidth - mPaddingRight;
+        final int bottom = mCurrentHeight - mPaddingBottom;
+        final int contentWidth = right - left;
+        final int contentHeight = bottom - top;
+
+        // A. the background drawable (if set)
+        boolean requiresBackground = mCurrentBackgroundDrawn && sBackgroundDrawable != null
+                && !isPressed() && !mFloating;
+        if (requiresBackground) {
+            sBackgroundDrawable.setBounds(0, 0, mCurrentWidth, mCurrentHeight);
+            sBackgroundDrawable.draw(canvas);
+        }
+
+        // B. (when needed) draw the background rectangle; sharp our rounded
+        boolean requiresFundamentalFiller = mCurrentType >= TYPE_LARGE && !mFloating;
+        if (requiresFundamentalFiller) {
+            // create the filler paint on demand (not all icons need it)
+            if (mFundamentalPaint == null)
+                mFundamentalPaint = createFundamentalPaint(mFaviconBitmap, mForcedFundamentalColor);
+
+            // paint if not white, since requiresBackground already painted it white
+            int fundamentalColor = mFundamentalPaint.getColor();
+            if (fundamentalColor != COLOR_AUTO &&
+                    (fundamentalColor != Color.WHITE || !requiresBackground)) {
+                if (sRoundedRadius >= 1.) {
+                    sRectF.set(left, top, right, bottom);
+                    canvas.drawRoundRect(sRectF, sRoundedRadius, sRoundedRadius, mFundamentalPaint);
+                } else
+                    canvas.drawRect(left, top, right, bottom, mFundamentalPaint);
+            }
+        }
+
+        // C. (if present) draw the favicon
+        boolean requiresFavicon = mFaviconBitmap != null
+                && mFaviconWidth > 1 && mFaviconHeight > 1;
+        if (requiresFavicon) {
+            // destination can either fill, or auto-center
+            boolean fillSpace = mCurrentType <= TYPE_MEDIUM;
+            if (fillSpace || contentWidth < sLargeFaviconPx || contentHeight < sLargeFaviconPx) {
+                sDstRect.set(left, top, right, bottom);
+            } else {
+                int dstLeft = left + (contentWidth - sLargeFaviconPx) / 2;
+                int dstTop = top + (contentHeight - sLargeFaviconPx) / 2;
+                sDstRect.set(dstLeft, dstTop, dstLeft + sLargeFaviconPx, dstTop + sLargeFaviconPx);
+            }
+
+            // source has to 'crop proportionally' to keep the dest aspect ratio
+            sSrcRect.set(0, 0, mFaviconWidth, mFaviconHeight);
+            int sW = sSrcRect.width();
+            int sH = sSrcRect.height();
+            int dW = sDstRect.width();
+            int dH = sDstRect.height();
+            if (sW > 4 && sH > 4 && dW > 4 && dH > 4) {
+                float hScale = (float) dW / (float) sW;
+                float vScale = (float) dH / (float) sH;
+                if (hScale == vScale) {
+                    // no transformation needed, just zoom
+                } else if (hScale < vScale) {
+                    // horizontal crop
+                    float hCrop = 1 - hScale / vScale;
+                    int hCropPx = (int) (sW * hCrop / 2 + 0.5);
+                    sSrcRect.left += hCropPx;
+                    sSrcRect.right -= hCropPx;
+                    canvas.drawBitmap(mFaviconBitmap, sSrcRect, sDstRect, sBitmapPaint);
+                } else {
+                    // vertical crop
+                    float vCrop = 1 - vScale / hScale;
+                    int vCropPx = (int) (sH * vCrop / 2 + 0.5);
+                    sSrcRect.top += vCropPx;
+                    sSrcRect.bottom -= vCropPx;
+                }
+            }
+
+            // blit favicon, croppped, scaled
+            canvas.drawBitmap(mFaviconBitmap, sSrcRect, sDstRect, sBitmapPaint);
+        }
+
+        // D. (when needed) draw the thin over-line
+        boolean requiresOverline = mCurrentType == TYPE_MEDIUM
+                && sOverlineOutlinePaint != null;
+        if (requiresOverline) {
+            canvas.drawRect(left, top, right, bottom, sOverlineOutlinePaint);
+        }
+
+        /*if (true) { // DEBUG TYPE
+            Paint paint = new Paint();
+            paint.setColor(Color.BLACK);
+            paint.setTextSize(20);
+            canvas.drawText(String.valueOf(mCurrentType), 30, 30, paint);
+        }*/
+    }
+
+
+    /**
+     * Creates a fill Paint from the favicon, or using the forced color (if not COLOR_AUTO)
+     */
+    private static Paint createFundamentalPaint(Bitmap favicon, int forceFillColor) {
+        final Paint fillPaint = new Paint();
+        if (forceFillColor != COLOR_AUTO)
+            fillPaint.setColor(forceFillColor);
+        else
+            fillPaint.setColor(guessFundamentalColor(favicon));
+        return fillPaint;
+    }
+
+    /**
+     * This uses very stupid mechanism - a 9x9 grid sample on the borders and center - and selects
+     * the color with the most frequency, or the center.
+     *
+     * @param bitmap the bitmap to guesss the color about
+     * @return a Color
+     */
+    private static int guessFundamentalColor(Bitmap bitmap) {
+        if (bitmap == null)
+            return FILLER_FALLBACK_COLOR;
+        int height = bitmap.getHeight();
+        int width = bitmap.getWidth();
+        if (height < 2 || width < 2)
+            return FILLER_FALLBACK_COLOR;
+
+        // pick up to 9 colors
+        // NOTE: the order of sampling sets the precendece, in case of ties
+        int[] pxColors = new int[9];
+        int idx = 0;
+        if ((pxColors[idx] = sampleColor(bitmap, width / 2, height / 2)) != 0) idx++;
+        if ((pxColors[idx] = sampleColor(bitmap, width / 2, height - 1)) != 0) idx++;
+        if ((pxColors[idx] = sampleColor(bitmap, width - 1, height - 1)) != 0) idx++;
+        if ((pxColors[idx] = sampleColor(bitmap, width - 1, height / 2)) != 0) idx++;
+        if ((pxColors[idx] = sampleColor(bitmap,         0, 0         )) != 0) idx++;
+        if ((pxColors[idx] = sampleColor(bitmap, width / 2, 0         )) != 0) idx++;
+        if ((pxColors[idx] = sampleColor(bitmap, width - 1, 0         )) != 0) idx++;
+        if ((pxColors[idx] = sampleColor(bitmap, 0        , height / 2)) != 0) idx++;
+        if ((pxColors[idx] = sampleColor(bitmap, 0        , height - 1)) != 0) idx++;
+
+        // find the most popular
+        int popColor = -1;
+        int popCount = -1;
+        for (int i = 0; i < idx; i++) {
+            int thisColor = pxColors[i];
+            int thisCount = 0;
+            for (int j = 0; j < idx; j++) {
+                if (pxColors[j] == thisColor)
+                    thisCount++;
+            }
+            if (thisCount > popCount) {
+                popColor = thisColor;
+                popCount = thisCount;
+            }
+        }
+        return popCount > -1 ? popColor : FILLER_FALLBACK_COLOR;
+    }
+
+    /**
+     * @return Color, but if it's 0, you should discard it (not representative)
+     */
+    private static int sampleColor(Bitmap bitmap, int x, int y) {
+        int color = bitmap.getPixel(x, y);
+        // discard semi-transparent pixels, because they're probably from a spurious border
+        // discard black pixels, because black is not a color (well, not a good looking one)
+        if ((color >>> 24) <= 128 || (color & 0xFFFFFF) == 0)
+            return 0;
+        return color;
+    }
+
+}
\ No newline at end of file
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 3e3c9aa..ef4c380 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -193,6 +193,8 @@
     // determine if webview is destroyed to MemoryMonitor
     private boolean mWebViewDestroyedByMemoryMonitor;
 
+    private String mTouchIconUrl;
+
     private Observable mFirstPixelObservable;
     private Observable mTabHistoryUpdateObservable;
 
@@ -634,6 +636,7 @@
 
         @Override
         public void beforeNavigation(WebView view, String url) {
+            mTouchIconUrl = null;
             if (BrowserCommandLine.hasSwitch("ui-low-power-mode")) {
                 return;
             }
@@ -726,6 +729,9 @@
         mCurrentState.mIncognito = view.isPrivateBrowsingEnabled();
     }
 
+    public String getTouchIconUrl() {
+        return mTouchIconUrl;
+    }
 
     public boolean isTabFullScreen() {
         return mFullScreen;
@@ -771,6 +777,8 @@
                 mWebViewController.attachSubWindow(Tab.this);
                 transport.setWebView(mSubView);
             } else {
+                capture();
+
                 final Tab newTab = mWebViewController.openTab(url,
                         Tab.this, true, true);
                 // This is special case for rendering links on a webpage in
@@ -919,6 +927,7 @@
                         mContext, cr, view);
                 mTouchIconLoader.execute(url);
             }
+            mTouchIconUrl = url;
         }
 
         @Override
diff --git a/src/com/android/browser/preferences/SiteSpecificPreferencesFragment.java b/src/com/android/browser/preferences/SiteSpecificPreferencesFragment.java
index 329f66c..1059507 100644
--- a/src/com/android/browser/preferences/SiteSpecificPreferencesFragment.java
+++ b/src/com/android/browser/preferences/SiteSpecificPreferencesFragment.java
@@ -229,6 +229,11 @@
         mSslCert = (parcel != null) ? SslCertificate.restoreState(parcel) : null;
 
         if (mSslCert != null) {
+            Preference pref = findPreference("site_security_info");
+            if (pref != null) {
+                pref.setSelectable(true);
+            }
+
             int certErrors = args.getInt(EXTRA_SECURITY_CERT_ERR, 0);
 
             if (certErrors == 0) {
diff --git a/src/com/android/browser/view/BookmarkContainer.java b/src/com/android/browser/view/BookmarkContainer.java
index 54f281c..730f127 100644
--- a/src/com/android/browser/view/BookmarkContainer.java
+++ b/src/com/android/browser/view/BookmarkContainer.java
@@ -26,6 +26,9 @@
 import android.view.ViewConfiguration;
 import android.widget.LinearLayout;
 
+import com.android.browser.R;
+import com.android.browser.SiteTileView;
+
 public class BookmarkContainer extends LinearLayout implements OnClickListener {
 
     private OnClickListener mClickListener;
@@ -53,13 +56,21 @@
     }
 
     @Override
-    public void setBackgroundDrawable(Drawable d) {
-        super.setBackgroundDrawable(d);
+    public void setOnClickListener(OnClickListener l) {
+        mClickListener = l;
+        View thumb =  findViewById(R.id.thumb_image);
+        if (thumb != null) {
+            thumb.setOnClickListener(l);
+        }
     }
 
     @Override
-    public void setOnClickListener(OnClickListener l) {
-        mClickListener = l;
+    public void setTag(int key, final Object tag) {
+        super.setTag(key, tag);
+        View thumb =  findViewById(R.id.thumb_image);
+        if (thumb != null) {
+            thumb.setTag(key, tag);
+        }
     }
 
     @Override