diff options
author | Pankaj Garg <pgarg@codeaurora.org> | 2015-07-02 17:17:24 -0700 |
---|---|---|
committer | jrizzoli <joey@cyanogenmoditalia.it> | 2015-08-28 13:15:45 +0200 |
commit | 21dad566a57084c8c5eae66909f917ff7c1fd222 (patch) | |
tree | 6ad508be6d8cebba36c7521e01e8d80c9f285979 /src/com/android/browser/SiteTileView.java | |
parent | 680d3981b289233b9ff3d60683e99880dc90559c (diff) | |
download | android_packages_apps_Gello-21dad566a57084c8c5eae66909f917ff7c1fd222.tar.gz android_packages_apps_Gello-21dad566a57084c8c5eae66909f917ff7c1fd222.tar.bz2 android_packages_apps_Gello-21dad566a57084c8c5eae66909f917ff7c1fd222.zip |
Use tiles for bookmarks
- Use tile based bitmap for bookmarks and history
- Settings UI cleanup
Change-Id: If959cb0b8f110035b8dd2fefe8106e9c5d30f4f1
Diffstat (limited to 'src/com/android/browser/SiteTileView.java')
-rw-r--r-- | src/com/android/browser/SiteTileView.java | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/src/com/android/browser/SiteTileView.java b/src/com/android/browser/SiteTileView.java new file mode 100644 index 00000000..36f21c9d --- /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 |