1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.icons; 18 19 import static android.graphics.Paint.ANTI_ALIAS_FLAG; 20 import static android.graphics.Paint.FILTER_BITMAP_FLAG; 21 22 import android.graphics.Bitmap; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.Paint; 26 import android.graphics.Path; 27 import android.graphics.PathMeasure; 28 import android.graphics.Rect; 29 import android.graphics.RectF; 30 import android.util.Log; 31 import android.view.ViewDebug; 32 33 /** 34 * Used to draw a notification dot on top of an icon. 35 */ 36 public class DotRenderer { 37 38 private static final String TAG = "DotRenderer"; 39 40 // The dot size is defined as a percentage of the app icon size. 41 private static final float SIZE_PERCENTAGE = 0.228f; 42 43 private final float mCircleRadius; 44 private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); 45 46 private final Bitmap mBackgroundWithShadow; 47 private final float mBitmapOffset; 48 49 // Stores the center x and y position as a percentage (0 to 1) of the icon size 50 private final float[] mRightDotPosition; 51 private final float[] mLeftDotPosition; 52 DotRenderer(int iconSizePx, Path iconShapePath, int pathSize)53 public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) { 54 int size = Math.round(SIZE_PERCENTAGE * iconSizePx); 55 ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT); 56 builder.ambientShadowAlpha = 88; 57 mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size); 58 mCircleRadius = builder.radius; 59 60 mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width. 61 62 // Find the points on the path that are closest to the top left and right corners. 63 mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1); 64 mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1); 65 } 66 getPathPoint(Path path, float size, float direction)67 private static float[] getPathPoint(Path path, float size, float direction) { 68 float halfSize = size / 2; 69 // Small delta so that we don't get a zero size triangle 70 float delta = 1; 71 72 float x = halfSize + direction * halfSize; 73 Path trianglePath = new Path(); 74 trianglePath.moveTo(halfSize, halfSize); 75 trianglePath.lineTo(x + delta * direction, 0); 76 trianglePath.lineTo(x, -delta); 77 trianglePath.close(); 78 79 trianglePath.op(path, Path.Op.INTERSECT); 80 float[] pos = new float[2]; 81 new PathMeasure(trianglePath, false).getPosTan(0, pos, null); 82 83 pos[0] = pos[0] / size; 84 pos[1] = pos[1] / size; 85 return pos; 86 } 87 getLeftDotPosition()88 public float[] getLeftDotPosition() { 89 return mLeftDotPosition; 90 } 91 getRightDotPosition()92 public float[] getRightDotPosition() { 93 return mRightDotPosition; 94 } 95 96 /** 97 * Draw a circle on top of the canvas according to the given params. 98 */ draw(Canvas canvas, DrawParams params)99 public void draw(Canvas canvas, DrawParams params) { 100 if (params == null) { 101 Log.e(TAG, "Invalid null argument(s) passed in call to draw."); 102 return; 103 } 104 canvas.save(); 105 106 Rect iconBounds = params.iconBounds; 107 float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition; 108 float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0]; 109 float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1]; 110 111 // Ensure dot fits entirely in canvas clip bounds. 112 Rect canvasBounds = canvas.getClipBounds(); 113 float offsetX = params.leftAlign 114 ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset)) 115 : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset)); 116 float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset)); 117 118 // We draw the dot relative to its center. 119 canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY); 120 canvas.scale(params.scale, params.scale); 121 122 mCirclePaint.setColor(Color.BLACK); 123 canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint); 124 mCirclePaint.setColor(params.color); 125 canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); 126 canvas.restore(); 127 } 128 129 public static class DrawParams { 130 /** The color (possibly based on the icon) to use for the dot. */ 131 @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) 132 public int color; 133 /** The bounds of the icon that the dot is drawn on top of. */ 134 @ViewDebug.ExportedProperty(category = "notification dot") 135 public Rect iconBounds = new Rect(); 136 /** The progress of the animation, from 0 to 1. */ 137 @ViewDebug.ExportedProperty(category = "notification dot") 138 public float scale; 139 /** Whether the dot should align to the top left of the icon rather than the top right. */ 140 @ViewDebug.ExportedProperty(category = "notification dot") 141 public boolean leftAlign; 142 } 143 } 144