blob: 8d69dbafe3ff8c826aa21285a681b043643442a2 [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;
Pankaj Garg07b2fd92015-07-15 12:07:37 -070032import android.content.res.Resources;
Pankaj Garg21dad562015-07-02 17:17:24 -070033import android.content.res.TypedArray;
34import android.graphics.Bitmap;
Pankaj Garg07b2fd92015-07-15 12:07:37 -070035import android.graphics.BitmapFactory;
Pankaj Garg21dad562015-07-02 17:17:24 -070036import android.graphics.Canvas;
37import android.graphics.Color;
38import android.graphics.Paint;
39import android.graphics.Rect;
40import android.graphics.RectF;
Pankaj Garg07b2fd92015-07-15 12:07:37 -070041import android.graphics.Typeface;
Pankaj Garg21dad562015-07-02 17:17:24 -070042import android.graphics.drawable.BitmapDrawable;
43import android.graphics.drawable.Drawable;
44import android.util.AttributeSet;
45import android.util.DisplayMetrics;
46import android.util.TypedValue;
47import android.view.View;
48
Sagar Dhawanca9ecfb2015-08-10 17:27:58 -070049import java.util.HashMap;
Pankaj Garg07b2fd92015-07-15 12:07:37 -070050import java.util.Map;
51
Pankaj Garg21dad562015-07-02 17:17:24 -070052/**
53 * This represents a WebSite Tile that is created from a Drawable and will scale across any
54 * area this is externally layouted to. There are 3 possible looks:
55 * - just the favicon (TYPE_SMALL)
56 * - drop-shadow plus a thin overlay border (1dp) (TYPE_MEDIUM)
57 * - centered favicon, extended color, rounded base (TYPE_LARGE)
58 *
59 * By centralizing everything in this class we make customization of looks much easier.
60 *
61 * NOTES:
62 * - do not set a background from the outside; this overrides it automatically
63 */
64public class SiteTileView extends View {
65
Pankaj Garg66f8cef2015-07-07 17:29:03 -070066 // public trust level constants
67 public static final int TRUST_UNKNOWN = 0; // default
68 public static final int TRUST_AVOID = 0x01;
69 public static final int TRUST_UNTRUSTED = 0x02;
70 public static final int TRUST_TRUSTED = 0x04;
71 private static final int TRUST_MASK = 0x07;
Pankaj Garg21dad562015-07-02 17:17:24 -070072
73
74 // static configuration
75 private static final int THRESHOLD_MEDIUM_DP = 32;
76 private static final int THRESHOLD_LARGE_DP = 64;
77 private static final int LARGE_FAVICON_SIZE_DP = 48;
78 private static final int BACKGROUND_DRAWABLE_RES = R.drawable.img_tile_background;
Pankaj Garg07b2fd92015-07-15 12:07:37 -070079 private static final int DEFAULT_SITE_FAVICON = 0;
Pankaj Garg21dad562015-07-02 17:17:24 -070080 private static final float FILLER_RADIUS_DP = 2f; // sync with the bg image radius
81 private static final int FILLER_FALLBACK_COLOR = Color.WHITE; // in case there is no favicon
Pankaj Garg07b2fd92015-07-15 12:07:37 -070082 private static final boolean BADGE_SHOW_BLOCKED_COUNT = false;
Pankaj Garg66f8cef2015-07-07 17:29:03 -070083
84 // internal enums
85 private static final int TYPE_SMALL = 1;
86 private static final int TYPE_MEDIUM = 2;
87 private static final int TYPE_LARGE = 3;
88 private static final int TYPE_AUTO = 0;
89 private static final int COLOR_AUTO = 0;
Pankaj Garg21dad562015-07-02 17:17:24 -070090
91
92 // configuration
93 private Bitmap mFaviconBitmap = null;
94 private Paint mFundamentalPaint = null;
95 private int mFaviconWidth = 0;
96 private int mFaviconHeight = 0;
Pankaj Garg21dad562015-07-02 17:17:24 -070097 private int mForcedFundamentalColor = COLOR_AUTO;
Pankaj Garg07b2fd92015-07-15 12:07:37 -070098 private boolean mBackgroundDisabled = false;
Pankaj Garg66f8cef2015-07-07 17:29:03 -070099 private int mTrustLevel = TRUST_UNKNOWN;
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700100 private int mBadgeBlockedObjectsCount = 0;
101 private boolean mBadgeHasCertIssues = false;
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700102
103 // runtime params set on Layout
104 private int mCurrentWidth = 0;
105 private int mCurrentHeight = 0;
106 private int mCurrentType = TYPE_MEDIUM;
107 private int mPaddingLeft = 0;
108 private int mPaddingTop = 0;
109 private int mPaddingRight = 0;
110 private int mPaddingBottom = 0;
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700111 private boolean mCurrentShadowDrawn = false;
Pankaj Garg21dad562015-07-02 17:17:24 -0700112
113 // static objects, to be recycled amongst instances (this is an optimization)
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700114 // NOTE: package-visible statics are for optimized usage inside FolderTileView as well
Pankaj Garg21dad562015-07-02 17:17:24 -0700115 private static int sMediumPxThreshold = -1;
116 private static int sLargePxThreshold = -1;
117 private static int sLargeFaviconPx = -1;
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700118 /* package */ static float sRoundedRadius = -1;
Pankaj Garg21dad562015-07-02 17:17:24 -0700119 private static Paint sBitmapPaint = null;
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700120 private static Paint sBadgeTextPaint = null;
Pankaj Garg21dad562015-07-02 17:17:24 -0700121 private static Rect sSrcRect = new Rect();
122 private static Rect sDstRect = new Rect();
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700123 /* package */ static RectF sRectF = new RectF();
Pankaj Garg21dad562015-07-02 17:17:24 -0700124 private static Drawable sBackgroundDrawable = null;
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700125 private static class BadgeAssets {
126 Drawable back;
127 Drawable accent;
128 int textColor;
129 }
130 private static Map<Integer, BadgeAssets> sBadges;
131 private static Bitmap sDefaultSiteBitmap = null;
132 /* package */ static Rect sBackgroundDrawablePadding = new Rect();
Pankaj Garg21dad562015-07-02 17:17:24 -0700133
Pankaj Garg21dad562015-07-02 17:17:24 -0700134
135 /* XML constructors */
136
137 public SiteTileView(Context context) {
138 super(context);
139 xmlInit(null, 0);
140 }
141
142 public SiteTileView(Context context, AttributeSet attrs) {
143 super(context, attrs);
144 xmlInit(attrs, 0);
145 }
146
147 public SiteTileView(Context context, AttributeSet attrs, int defStyle) {
148 super(context, attrs, defStyle);
149 xmlInit(attrs, defStyle);
150 }
151
152
153 /* Programmatic Constructors */
154
155 public SiteTileView(Context context, Bitmap favicon) {
156 super(context);
157 init(favicon, COLOR_AUTO);
158 }
159
160 public SiteTileView(Context context, Bitmap favicon, int fundamentalColor) {
161 super(context);
162 init(favicon, fundamentalColor);
163 }
164
165
166 /**
167 * Changes the current favicon (and associated fundamental color) on the fly
168 */
169 public void replaceFavicon(Bitmap favicon) {
170 replaceFavicon(favicon, COLOR_AUTO);
171 }
172
173 /**
174 * Changes the current favicon (and associated fundamental color) on the fly
175 * @param favicon the new favicon
176 * @param fundamentalColor the new fudamental color, or COLOR_AUTO
177 */
178 public void replaceFavicon(Bitmap favicon, int fundamentalColor) {
179 init(favicon, fundamentalColor);
180 requestLayout();
181 }
182
183 /**
184 * Disables the automatic background and filling. Useful for things that are not really
185 * "Website Tiles", like folders.
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700186 * @param disabled true to disable the background (defaults to false)
Pankaj Garg21dad562015-07-02 17:17:24 -0700187 */
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700188 public void setBackgroundDisabled(boolean disabled) {
189 if (mBackgroundDisabled != disabled) {
190 mBackgroundDisabled = disabled;
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700191 invalidate();
192 }
Pankaj Garg21dad562015-07-02 17:17:24 -0700193 }
194
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700195 /**
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700196 * This results in the Badge being updated
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700197 * @param trustLevel one of the TRUST_ constants
198 */
199 public void setTrustLevel(int trustLevel) {
200 if (mTrustLevel != trustLevel) {
201 mTrustLevel = trustLevel;
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700202 invalidate();
203 }
204 }
205
206 /**
207 * Tells that there will be some message about issues inside
208 * @param certIssues true if there are issues.
209 */
210 public void setBadgeHasCertIssues(boolean certIssues) {
211 if (certIssues != mBadgeHasCertIssues) {
212 mBadgeHasCertIssues = certIssues;
213 invalidate();
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700214 }
215 }
216
217 /**
218 * Sets the number of objects blocked (a positive contribution to the page). Presentation
219 * may or may not have the number indication.
220 * @param sessionCounter Counter of blocked objects. Use 0 to not display anything.
221 */
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700222 public void setBadgeBlockedObjectsCount(int sessionCounter) {
223 if (sessionCounter != mBadgeBlockedObjectsCount) {
224 // repaint if going from or to 0, or if showing the ads count
225 //noinspection PointlessBooleanExpression,ConstantConditions
226 if (mBadgeBlockedObjectsCount == 0 || sessionCounter == 0 || BADGE_SHOW_BLOCKED_COUNT)
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700227 invalidate();
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700228 mBadgeBlockedObjectsCount = sessionCounter;
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700229 }
230 }
231
Pankaj Garg21dad562015-07-02 17:17:24 -0700232
233 /**
234 * @return The fundamental color representing the site.
235 */
236 public int getFundamentalColor() {
237 if (mForcedFundamentalColor != COLOR_AUTO)
238 return mForcedFundamentalColor;
239 if (mFundamentalPaint == null)
240 mFundamentalPaint = createFundamentalPaint(mFaviconBitmap, COLOR_AUTO);
241 return mFundamentalPaint.getColor();
242 }
243
244
245 /*** private stuff ahead ***/
246
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700247 private boolean requiresBadge() {
248 return !mBackgroundDisabled && (mTrustLevel != TRUST_UNKNOWN || mBadgeHasCertIssues
249 || mBadgeBlockedObjectsCount > 0);
250 }
251
252 private int computeBadgeMessages() {
253 // special case, for TRUST_AVOID, always show the common accent
254 if (mTrustLevel == TRUST_AVOID)
255 return 0;
256
257 // recompute number of 'messages' inside the badge
258 int count = 0;
259 if (mBadgeHasCertIssues)
260 count++;
261 if (mBadgeBlockedObjectsCount > 0)
262 count++;
263
264 // add the number of blocked objects (-1, for having already counted the message) if needed
265 if (BADGE_SHOW_BLOCKED_COUNT)
266 count += mBadgeBlockedObjectsCount - 1;
267
268 return count;
269 }
270
Pankaj Garg21dad562015-07-02 17:17:24 -0700271 private void xmlInit(AttributeSet attrs, int defStyle) {
272 // load attributes
273 final TypedArray a = getContext().obtainStyledAttributes(attrs,
274 R.styleable.SiteTileView, defStyle, 0);
275
276 // fetch the drawable, if defined - then just extract and use the bitmap
277 final Drawable drawable = a.getDrawable(R.styleable.SiteTileView_android_src);
278 final Bitmap favicon = drawable instanceof BitmapDrawable ?
279 ((BitmapDrawable) drawable).getBitmap() : null;
280
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700281 // check if we want it background-less (disable shadow and filler)
282 setBackgroundDisabled(a.getBoolean(R.styleable.SiteTileView_disableBackground, false));
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700283
284 // read the trust level (unknown, aka 'default', if not present)
285 setTrustLevel(a.getInteger(R.styleable.SiteTileView_trustLevel, TRUST_UNKNOWN)
286 & TRUST_MASK);
287
288 // read the amount of blocked objects (or 0 if not present)
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700289 setBadgeBlockedObjectsCount(a.getInteger(R.styleable.SiteTileView_blockedObjects, 0));
Pankaj Garg21dad562015-07-02 17:17:24 -0700290
291 // delete attribute resolution
292 a.recycle();
293
294 // proceed with real initialization
295 init(favicon, COLOR_AUTO);
296 }
297
298 private void init(Bitmap favicon, int fundamentalColor) {
299 mFaviconBitmap = favicon;
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700300
301 // show a default favicon if nothing is set (consider removing this, it's ugly)
302 if (mFaviconBitmap == null && DEFAULT_SITE_FAVICON != 0) {
303 if (sDefaultSiteBitmap == null)
304 sDefaultSiteBitmap = BitmapFactory.decodeResource(getResources(),
305 DEFAULT_SITE_FAVICON);
306 mFaviconBitmap = sDefaultSiteBitmap;
307 fundamentalColor = 0xFF262626;
308 }
309
Pankaj Garg21dad562015-07-02 17:17:24 -0700310 if (mFaviconBitmap != null) {
311 mFaviconWidth = mFaviconBitmap.getWidth();
312 mFaviconHeight = mFaviconBitmap.getHeight();
313 }
314
315 // don't compute the paint right now, just save any hint for later
316 mFundamentalPaint = null;
317 mForcedFundamentalColor = fundamentalColor;
318
319 // shared (static) resources initialization; except for background, inited on-demand
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700320 ensureCommonLoaded(getResources());
Pankaj Garg21dad562015-07-02 17:17:24 -0700321
322 // change when clicked
323 setClickable(true);
Pankaj Garg21dad562015-07-02 17:17:24 -0700324 }
325
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700326 static void ensureCommonLoaded(Resources r) {
327 // check if already initialized
328 if (sMediumPxThreshold != -1)
329 return;
330
331 // heuristics thresholds
332 final DisplayMetrics displayMetrics = r.getDisplayMetrics();
333 sMediumPxThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
334 THRESHOLD_MEDIUM_DP, displayMetrics);
335 sLargePxThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
336 THRESHOLD_LARGE_DP, displayMetrics);
337 sLargeFaviconPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
338 LARGE_FAVICON_SIZE_DP, displayMetrics);
339
340 // rounded radius
341 sRoundedRadius = FILLER_RADIUS_DP > 0 ? TypedValue.applyDimension(
342 TypedValue.COMPLEX_UNIT_DIP, FILLER_RADIUS_DP, displayMetrics) : 0;
343
344 // bitmap paint (copy, smooth scale)
345 sBitmapPaint = new Paint();
346 sBitmapPaint.setColor(Color.BLACK);
347 sBitmapPaint.setFilterBitmap(true);
348
349 // badge text paint (anti-aliased)
350 sBadgeTextPaint = new Paint();
351 sBadgeTextPaint.setAntiAlias(true);
352 Typeface badgeTypeface = Typeface.create("sans-serif-medium", Typeface.NORMAL);
353 if (badgeTypeface != null)
354 sBadgeTextPaint.setTypeface(badgeTypeface);
355
356 // load the background (could be loaded on demand, but in the end it's always needed)
357 sBackgroundDrawable = r.getDrawable(BACKGROUND_DRAWABLE_RES);
358 if (sBackgroundDrawable != null)
359 sBackgroundDrawable.getPadding(sBackgroundDrawablePadding);
360
361 // load all the badge drawables
Sagar Dhawanca9ecfb2015-08-10 17:27:58 -0700362 sBadges = new HashMap<>();
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700363 loadBadgeResources(r, TRUST_AVOID, R.drawable.img_deco_tile_avoid,
364 R.drawable.img_deco_tile_avoid_accent, R.color.TileBadgeTextAvoid);
365 loadBadgeResources(r, TRUST_UNTRUSTED, R.drawable.img_deco_tile_untrusted,
366 R.drawable.img_deco_tile_untrusted_accent, R.color.TileBadgeTextUntrusted);
367 loadBadgeResources(r, TRUST_UNKNOWN, R.drawable.img_deco_tile_unknown,
368 R.drawable.img_deco_tile_unknown_accent, R.color.TileBadgeTextUnknown);
369 loadBadgeResources(r, TRUST_TRUSTED, R.drawable.img_deco_tile_verified,
370 R.drawable.img_deco_tile_verified_accent, R.color.TileBadgeTextVerified);
371 }
372
373 private static void loadBadgeResources(Resources r, int t, int back, int accent, int color) {
374 BadgeAssets ba = new BadgeAssets();
375 ba.back = back == 0 ? null : r.getDrawable(back);
376 ba.accent = accent == 0 ? null : r.getDrawable(accent);
377 ba.textColor = color == 0 ? Color.TRANSPARENT : r.getColor(color);
378 sBadges.put(t, ba);
379 }
380
381 static Rect getBackgroundDrawablePadding() {
382 return sBackgroundDrawablePadding != null ? sBackgroundDrawablePadding : new Rect();
383 }
Pankaj Garg21dad562015-07-02 17:17:24 -0700384
385 @Override
386 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
387 mCurrentWidth = right - left;
388 mCurrentHeight = bottom - top;
389
390 // auto-determine the "TYPE_" from the physical size of the layout
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700391 if (mCurrentWidth < sMediumPxThreshold && mCurrentHeight < sMediumPxThreshold)
392 mCurrentType = TYPE_SMALL;
393 else if (mCurrentWidth < sLargePxThreshold && mCurrentHeight < sLargePxThreshold)
394 mCurrentType = TYPE_MEDIUM;
395 else
396 mCurrentType = TYPE_LARGE;
Pankaj Garg21dad562015-07-02 17:17:24 -0700397
398 // set or remove the background (if the need changed!)
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700399 boolean requiresBackgroundDrawable = mCurrentType >= TYPE_MEDIUM;
400 if (requiresBackgroundDrawable && !mCurrentShadowDrawn) {
Pankaj Garg21dad562015-07-02 17:17:24 -0700401 // draw the background
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700402 mCurrentShadowDrawn = mCurrentType >= TYPE_LARGE;
Pankaj Garg21dad562015-07-02 17:17:24 -0700403
404 // background -> padding
405 mPaddingLeft = sBackgroundDrawablePadding.left;
406 mPaddingTop = sBackgroundDrawablePadding.top;
407 mPaddingRight = sBackgroundDrawablePadding.right;
408 mPaddingBottom = sBackgroundDrawablePadding.bottom;
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700409 } else if (!requiresBackgroundDrawable && mCurrentShadowDrawn) {
Pankaj Garg21dad562015-07-02 17:17:24 -0700410 // turn off background drawing
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700411 mCurrentShadowDrawn = false;
Pankaj Garg21dad562015-07-02 17:17:24 -0700412
413 // no background -> no padding
414 mPaddingLeft = 0;
415 mPaddingTop = 0;
416 mPaddingRight = 0;
417 mPaddingBottom = 0;
418 }
419
420 // just proceed, do nothing here
421 super.onLayout(changed, left, top, right, bottom);
422 }
423
424 @Override
425 public void setPressed(boolean pressed) {
426 super.setPressed(pressed);
427 // schedule a repaint to show pressed/released
428 invalidate();
429 }
430
431 @Override
432 public void setSelected(boolean selected) {
433 super.setSelected(selected);
434 // schedule a repaint to show selected
435 invalidate();
436 }
437
438 @Override
439 protected void onDraw(Canvas canvas) {
440 super.onDraw(canvas);
441
442 // Selection State: make everything smaller
443 if (isSelected()) {
444 float scale = 0.8f;
445 canvas.translate(mCurrentWidth * (1 - scale) / 2, mCurrentHeight * (1 - scale) / 2);
446 canvas.scale(scale, scale);
447 }
448
449 // Pressed state: make the button reach the finger
450 if (isPressed()) {
451 float scale = 1.1f;
452 canvas.translate(mCurrentWidth * (1 - scale) / 2, mCurrentHeight * (1 - scale) / 2);
453 canvas.scale(scale, scale);
454 }
455
456 final int left = mPaddingLeft;
457 final int top = mPaddingTop;
458 final int right = mCurrentWidth - mPaddingRight;
459 final int bottom = mCurrentHeight - mPaddingBottom;
460 final int contentWidth = right - left;
461 final int contentHeight = bottom - top;
462
463 // A. the background drawable (if set)
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700464 boolean requiresBackground = mCurrentShadowDrawn && sBackgroundDrawable != null
465 && !isPressed() && !mBackgroundDisabled;
Pankaj Garg21dad562015-07-02 17:17:24 -0700466 if (requiresBackground) {
467 sBackgroundDrawable.setBounds(0, 0, mCurrentWidth, mCurrentHeight);
468 sBackgroundDrawable.draw(canvas);
469 }
470
471 // B. (when needed) draw the background rectangle; sharp our rounded
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700472 boolean requiresFundamentalFiller = mCurrentType >= TYPE_LARGE && !mBackgroundDisabled;
Pankaj Garg21dad562015-07-02 17:17:24 -0700473 if (requiresFundamentalFiller) {
474 // create the filler paint on demand (not all icons need it)
475 if (mFundamentalPaint == null)
476 mFundamentalPaint = createFundamentalPaint(mFaviconBitmap, mForcedFundamentalColor);
477
478 // paint if not white, since requiresBackground already painted it white
479 int fundamentalColor = mFundamentalPaint.getColor();
480 if (fundamentalColor != COLOR_AUTO &&
481 (fundamentalColor != Color.WHITE || !requiresBackground)) {
482 if (sRoundedRadius >= 1.) {
483 sRectF.set(left, top, right, bottom);
484 canvas.drawRoundRect(sRectF, sRoundedRadius, sRoundedRadius, mFundamentalPaint);
485 } else
486 canvas.drawRect(left, top, right, bottom, mFundamentalPaint);
487 }
488 }
489
490 // C. (if present) draw the favicon
491 boolean requiresFavicon = mFaviconBitmap != null
492 && mFaviconWidth > 1 && mFaviconHeight > 1;
493 if (requiresFavicon) {
494 // destination can either fill, or auto-center
495 boolean fillSpace = mCurrentType <= TYPE_MEDIUM;
496 if (fillSpace || contentWidth < sLargeFaviconPx || contentHeight < sLargeFaviconPx) {
497 sDstRect.set(left, top, right, bottom);
498 } else {
499 int dstLeft = left + (contentWidth - sLargeFaviconPx) / 2;
500 int dstTop = top + (contentHeight - sLargeFaviconPx) / 2;
501 sDstRect.set(dstLeft, dstTop, dstLeft + sLargeFaviconPx, dstTop + sLargeFaviconPx);
502 }
503
504 // source has to 'crop proportionally' to keep the dest aspect ratio
505 sSrcRect.set(0, 0, mFaviconWidth, mFaviconHeight);
506 int sW = sSrcRect.width();
507 int sH = sSrcRect.height();
508 int dW = sDstRect.width();
509 int dH = sDstRect.height();
510 if (sW > 4 && sH > 4 && dW > 4 && dH > 4) {
511 float hScale = (float) dW / (float) sW;
512 float vScale = (float) dH / (float) sH;
513 if (hScale == vScale) {
514 // no transformation needed, just zoom
515 } else if (hScale < vScale) {
516 // horizontal crop
517 float hCrop = 1 - hScale / vScale;
518 int hCropPx = (int) (sW * hCrop / 2 + 0.5);
519 sSrcRect.left += hCropPx;
520 sSrcRect.right -= hCropPx;
521 canvas.drawBitmap(mFaviconBitmap, sSrcRect, sDstRect, sBitmapPaint);
522 } else {
523 // vertical crop
524 float vCrop = 1 - vScale / hScale;
525 int vCropPx = (int) (sH * vCrop / 2 + 0.5);
526 sSrcRect.top += vCropPx;
527 sSrcRect.bottom -= vCropPx;
528 }
529 }
530
531 // blit favicon, croppped, scaled
532 canvas.drawBitmap(mFaviconBitmap, sSrcRect, sDstRect, sBitmapPaint);
533 }
534
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700535 // D. show badge, if requested
536 if (requiresBadge()) {
537 // retrieve the badge resources
538 final BadgeAssets ba = sBadges.get(mTrustLevel);
539 if (ba != null) {
540
541 // paint back
542 final Drawable back = ba.back;
543 int badgeL = 0, badgeT = 0, badgeW = 0, badgeH = 0;
544 if (back != null) {
545 badgeW = back.getIntrinsicWidth();
546 badgeH = back.getIntrinsicHeight();
547 badgeL = mCurrentWidth - mPaddingRight / 3 - badgeW;
548 badgeT = mCurrentHeight - mPaddingBottom / 3 - badgeH;
549 back.setBounds(badgeL, badgeT, badgeL + badgeW, badgeT + badgeH);
550 back.draw(canvas);
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700551 }
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700552 int messagesCount = computeBadgeMessages();
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700553
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700554 // paint accent, if 0 messages
555 if (messagesCount < 1) {
556 final Drawable accent = ba.accent;
557 if (accent != null && badgeW > 0 && badgeH > 0) {
558 int accentW = accent.getIntrinsicWidth();
559 int accentH = accent.getIntrinsicHeight();
560 int accentL = badgeL + (badgeW - accentW) / 2;
561 int accentT = badgeT + (badgeH - accentH) / 2;
562 accent.setBounds(accentL, accentT, accentL + accentW, accentT + accentH);
563 accent.draw(canvas);
564 }
565 }
566 // at least 1 message, draw text
567 else if (Color.alpha(ba.textColor) > 0) {
568 float textSize = Math.min(2 * contentWidth / 5, sMediumPxThreshold / 4) * 1.1f;
569 sBadgeTextPaint.setTextSize(textSize);
570 sBadgeTextPaint.setColor(ba.textColor);
571 final String text = String.valueOf(messagesCount);
572 int textWidth = Math.round(sBadgeTextPaint.measureText(text) / 2);
573 int textCx = badgeL + badgeW / 2;
574 int textCy = badgeT + badgeH / 2;
575 canvas.drawText(text, textCx - textWidth, textCy + textSize / 3 + 1,
576 sBadgeTextPaint);
577 }
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700578 }
579 }
580
Pankaj Garg21dad562015-07-02 17:17:24 -0700581 /*if (true) { // DEBUG TYPE
582 Paint paint = new Paint();
583 paint.setColor(Color.BLACK);
584 paint.setTextSize(20);
585 canvas.drawText(String.valueOf(mCurrentType), 30, 30, paint);
586 }*/
587 }
588
589
590 /**
591 * Creates a fill Paint from the favicon, or using the forced color (if not COLOR_AUTO)
592 */
593 private static Paint createFundamentalPaint(Bitmap favicon, int forceFillColor) {
594 final Paint fillPaint = new Paint();
595 if (forceFillColor != COLOR_AUTO)
596 fillPaint.setColor(forceFillColor);
597 else
598 fillPaint.setColor(guessFundamentalColor(favicon));
599 return fillPaint;
600 }
601
602 /**
603 * This uses very stupid mechanism - a 9x9 grid sample on the borders and center - and selects
604 * the color with the most frequency, or the center.
605 *
606 * @param bitmap the bitmap to guesss the color about
607 * @return a Color
608 */
609 private static int guessFundamentalColor(Bitmap bitmap) {
610 if (bitmap == null)
611 return FILLER_FALLBACK_COLOR;
612 int height = bitmap.getHeight();
613 int width = bitmap.getWidth();
614 if (height < 2 || width < 2)
615 return FILLER_FALLBACK_COLOR;
616
617 // pick up to 9 colors
618 // NOTE: the order of sampling sets the precendece, in case of ties
619 int[] pxColors = new int[9];
620 int idx = 0;
621 if ((pxColors[idx] = sampleColor(bitmap, width / 2, height / 2)) != 0) idx++;
622 if ((pxColors[idx] = sampleColor(bitmap, width / 2, height - 1)) != 0) idx++;
623 if ((pxColors[idx] = sampleColor(bitmap, width - 1, height - 1)) != 0) idx++;
624 if ((pxColors[idx] = sampleColor(bitmap, width - 1, height / 2)) != 0) idx++;
625 if ((pxColors[idx] = sampleColor(bitmap, 0, 0 )) != 0) idx++;
626 if ((pxColors[idx] = sampleColor(bitmap, width / 2, 0 )) != 0) idx++;
627 if ((pxColors[idx] = sampleColor(bitmap, width - 1, 0 )) != 0) idx++;
628 if ((pxColors[idx] = sampleColor(bitmap, 0 , height / 2)) != 0) idx++;
629 if ((pxColors[idx] = sampleColor(bitmap, 0 , height - 1)) != 0) idx++;
630
631 // find the most popular
632 int popColor = -1;
633 int popCount = -1;
634 for (int i = 0; i < idx; i++) {
635 int thisColor = pxColors[i];
636 int thisCount = 0;
637 for (int j = 0; j < idx; j++) {
638 if (pxColors[j] == thisColor)
639 thisCount++;
640 }
641 if (thisCount > popCount) {
642 popColor = thisColor;
643 popCount = thisCount;
644 }
645 }
646 return popCount > -1 ? popColor : FILLER_FALLBACK_COLOR;
647 }
648
649 /**
650 * @return Color, but if it's 0, you should discard it (not representative)
651 */
652 private static int sampleColor(Bitmap bitmap, int x, int y) {
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700653 final int color = bitmap.getPixel(x, y);
654
Pankaj Garg21dad562015-07-02 17:17:24 -0700655 // discard semi-transparent pixels, because they're probably from a spurious border
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700656 //if ((color >>> 24) <= 128)
657 // return 0;
658
659 // compose transparent pixels with white, since the BG will be white anyway
660 final int alpha = Color.alpha(color);
661 if (alpha == 0)
662 return Color.WHITE;
663 if (alpha < 255) {
664 // perform simplified Porter-Duff source-over
665 int dstContribution = 255 - alpha;
666 return Color.argb(255,
667 ((alpha * Color.red(color)) >> 8) + dstContribution,
668 ((alpha * Color.green(color)) >> 8) + dstContribution,
669 ((alpha * Color.blue(color)) >> 8) + dstContribution
670 );
671 }
672
Pankaj Garg21dad562015-07-02 17:17:24 -0700673 // discard black pixels, because black is not a color (well, not a good looking one)
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700674 if ((color & 0xFFFFFF) == 0)
Pankaj Garg21dad562015-07-02 17:17:24 -0700675 return 0;
Pankaj Garg66f8cef2015-07-07 17:29:03 -0700676
Pankaj Garg21dad562015-07-02 17:17:24 -0700677 return color;
678 }
679
Pankaj Garg07b2fd92015-07-15 12:07:37 -0700680}