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