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 
53     private static final int MIN_DOT_SIZE = 1;
DotRenderer(int iconSizePx, Path iconShapePath, int pathSize)54     public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
55         int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
56         if (size <= 0) {
57             size = MIN_DOT_SIZE;
58         }
59         ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
60         builder.ambientShadowAlpha = 88;
61         mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
62         mCircleRadius = builder.radius;
63 
64         mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
65 
66         // Find the points on the path that are closest to the top left and right corners.
67         mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
68         mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
69     }
70 
getPathPoint(Path path, float size, float direction)71     private static float[] getPathPoint(Path path, float size, float direction) {
72         float halfSize = size / 2;
73         // Small delta so that we don't get a zero size triangle
74         float delta = 1;
75 
76         float x = halfSize + direction * halfSize;
77         Path trianglePath = new Path();
78         trianglePath.moveTo(halfSize, halfSize);
79         trianglePath.lineTo(x + delta * direction, 0);
80         trianglePath.lineTo(x, -delta);
81         trianglePath.close();
82 
83         trianglePath.op(path, Path.Op.INTERSECT);
84         float[] pos = new float[2];
85         new PathMeasure(trianglePath, false).getPosTan(0, pos, null);
86 
87         pos[0] = pos[0] / size;
88         pos[1] = pos[1] / size;
89         return pos;
90     }
91 
getLeftDotPosition()92     public float[] getLeftDotPosition() {
93         return mLeftDotPosition;
94     }
95 
getRightDotPosition()96     public float[] getRightDotPosition() {
97         return mRightDotPosition;
98     }
99 
100     /**
101      * Draw a circle on top of the canvas according to the given params.
102      */
draw(Canvas canvas, DrawParams params)103     public void draw(Canvas canvas, DrawParams params) {
104         if (params == null) {
105             Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
106             return;
107         }
108         canvas.save();
109 
110         Rect iconBounds = params.iconBounds;
111         float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
112         float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
113         float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];
114 
115         // Ensure dot fits entirely in canvas clip bounds.
116         Rect canvasBounds = canvas.getClipBounds();
117         float offsetX = params.leftAlign
118                 ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset))
119                 : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset));
120         float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset));
121 
122         // We draw the dot relative to its center.
123         canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
124         canvas.scale(params.scale, params.scale);
125 
126         mCirclePaint.setColor(Color.BLACK);
127         canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint);
128         mCirclePaint.setColor(params.dotColor);
129         canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
130         canvas.restore();
131     }
132 
133     public static class DrawParams {
134         /** The color (possibly based on the icon) to use for the dot. */
135         @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
136         public int dotColor;
137         /** The color (possibly based on the icon) to use for a predicted app. */
138         @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
139         public int appColor;
140         /** The bounds of the icon that the dot is drawn on top of. */
141         @ViewDebug.ExportedProperty(category = "notification dot")
142         public Rect iconBounds = new Rect();
143         /** The progress of the animation, from 0 to 1. */
144         @ViewDebug.ExportedProperty(category = "notification dot")
145         public float scale;
146         /** Whether the dot should align to the top left of the icon rather than the top right. */
147         @ViewDebug.ExportedProperty(category = "notification dot")
148         public boolean leftAlign;
149     }
150 }
151