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