• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.settings.graph;
16 
17 import android.annotation.Nullable;
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.graphics.Canvas;
21 import android.graphics.CornerPathEffect;
22 import android.graphics.DashPathEffect;
23 import android.graphics.LinearGradient;
24 import android.graphics.Paint;
25 import android.graphics.Paint.Cap;
26 import android.graphics.Paint.Join;
27 import android.graphics.Paint.Style;
28 import android.graphics.Path;
29 import android.graphics.Shader.TileMode;
30 import android.graphics.drawable.Drawable;
31 import android.support.annotation.VisibleForTesting;
32 import android.util.AttributeSet;
33 import android.util.SparseIntArray;
34 import android.util.TypedValue;
35 import android.view.View;
36 
37 import com.android.settings.fuelgauge.BatteryUtils;
38 import com.android.settingslib.R;
39 
40 public class UsageGraph extends View {
41 
42     private static final int PATH_DELIM = -1;
43     public static final String LOG_TAG = "UsageGraph";
44 
45     private final Paint mLinePaint;
46     private final Paint mFillPaint;
47     private final Paint mDottedPaint;
48 
49     private final Drawable mDivider;
50     private final Drawable mTintedDivider;
51     private final int mDividerSize;
52 
53     private final Path mPath = new Path();
54 
55     // Paths in coordinates they are passed in.
56     private final SparseIntArray mPaths = new SparseIntArray();
57     // Paths in local coordinates for drawing.
58     private final SparseIntArray mLocalPaths = new SparseIntArray();
59 
60     // Paths for projection in coordinates they are passed in.
61     private final SparseIntArray mProjectedPaths = new SparseIntArray();
62     // Paths for projection in local coordinates for drawing.
63     private final SparseIntArray mLocalProjectedPaths = new SparseIntArray();
64 
65     private final int mCornerRadius;
66     private int mAccentColor;
67 
68     private float mMaxX = 100;
69     private float mMaxY = 100;
70 
71     private float mMiddleDividerLoc = .5f;
72     private int mMiddleDividerTint = -1;
73     private int mTopDividerTint = -1;
74 
UsageGraph(Context context, @Nullable AttributeSet attrs)75     public UsageGraph(Context context, @Nullable AttributeSet attrs) {
76         super(context, attrs);
77         final Resources resources = context.getResources();
78 
79         mLinePaint = new Paint();
80         mLinePaint.setStyle(Style.STROKE);
81         mLinePaint.setStrokeCap(Cap.ROUND);
82         mLinePaint.setStrokeJoin(Join.ROUND);
83         mLinePaint.setAntiAlias(true);
84         mCornerRadius = resources.getDimensionPixelSize(R.dimen.usage_graph_line_corner_radius);
85         mLinePaint.setPathEffect(new CornerPathEffect(mCornerRadius));
86         mLinePaint.setStrokeWidth(resources.getDimensionPixelSize(R.dimen.usage_graph_line_width));
87 
88         mFillPaint = new Paint(mLinePaint);
89         mFillPaint.setStyle(Style.FILL);
90 
91         mDottedPaint = new Paint(mLinePaint);
92         mDottedPaint.setStyle(Style.STROKE);
93         float dots = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_size);
94         float interval = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_interval);
95         mDottedPaint.setStrokeWidth(dots * 3);
96         mDottedPaint.setPathEffect(new DashPathEffect(new float[] {dots, interval}, 0));
97         mDottedPaint.setColor(context.getColor(R.color.usage_graph_dots));
98 
99         TypedValue v = new TypedValue();
100         context.getTheme().resolveAttribute(com.android.internal.R.attr.listDivider, v, true);
101         mDivider = context.getDrawable(v.resourceId);
102         mTintedDivider = context.getDrawable(v.resourceId);
103         mDividerSize = resources.getDimensionPixelSize(R.dimen.usage_graph_divider_size);
104     }
105 
clearPaths()106     void clearPaths() {
107         mPaths.clear();
108         mLocalPaths.clear();
109         mProjectedPaths.clear();
110         mLocalProjectedPaths.clear();
111     }
112 
setMax(int maxX, int maxY)113     void setMax(int maxX, int maxY) {
114         final long startTime = System.currentTimeMillis();
115         mMaxX = maxX;
116         mMaxY = maxY;
117         calculateLocalPaths();
118         postInvalidate();
119         BatteryUtils.logRuntime(LOG_TAG, "setMax", startTime);
120     }
121 
setDividerLoc(int height)122     void setDividerLoc(int height) {
123         mMiddleDividerLoc = 1 - height / mMaxY;
124     }
125 
setDividerColors(int middleColor, int topColor)126     void setDividerColors(int middleColor, int topColor) {
127         mMiddleDividerTint = middleColor;
128         mTopDividerTint = topColor;
129     }
130 
addPath(SparseIntArray points)131     public void addPath(SparseIntArray points) {
132         addPathAndUpdate(points, mPaths, mLocalPaths);
133     }
134 
addProjectedPath(SparseIntArray points)135     public void addProjectedPath(SparseIntArray points) {
136         addPathAndUpdate(points, mProjectedPaths, mLocalProjectedPaths);
137     }
138 
addPathAndUpdate( SparseIntArray points, SparseIntArray paths, SparseIntArray localPaths)139     private void addPathAndUpdate(
140             SparseIntArray points, SparseIntArray paths, SparseIntArray localPaths) {
141         final long startTime = System.currentTimeMillis();
142         for (int i = 0, size = points.size(); i < size; i++) {
143             paths.put(points.keyAt(i), points.valueAt(i));
144         }
145         // Add a delimiting value immediately after the last point.
146         paths.put(points.keyAt(points.size() - 1) + 1, PATH_DELIM);
147         calculateLocalPaths(paths, localPaths);
148         postInvalidate();
149         BatteryUtils.logRuntime(LOG_TAG, "addPathAndUpdate", startTime);
150     }
151 
setAccentColor(int color)152     void setAccentColor(int color) {
153         mAccentColor = color;
154         mLinePaint.setColor(mAccentColor);
155         updateGradient();
156         postInvalidate();
157     }
158 
159     @Override
onSizeChanged(int w, int h, int oldw, int oldh)160     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
161         final long startTime = System.currentTimeMillis();
162         super.onSizeChanged(w, h, oldw, oldh);
163         updateGradient();
164         calculateLocalPaths();
165         BatteryUtils.logRuntime(LOG_TAG, "onSizeChanged", startTime);
166     }
167 
calculateLocalPaths()168     private void calculateLocalPaths() {
169         calculateLocalPaths(mPaths, mLocalPaths);
170         calculateLocalPaths(mProjectedPaths, mLocalProjectedPaths);
171     }
172 
173     @VisibleForTesting
calculateLocalPaths(SparseIntArray paths, SparseIntArray localPaths)174     void calculateLocalPaths(SparseIntArray paths, SparseIntArray localPaths) {
175         final long startTime = System.currentTimeMillis();
176         if (getWidth() == 0) {
177             return;
178         }
179         localPaths.clear();
180         // Store the local coordinates of the most recent point.
181         int lx = 0;
182         int ly = PATH_DELIM;
183         boolean skippedLastPoint = false;
184         for (int i = 0; i < paths.size(); i++) {
185             int x = paths.keyAt(i);
186             int y = paths.valueAt(i);
187             if (y == PATH_DELIM) {
188                 if (i == 1) {
189                     localPaths.put(getX(x+1) - 1, getY(0));
190                     continue;
191                 }
192                 if (i == paths.size() - 1 && skippedLastPoint) {
193                     // Add back skipped point to complete the path.
194                     localPaths.put(lx, ly);
195                 }
196                 skippedLastPoint = false;
197                 localPaths.put(lx + 1, PATH_DELIM);
198             } else {
199                 lx = getX(x);
200                 ly = getY(y);
201                 // Skip this point if it is not far enough from the last one added.
202                 if (localPaths.size() > 0) {
203                     int lastX = localPaths.keyAt(localPaths.size() - 1);
204                     int lastY = localPaths.valueAt(localPaths.size() - 1);
205                     if (lastY != PATH_DELIM && !hasDiff(lastX, lx) && !hasDiff(lastY, ly)) {
206                         skippedLastPoint = true;
207                         continue;
208                     }
209                 }
210                 skippedLastPoint = false;
211                 localPaths.put(lx, ly);
212             }
213         }
214         BatteryUtils.logRuntime(LOG_TAG, "calculateLocalPaths", startTime);
215     }
216 
hasDiff(int x1, int x2)217     private boolean hasDiff(int x1, int x2) {
218         return Math.abs(x2 - x1) >= mCornerRadius;
219     }
220 
getX(float x)221     private int getX(float x) {
222         return (int) (x / mMaxX * getWidth());
223     }
224 
getY(float y)225     private int getY(float y) {
226         return (int) (getHeight() * (1 - (y / mMaxY)));
227     }
228 
updateGradient()229     private void updateGradient() {
230         mFillPaint.setShader(
231                 new LinearGradient(
232                         0, 0, 0, getHeight(), getColor(mAccentColor, .2f), 0, TileMode.CLAMP));
233     }
234 
getColor(int color, float alphaScale)235     private int getColor(int color, float alphaScale) {
236         return (color & (((int) (0xff * alphaScale) << 24) | 0xffffff));
237     }
238 
239     @Override
onDraw(Canvas canvas)240     protected void onDraw(Canvas canvas) {
241         final long startTime = System.currentTimeMillis();
242         // Draw lines across the top, middle, and bottom.
243         if (mMiddleDividerLoc != 0) {
244             drawDivider(0, canvas, mTopDividerTint);
245         }
246         drawDivider(
247                 (int) ((canvas.getHeight() - mDividerSize) * mMiddleDividerLoc),
248                 canvas,
249                 mMiddleDividerTint);
250         drawDivider(canvas.getHeight() - mDividerSize, canvas, -1);
251 
252         if (mLocalPaths.size() == 0 && mLocalProjectedPaths.size() == 0) {
253             return;
254         }
255 
256         drawLinePath(canvas, mLocalProjectedPaths, mDottedPaint);
257         drawFilledPath(canvas, mLocalPaths, mFillPaint);
258         drawLinePath(canvas, mLocalPaths, mLinePaint);
259         BatteryUtils.logRuntime(LOG_TAG, "onDraw", startTime);
260     }
261 
drawLinePath(Canvas canvas, SparseIntArray localPaths, Paint paint)262     private void drawLinePath(Canvas canvas, SparseIntArray localPaths, Paint paint) {
263         if (localPaths.size() == 0) {
264             return;
265         }
266         mPath.reset();
267         mPath.moveTo(localPaths.keyAt(0), localPaths.valueAt(0));
268         for (int i = 1; i < localPaths.size(); i++) {
269             int x = localPaths.keyAt(i);
270             int y = localPaths.valueAt(i);
271             if (y == PATH_DELIM) {
272                 if (++i < localPaths.size()) {
273                     mPath.moveTo(localPaths.keyAt(i), localPaths.valueAt(i));
274                 }
275             } else {
276                 mPath.lineTo(x, y);
277             }
278         }
279         canvas.drawPath(mPath, paint);
280     }
281 
drawFilledPath(Canvas canvas, SparseIntArray localPaths, Paint paint)282     private void drawFilledPath(Canvas canvas, SparseIntArray localPaths, Paint paint) {
283         mPath.reset();
284         float lastStartX = localPaths.keyAt(0);
285         mPath.moveTo(localPaths.keyAt(0), localPaths.valueAt(0));
286         for (int i = 1; i < localPaths.size(); i++) {
287             int x = localPaths.keyAt(i);
288             int y = localPaths.valueAt(i);
289             if (y == PATH_DELIM) {
290                 mPath.lineTo(localPaths.keyAt(i - 1), getHeight());
291                 mPath.lineTo(lastStartX, getHeight());
292                 mPath.close();
293                 if (++i < localPaths.size()) {
294                     lastStartX = localPaths.keyAt(i);
295                     mPath.moveTo(localPaths.keyAt(i), localPaths.valueAt(i));
296                 }
297             } else {
298                 mPath.lineTo(x, y);
299             }
300         }
301         canvas.drawPath(mPath, paint);
302     }
303 
drawDivider(int y, Canvas canvas, int tintColor)304     private void drawDivider(int y, Canvas canvas, int tintColor) {
305         Drawable d = mDivider;
306         if (tintColor != -1) {
307             mTintedDivider.setTint(tintColor);
308             d = mTintedDivider;
309         }
310         d.setBounds(0, y, canvas.getWidth(), y + mDividerSize);
311         d.draw(canvas);
312     }
313 }
314