blob: e22404f52d353679c40b8f0740d00dcb2eae7786 [file] [log] [blame]
Pankaj Garg21dad562015-07-02 17:17:24 -07001/*
2 * Copyright (c) 2015, The Linux Foundation. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above
10 * copyright notice, this list of conditions and the following
11 * disclaimer in the documentation and/or other materials provided
12 * with the distribution.
13 * * Neither the name of The Linux Foundation nor the names of its
14 * contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*/
29package com.android.browser;
30
31import android.content.Context;
32import android.content.res.TypedArray;
33import android.graphics.Bitmap;
34import android.graphics.Canvas;
35import android.graphics.Color;
36import android.graphics.Paint;
37import android.graphics.Rect;
38import android.graphics.RectF;
39import android.graphics.drawable.BitmapDrawable;
40import android.graphics.drawable.Drawable;
41import android.util.AttributeSet;
42import android.util.DisplayMetrics;
43import android.util.TypedValue;
44import android.view.View;
45
46/**
47 * This represents a WebSite Tile that is created from a Drawable and will scale across any
48 * area this is externally layouted to. There are 3 possible looks:
49 * - just the favicon (TYPE_SMALL)
50 * - drop-shadow plus a thin overlay border (1dp) (TYPE_MEDIUM)
51 * - centered favicon, extended color, rounded base (TYPE_LARGE)
52 *
53 * By centralizing everything in this class we make customization of looks much easier.
54 *
55 * NOTES:
56 * - do not set a background from the outside; this overrides it automatically
57 */
58public class SiteTileView extends View {
59
Pankaj Garg66f8cef2015-07-07 17:29:03 -070060 // public trust level constants
61 public static final int TRUST_UNKNOWN = 0; // default
62 public static final int TRUST_AVOID = 0x01;
63 public static final int TRUST_UNTRUSTED = 0x02;
64 public static final int TRUST_TRUSTED = 0x04;
65 private static final int TRUST_MASK = 0x07;
Pankaj Garg21dad562015-07-02 17:17:24 -070066
67
68 // static configuration
69 private static final int THRESHOLD_MEDIUM_DP = 32;
70 private static final int THRESHOLD_LARGE_DP = 64;
71 private static final int LARGE_FAVICON_SIZE_DP = 48;
72 private static final int BACKGROUND_DRAWABLE_RES = R.drawable.img_tile_background;
73 private static final float FILLER_RADIUS_DP = 2f; // sync with the bg image radius
74 private static final int FILLER_FALLBACK_COLOR = Color.WHITE; // in case there is no favicon
Pankaj Garg66f8cef2015-07-07 17:29:03 -070075 private static final int OVERLINE_WIDTH_RES = R.dimen.SiteTileOverlineWidth;
76 private static final int OVERLINE_COLOR_DEFAULT_RES = R.color.SiteTileOverlineUnknown;
77 private static final int OVERLINE_COLOR_TRUSTED_RES = R.color.SiteTileOverlineTrusted;
78 private static final int OVERLINE_COLOR_UNTRUSTED_RES = R.color.SiteTileOverlineUntrusted;
79 private static final int OVERLINE_COLOR_AVOID_RES = R.color.SiteTileOverlineAvoid;
80 private static final boolean SHOW_EXTRAS_BLOCKED_COUNT = true;
81
82 // internal enums
83 private static final int TYPE_SMALL = 1;
84 private static final int TYPE_MEDIUM = 2;
85 private static final int TYPE_LARGE = 3;
86 private static final int TYPE_AUTO = 0;
87 private static final int COLOR_AUTO = 0;
Pankaj Garg21dad562015-07-02 17:17:24 -070088
89
90 // configuration
91 private Bitmap mFaviconBitmap = null;
92 private Paint mFundamentalPaint = null;
93 private int mFaviconWidth = 0;
94 private int mFaviconHeight = 0;
95 private int mForcedType = TYPE_AUTO;
96 private int mForcedFundamentalColor = COLOR_AUTO;
Pankaj Garg66f8cef2015-07-07 17:29:03 -070097 private boolean mFloating = false;
98 private int mTrustLevel = TRUST_UNKNOWN;
99 private int mExtraBlockedObjectsCount = 0;
100 private boolean mExtrasShown = false;
101
102 // runtime params set on Layout
103 private int mCurrentWidth = 0;
104 private int mCurrentHeight = 0;
105 private int mCurrentType = TYPE_MEDIUM;
106 private int mPaddingLeft = 0;
107 private int mPaddingTop = 0;
108 private int mPaddingRight = 0;
109 private int mPaddingBottom = 0;
110 private boolean mCurrentBackgroundDrawn = false;
Pankaj Garg21dad562015-07-02 17:17:24 -0700111
112 // static objects, to be recycled amongst instances (this is an optimization)
113 private static int sMediumPxThreshold = -1;
114 private static int sLargePxThreshold = -1;
115 private static int sLargeFaviconPx = -1;
116 private static float sRoundedRadius = -1;
117 private static Paint sBitmapPaint = null;
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700118 private static Paint sExtrasPaint = null;
Pankaj Garg21dad562015-07-02 17:17:24 -0700119 private static Rect sSrcRect = new Rect();
120 private static Rect sDstRect = new Rect();
121 private static RectF sRectF = new RectF();
122 private static Paint sOverlineOutlinePaint = null;
123 private static Drawable sBackgroundDrawable = null;
124 private static Rect sBackgroundDrawablePadding = new Rect();
125
Pankaj Garg21dad562015-07-02 17:17:24 -0700126
127 /* XML constructors */
128
129 public SiteTileView(Context context) {
130 super(context);
131 xmlInit(null, 0);
132 }
133
134 public SiteTileView(Context context, AttributeSet attrs) {
135 super(context, attrs);
136 xmlInit(attrs, 0);
137 }
138
139 public SiteTileView(Context context, AttributeSet attrs, int defStyle) {
140 super(context, attrs, defStyle);
141 xmlInit(attrs, defStyle);
142 }
143
144
145 /* Programmatic Constructors */
146
147 public SiteTileView(Context context, Bitmap favicon) {
148 super(context);
149 init(favicon, COLOR_AUTO);
150 }
151
152 public SiteTileView(Context context, Bitmap favicon, int fundamentalColor) {
153 super(context);
154 init(favicon, fundamentalColor);
155 }
156
157
158 /**
159 * Changes the current favicon (and associated fundamental color) on the fly
160 */
161 public void replaceFavicon(Bitmap favicon) {
162 replaceFavicon(favicon, COLOR_AUTO);
163 }
164
165 /**
166 * Changes the current favicon (and associated fundamental color) on the fly
167 * @param favicon the new favicon
168 * @param fundamentalColor the new fudamental color, or COLOR_AUTO
169 */
170 public void replaceFavicon(Bitmap favicon, int fundamentalColor) {
171 init(favicon, fundamentalColor);
172 requestLayout();
173 }
174
175 /**
176 * Disables the automatic background and filling. Useful for things that are not really
177 * "Website Tiles", like folders.
178 * @param floating true to disable the background (defaults to false)
179 */
180 public void setFloating(boolean floating) {
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700181 if (mFloating != floating) {
182 mFloating = floating;
183 invalidate();
184 }
Pankaj Garg21dad562015-07-02 17:17:24 -0700185 }
186
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700187 /**
188 * For 'Medium' tiles updates the border and corner badges
189 * @param trustLevel one of the TRUST_ constants
190 */
191 public void setTrustLevel(int trustLevel) {
192 if (mTrustLevel != trustLevel) {
193 mTrustLevel = trustLevel;
194 if (requiresOverline())
195 invalidate();
196 }
197 }
198
199 /**
200 * Sets the number of objects blocked (a positive contribution to the page). Presentation
201 * may or may not have the number indication.
202 * @param sessionCounter Counter of blocked objects. Use 0 to not display anything.
203 */
204 public void setExtraBlockedObjectsCount(int sessionCounter) {
205 if (sessionCounter != mExtraBlockedObjectsCount) {
206 mExtraBlockedObjectsCount = sessionCounter;
207 updateExtrasShown();
208 if (SHOW_EXTRAS_BLOCKED_COUNT)
209 invalidate();
210 }
211 }
212
213 private void updateExtrasShown() {
214 boolean shouldBeShown = mExtraBlockedObjectsCount > 0;
215 if (shouldBeShown != mExtrasShown) {
216 mExtrasShown = shouldBeShown;
217 invalidate();
218 }
219 }
220
221
Pankaj Garg21dad562015-07-02 17:17:24 -0700222
223 /**
224 * @return The fundamental color representing the site.
225 */
226 public int getFundamentalColor() {
227 if (mForcedFundamentalColor != COLOR_AUTO)
228 return mForcedFundamentalColor;
229 if (mFundamentalPaint == null)
230 mFundamentalPaint = createFundamentalPaint(mFaviconBitmap, COLOR_AUTO);
231 return mFundamentalPaint.getColor();
232 }
233
234
235 /*** private stuff ahead ***/
236
237 private void xmlInit(AttributeSet attrs, int defStyle) {
238 // load attributes
239 final TypedArray a = getContext().obtainStyledAttributes(attrs,
240 R.styleable.SiteTileView, defStyle, 0);
241
242 // fetch the drawable, if defined - then just extract and use the bitmap
243 final Drawable drawable = a.getDrawable(R.styleable.SiteTileView_android_src);
244 final Bitmap favicon = drawable instanceof BitmapDrawable ?
245 ((BitmapDrawable) drawable).getBitmap() : null;
246
247 // check if we disable shading (plain favicon)
248 if (a.getBoolean(R.styleable.SiteTileView_flat, false))
249 mForcedType = TYPE_SMALL;
250
251 // check if we want it floating (disable shadow and filler)
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700252 setFloating(a.getBoolean(R.styleable.SiteTileView_floating, false));
253
254 // read the trust level (unknown, aka 'default', if not present)
255 setTrustLevel(a.getInteger(R.styleable.SiteTileView_trustLevel, TRUST_UNKNOWN)
256 & TRUST_MASK);
257
258 // read the amount of blocked objects (or 0 if not present)
259 setExtraBlockedObjectsCount(a.getInteger(R.styleable.SiteTileView_blockedObjects, 0));
Pankaj Garg21dad562015-07-02 17:17:24 -0700260
261 // delete attribute resolution
262 a.recycle();
263
264 // proceed with real initialization
265 init(favicon, COLOR_AUTO);
266 }
267
268 private void init(Bitmap favicon, int fundamentalColor) {
269 mFaviconBitmap = favicon;
270 if (mFaviconBitmap != null) {
271 mFaviconWidth = mFaviconBitmap.getWidth();
272 mFaviconHeight = mFaviconBitmap.getHeight();
273 }
274
275 // don't compute the paint right now, just save any hint for later
276 mFundamentalPaint = null;
277 mForcedFundamentalColor = fundamentalColor;
278
279 // shared (static) resources initialization; except for background, inited on-demand
280 if (sMediumPxThreshold < 0) {
281 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
282
283 // heuristics thresholds
284 sMediumPxThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
285 THRESHOLD_MEDIUM_DP, displayMetrics);
286 sLargePxThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
287 THRESHOLD_LARGE_DP, displayMetrics);
288 sLargeFaviconPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
289 LARGE_FAVICON_SIZE_DP, displayMetrics);
290
291 // rounded radius
292 sRoundedRadius = FILLER_RADIUS_DP > 0 ? TypedValue.applyDimension(
293 TypedValue.COMPLEX_UNIT_DIP, FILLER_RADIUS_DP, displayMetrics) : 0;
294
295 // bitmap paint (copy, smooth scale)
296 sBitmapPaint = new Paint();
297 sBitmapPaint.setColor(Color.BLACK);
298 sBitmapPaint.setFilterBitmap(true);
299
300 // overline configuration (null if we don't need it)
Pankaj Garg21dad562015-07-02 17:17:24 -0700301 float ovlWidthPx = getResources().getDimension(OVERLINE_WIDTH_RES);
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700302 if (ovlWidthPx > 0.5) {
Pankaj Garg21dad562015-07-02 17:17:24 -0700303 sOverlineOutlinePaint = new Paint();
Pankaj Garg21dad562015-07-02 17:17:24 -0700304 sOverlineOutlinePaint.setStrokeWidth(ovlWidthPx);
305 sOverlineOutlinePaint.setStyle(Paint.Style.STROKE);
306 }
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700307
308 // extras paint (anti-aliased)
309 sExtrasPaint = new Paint();
310 sExtrasPaint.setAntiAlias(true);
311 sExtrasPaint.setColor(getResources().getColor(OVERLINE_COLOR_TRUSTED_RES));
312 sExtrasPaint.setStrokeWidth(Math.max(ovlWidthPx, 1));
Pankaj Garg21dad562015-07-02 17:17:24 -0700313 }
314
315 // change when clicked
316 setClickable(true);
317 // disable by default the long click
318 setLongClickable(false);
319 }
320
321
322 @Override
323 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
324 mCurrentWidth = right - left;
325 mCurrentHeight = bottom - top;
326
327 // auto-determine the "TYPE_" from the physical size of the layout
328 if (mForcedType == TYPE_AUTO) {
329 if (mCurrentWidth < sMediumPxThreshold && mCurrentHeight < sMediumPxThreshold)
330 mCurrentType = TYPE_SMALL;
331 else if (mCurrentWidth < sLargePxThreshold && mCurrentHeight < sLargePxThreshold)
332 mCurrentType = TYPE_MEDIUM;
333 else
334 mCurrentType = TYPE_LARGE;
335 } else {
336 // or use the forced one, if defined
337 mCurrentType = mForcedType;
338 }
339
340 // set or remove the background (if the need changed!)
341 boolean requiresBackground = mCurrentType >= TYPE_MEDIUM;
342 if (requiresBackground && !mCurrentBackgroundDrawn) {
343 // draw the background
344 mCurrentBackgroundDrawn = true;
345
346 // load the background just the first time, on demand (it may fail too)
347 if (sBackgroundDrawable == null) {
348 sBackgroundDrawable = getResources().getDrawable(BACKGROUND_DRAWABLE_RES);
349 if (sBackgroundDrawable != null)
350 sBackgroundDrawable.getPadding(sBackgroundDrawablePadding);
351 }
352
353 // background -> padding
354 mPaddingLeft = sBackgroundDrawablePadding.left;
355 mPaddingTop = sBackgroundDrawablePadding.top;
356 mPaddingRight = sBackgroundDrawablePadding.right;
357 mPaddingBottom = sBackgroundDrawablePadding.bottom;
358 } else if (!requiresBackground && mCurrentBackgroundDrawn) {
359 // turn off background drawing
360 mCurrentBackgroundDrawn = false;
361
362 // no background -> no padding
363 mPaddingLeft = 0;
364 mPaddingTop = 0;
365 mPaddingRight = 0;
366 mPaddingBottom = 0;
367 }
368
369 // just proceed, do nothing here
370 super.onLayout(changed, left, top, right, bottom);
371 }
372
373 @Override
374 public void setPressed(boolean pressed) {
375 super.setPressed(pressed);
376 // schedule a repaint to show pressed/released
377 invalidate();
378 }
379
380 @Override
381 public void setSelected(boolean selected) {
382 super.setSelected(selected);
383 // schedule a repaint to show selected
384 invalidate();
385 }
386
387 @Override
388 protected void onDraw(Canvas canvas) {
389 super.onDraw(canvas);
390
391 // Selection State: make everything smaller
392 if (isSelected()) {
393 float scale = 0.8f;
394 canvas.translate(mCurrentWidth * (1 - scale) / 2, mCurrentHeight * (1 - scale) / 2);
395 canvas.scale(scale, scale);
396 }
397
398 // Pressed state: make the button reach the finger
399 if (isPressed()) {
400 float scale = 1.1f;
401 canvas.translate(mCurrentWidth * (1 - scale) / 2, mCurrentHeight * (1 - scale) / 2);
402 canvas.scale(scale, scale);
403 }
404
405 final int left = mPaddingLeft;
406 final int top = mPaddingTop;
407 final int right = mCurrentWidth - mPaddingRight;
408 final int bottom = mCurrentHeight - mPaddingBottom;
409 final int contentWidth = right - left;
410 final int contentHeight = bottom - top;
411
412 // A. the background drawable (if set)
413 boolean requiresBackground = mCurrentBackgroundDrawn && sBackgroundDrawable != null
414 && !isPressed() && !mFloating;
415 if (requiresBackground) {
416 sBackgroundDrawable.setBounds(0, 0, mCurrentWidth, mCurrentHeight);
417 sBackgroundDrawable.draw(canvas);
418 }
419
420 // B. (when needed) draw the background rectangle; sharp our rounded
421 boolean requiresFundamentalFiller = mCurrentType >= TYPE_LARGE && !mFloating;
422 if (requiresFundamentalFiller) {
423 // create the filler paint on demand (not all icons need it)
424 if (mFundamentalPaint == null)
425 mFundamentalPaint = createFundamentalPaint(mFaviconBitmap, mForcedFundamentalColor);
426
427 // paint if not white, since requiresBackground already painted it white
428 int fundamentalColor = mFundamentalPaint.getColor();
429 if (fundamentalColor != COLOR_AUTO &&
430 (fundamentalColor != Color.WHITE || !requiresBackground)) {
431 if (sRoundedRadius >= 1.) {
432 sRectF.set(left, top, right, bottom);
433 canvas.drawRoundRect(sRectF, sRoundedRadius, sRoundedRadius, mFundamentalPaint);
434 } else
435 canvas.drawRect(left, top, right, bottom, mFundamentalPaint);
436 }
437 }
438
439 // C. (if present) draw the favicon
440 boolean requiresFavicon = mFaviconBitmap != null
441 && mFaviconWidth > 1 && mFaviconHeight > 1;
442 if (requiresFavicon) {
443 // destination can either fill, or auto-center
444 boolean fillSpace = mCurrentType <= TYPE_MEDIUM;
445 if (fillSpace || contentWidth < sLargeFaviconPx || contentHeight < sLargeFaviconPx) {
446 sDstRect.set(left, top, right, bottom);
447 } else {
448 int dstLeft = left + (contentWidth - sLargeFaviconPx) / 2;
449 int dstTop = top + (contentHeight - sLargeFaviconPx) / 2;
450 sDstRect.set(dstLeft, dstTop, dstLeft + sLargeFaviconPx, dstTop + sLargeFaviconPx);
451 }
452
453 // source has to 'crop proportionally' to keep the dest aspect ratio
454 sSrcRect.set(0, 0, mFaviconWidth, mFaviconHeight);
455 int sW = sSrcRect.width();
456 int sH = sSrcRect.height();
457 int dW = sDstRect.width();
458 int dH = sDstRect.height();
459 if (sW > 4 && sH > 4 && dW > 4 && dH > 4) {
460 float hScale = (float) dW / (float) sW;
461 float vScale = (float) dH / (float) sH;
462 if (hScale == vScale) {
463 // no transformation needed, just zoom
464 } else if (hScale < vScale) {
465 // horizontal crop
466 float hCrop = 1 - hScale / vScale;
467 int hCropPx = (int) (sW * hCrop / 2 + 0.5);
468 sSrcRect.left += hCropPx;
469 sSrcRect.right -= hCropPx;
470 canvas.drawBitmap(mFaviconBitmap, sSrcRect, sDstRect, sBitmapPaint);
471 } else {
472 // vertical crop
473 float vCrop = 1 - vScale / hScale;
474 int vCropPx = (int) (sH * vCrop / 2 + 0.5);
475 sSrcRect.top += vCropPx;
476 sSrcRect.bottom -= vCropPx;
477 }
478 }
479
480 // blit favicon, croppped, scaled
481 canvas.drawBitmap(mFaviconBitmap, sSrcRect, sDstRect, sBitmapPaint);
482 }
483
484 // D. (when needed) draw the thin over-line
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700485 if (requiresOverline()) {
486 int colorRes;
487 switch (mTrustLevel) {
488 case TRUST_TRUSTED:
489 colorRes = OVERLINE_COLOR_TRUSTED_RES;
490 break;
491 case TRUST_UNTRUSTED:
492 colorRes = OVERLINE_COLOR_UNTRUSTED_RES;
493 break;
494 case TRUST_AVOID:
495 colorRes = OVERLINE_COLOR_AVOID_RES;
496 break;
497 default:
498 colorRes = OVERLINE_COLOR_DEFAULT_RES;
499 break;
500 }
501 int lineColor = getResources().getColor(colorRes);
502 if (lineColor != Color.TRANSPARENT) {
503 // draw the white inline first
504 boolean needSeparation = mTrustLevel != TRUST_UNKNOWN;
505 if (needSeparation) {
506 sOverlineOutlinePaint.setColor(Color.WHITE);
507 float d = sOverlineOutlinePaint.getStrokeWidth();
508 canvas.drawRect(left + d, top + d, right - d, bottom - d, sOverlineOutlinePaint);
509 }
510
511 // then draw the outline
512 sOverlineOutlinePaint.setColor(lineColor);
513 canvas.drawRect(left, top, right, bottom, sOverlineOutlinePaint);
514 }
515 }
516
517 // E. show extra, if requested
518 if (mExtrasShown) {
519 // as default, we show a bubble
520 int eRad = Math.min(2 * contentWidth / 5, sMediumPxThreshold / 4);
521 int eCX = Math.min(right - eRad / 2, mCurrentWidth - eRad); //left + (4 * contentWidth / 5) - eRad;
522 int eCY = Math.min(bottom - eRad / 4, mCurrentHeight - eRad);
523
524 // circle back
525 //canvas.drawCircle(eCX, eCY, eRad, sExtrasPaint);
526
527 // round rect back
528 sRectF.set(eCX - eRad, eCY - eRad, eCX + eRad, eCY + eRad);
529 sExtrasPaint.setStyle(Paint.Style.FILL);
530 sExtrasPaint.setColor(0xff666666);
531 canvas.drawRoundRect(sRectF, eRad / 2, eRad / 2, sExtrasPaint);
532
533 // DEBUG! -- draw blocked count
534 if (SHOW_EXTRAS_BLOCKED_COUNT && mExtraBlockedObjectsCount > 0) {
535 final Paint paint = new Paint();
536 float textSize = eRad * 1.2f;
537 paint.setColor(Color.WHITE);
538 paint.setAntiAlias(true);
539 paint.setTextSize(textSize);
540 String text = String.valueOf(mExtraBlockedObjectsCount);
541 int textWidth = Math.round(paint.measureText(text) / 2);
542 canvas.drawText(text, eCX - textWidth - 1, eCY + textSize / 3 + 1, paint);
543 }
544
545 // round rect stroke
546 sExtrasPaint.setStyle(Paint.Style.STROKE);
547 sExtrasPaint.setColor(0xFFeeeeee);
548 canvas.drawRoundRect(sRectF, eRad / 2, eRad / 2, sExtrasPaint);
Pankaj Garg21dad562015-07-02 17:17:24 -0700549 }
550
551 /*if (true) { // DEBUG TYPE
552 Paint paint = new Paint();
553 paint.setColor(Color.BLACK);
554 paint.setTextSize(20);
555 canvas.drawText(String.valueOf(mCurrentType), 30, 30, paint);
556 }*/
557 }
558
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700559 private boolean requiresOverline() {
560 return mCurrentType == TYPE_MEDIUM && sOverlineOutlinePaint != null && !mFloating;
561 }
562
Pankaj Garg21dad562015-07-02 17:17:24 -0700563
564 /**
565 * Creates a fill Paint from the favicon, or using the forced color (if not COLOR_AUTO)
566 */
567 private static Paint createFundamentalPaint(Bitmap favicon, int forceFillColor) {
568 final Paint fillPaint = new Paint();
569 if (forceFillColor != COLOR_AUTO)
570 fillPaint.setColor(forceFillColor);
571 else
572 fillPaint.setColor(guessFundamentalColor(favicon));
573 return fillPaint;
574 }
575
576 /**
577 * This uses very stupid mechanism - a 9x9 grid sample on the borders and center - and selects
578 * the color with the most frequency, or the center.
579 *
580 * @param bitmap the bitmap to guesss the color about
581 * @return a Color
582 */
583 private static int guessFundamentalColor(Bitmap bitmap) {
584 if (bitmap == null)
585 return FILLER_FALLBACK_COLOR;
586 int height = bitmap.getHeight();
587 int width = bitmap.getWidth();
588 if (height < 2 || width < 2)
589 return FILLER_FALLBACK_COLOR;
590
591 // pick up to 9 colors
592 // NOTE: the order of sampling sets the precendece, in case of ties
593 int[] pxColors = new int[9];
594 int idx = 0;
595 if ((pxColors[idx] = sampleColor(bitmap, width / 2, height / 2)) != 0) idx++;
596 if ((pxColors[idx] = sampleColor(bitmap, width / 2, height - 1)) != 0) idx++;
597 if ((pxColors[idx] = sampleColor(bitmap, width - 1, height - 1)) != 0) idx++;
598 if ((pxColors[idx] = sampleColor(bitmap, width - 1, height / 2)) != 0) idx++;
599 if ((pxColors[idx] = sampleColor(bitmap, 0, 0 )) != 0) idx++;
600 if ((pxColors[idx] = sampleColor(bitmap, width / 2, 0 )) != 0) idx++;
601 if ((pxColors[idx] = sampleColor(bitmap, width - 1, 0 )) != 0) idx++;
602 if ((pxColors[idx] = sampleColor(bitmap, 0 , height / 2)) != 0) idx++;
603 if ((pxColors[idx] = sampleColor(bitmap, 0 , height - 1)) != 0) idx++;
604
605 // find the most popular
606 int popColor = -1;
607 int popCount = -1;
608 for (int i = 0; i < idx; i++) {
609 int thisColor = pxColors[i];
610 int thisCount = 0;
611 for (int j = 0; j < idx; j++) {
612 if (pxColors[j] == thisColor)
613 thisCount++;
614 }
615 if (thisCount > popCount) {
616 popColor = thisColor;
617 popCount = thisCount;
618 }
619 }
620 return popCount > -1 ? popColor : FILLER_FALLBACK_COLOR;
621 }
622
623 /**
624 * @return Color, but if it's 0, you should discard it (not representative)
625 */
626 private static int sampleColor(Bitmap bitmap, int x, int y) {
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700627 final int color = bitmap.getPixel(x, y);
628
Pankaj Garg21dad562015-07-02 17:17:24 -0700629 // discard semi-transparent pixels, because they're probably from a spurious border
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700630 //if ((color >>> 24) <= 128)
631 // return 0;
632
633 // compose transparent pixels with white, since the BG will be white anyway
634 final int alpha = Color.alpha(color);
635 if (alpha == 0)
636 return Color.WHITE;
637 if (alpha < 255) {
638 // perform simplified Porter-Duff source-over
639 int dstContribution = 255 - alpha;
640 return Color.argb(255,
641 ((alpha * Color.red(color)) >> 8) + dstContribution,
642 ((alpha * Color.green(color)) >> 8) + dstContribution,
643 ((alpha * Color.blue(color)) >> 8) + dstContribution
644 );
645 }
646
Pankaj Garg21dad562015-07-02 17:17:24 -0700647 // discard black pixels, because black is not a color (well, not a good looking one)
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700648 if ((color & 0xFFFFFF) == 0)
Pankaj Garg21dad562015-07-02 17:17:24 -0700649 return 0;
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700650
Pankaj Garg21dad562015-07-02 17:17:24 -0700651 return color;
652 }
653
654}