Merge "Implement general projection curve support for UsageGraph." into oc-dr1-dev

This commit is contained in:
TreeHugger Robot
2017-06-08 23:23:00 +00:00
committed by Android (Google) Code Review
4 changed files with 94 additions and 74 deletions

View File

@@ -62,7 +62,7 @@ public class ChartDataUsagePreference extends Preference {
int top = getTop(); int top = getTop();
chart.clearPaths(); chart.clearPaths();
chart.configureGraph(toInt(mEnd - mStart), top, false, false); chart.configureGraph(toInt(mEnd - mStart), top);
calcPoints(chart); calcPoints(chart);
chart.setBottomLabels(new CharSequence[] { chart.setBottomLabels(new CharSequence[] {
Utils.formatDateRange(getContext(), mStart, mStart), Utils.formatDateRange(getContext(), mStart, mStart),

View File

@@ -55,18 +55,23 @@ public class BatteryInfo {
public void bindHistory(final UsageView view, BatteryDataParser... parsers) { public void bindHistory(final UsageView view, BatteryDataParser... parsers) {
BatteryDataParser parser = new BatteryDataParser() { BatteryDataParser parser = new BatteryDataParser() {
SparseIntArray points = new SparseIntArray(); SparseIntArray points = new SparseIntArray();
int lastTime = -1;
byte lastLevel;
int maxTime;
@Override @Override
public void onParsingStarted(long startTime, long endTime) { public void onParsingStarted(long startTime, long endTime) {
timePeriod = endTime - startTime - remainingTimeUs / 1000; this.maxTime = (int) (endTime - startTime);
timePeriod = maxTime - (remainingTimeUs / 1000);
view.clearPaths(); view.clearPaths();
view.configureGraph((int) (endTime - startTime), 100, remainingTimeUs != 0, view.configureGraph(maxTime, 100);
mCharging);
} }
@Override @Override
public void onDataPoint(long time, HistoryItem record) { public void onDataPoint(long time, HistoryItem record) {
points.put((int) time, record.batteryLevel); lastTime = (int) time;
lastLevel = record.batteryLevel;
points.put(lastTime, lastLevel);
} }
@Override @Override
@@ -79,8 +84,13 @@ public class BatteryInfo {
@Override @Override
public void onParsingDone() { public void onParsingDone() {
if (points.size() > 1) { onDataGap();
view.addPath(points);
// Add linear projection
if (lastTime >= 0 && remainingTimeUs != 0) {
points.put(lastTime, lastLevel);
points.put(maxTime, mCharging ? 100 : 0);
view.addProjectedPath(points);
} }
} }
}; };

View File

@@ -32,6 +32,7 @@ import android.util.AttributeSet;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
import com.android.settingslib.R; import com.android.settingslib.R;
public class UsageGraph extends View { public class UsageGraph extends View {
@@ -52,11 +53,14 @@ public class UsageGraph extends View {
private final SparseIntArray mPaths = new SparseIntArray(); private final SparseIntArray mPaths = new SparseIntArray();
// Paths in local coordinates for drawing. // Paths in local coordinates for drawing.
private final SparseIntArray mLocalPaths = new SparseIntArray(); private final SparseIntArray mLocalPaths = new SparseIntArray();
private final int mCornerRadius;
// Paths for projection in coordinates they are passed in.
private final SparseIntArray mProjectedPaths = new SparseIntArray();
// Paths for projection in local coordinates for drawing.
private final SparseIntArray mLocalProjectedPaths = new SparseIntArray();
private final int mCornerRadius;
private int mAccentColor; private int mAccentColor;
private boolean mShowProjection;
private boolean mProjectUp;
private float mMaxX = 100; private float mMaxX = 100;
private float mMaxY = 100; private float mMaxY = 100;
@@ -86,7 +90,7 @@ public class UsageGraph extends View {
float dots = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_size); float dots = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_size);
float interval = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_interval); float interval = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_interval);
mDottedPaint.setStrokeWidth(dots * 3); mDottedPaint.setStrokeWidth(dots * 3);
mDottedPaint.setPathEffect(new DashPathEffect(new float[] {dots, interval}, 0)); mDottedPaint.setPathEffect(new DashPathEffect(new float[]{dots, interval}, 0));
mDottedPaint.setColor(context.getColor(R.color.usage_graph_dots)); mDottedPaint.setColor(context.getColor(R.color.usage_graph_dots));
TypedValue v = new TypedValue(); TypedValue v = new TypedValue();
@@ -98,6 +102,9 @@ public class UsageGraph extends View {
void clearPaths() { void clearPaths() {
mPaths.clear(); mPaths.clear();
mLocalPaths.clear();
mProjectedPaths.clear();
mLocalProjectedPaths.clear();
} }
void setMax(int maxX, int maxY) { void setMax(int maxX, int maxY) {
@@ -115,11 +122,21 @@ public class UsageGraph extends View {
} }
public void addPath(SparseIntArray points) { public void addPath(SparseIntArray points) {
for (int i = 0; i < points.size(); i++) { addPathAndUpdate(points, mPaths, mLocalPaths);
mPaths.put(points.keyAt(i), points.valueAt(i)); }
public void addProjectedPath(SparseIntArray points) {
addPathAndUpdate(points, mProjectedPaths, mLocalProjectedPaths);
}
private void addPathAndUpdate(SparseIntArray points, SparseIntArray paths,
SparseIntArray localPaths) {
for (int i = 0, size = points.size(); i < size; i++) {
paths.put(points.keyAt(i), points.valueAt(i));
} }
mPaths.put(points.keyAt(points.size() - 1) + 1, PATH_DELIM); // Add a delimiting value immediately after the last point.
calculateLocalPaths(); paths.put(points.keyAt(points.size() - 1) + 1, PATH_DELIM);
calculateLocalPaths(paths, localPaths);
postInvalidate(); postInvalidate();
} }
@@ -130,48 +147,45 @@ public class UsageGraph extends View {
postInvalidate(); postInvalidate();
} }
void setShowProjection(boolean showProjection, boolean projectUp) {
mShowProjection = showProjection;
mProjectUp = projectUp;
postInvalidate();
}
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh); super.onSizeChanged(w, h, oldw, oldh);
updateGradient(); updateGradient();
calculateLocalPaths(); calculateLocalPaths(mPaths, mLocalPaths);
calculateLocalPaths(mProjectedPaths, mLocalProjectedPaths);
} }
private void calculateLocalPaths() { private void calculateLocalPaths(SparseIntArray paths, SparseIntArray localPaths) {
if (getWidth() == 0) return; if (getWidth() == 0) {
mLocalPaths.clear(); return;
}
localPaths.clear();
int pendingXLoc = 0; int pendingXLoc = 0;
int pendingYLoc = PATH_DELIM; int pendingYLoc = PATH_DELIM;
for (int i = 0; i < mPaths.size(); i++) { for (int i = 0; i < paths.size(); i++) {
int x = mPaths.keyAt(i); int x = paths.keyAt(i);
int y = mPaths.valueAt(i); int y = paths.valueAt(i);
if (y == PATH_DELIM) { if (y == PATH_DELIM) {
if (i == mPaths.size() - 1 && pendingYLoc != PATH_DELIM) { if (i == paths.size() - 1 && pendingYLoc != PATH_DELIM) {
// Connect to the end of the graph. // Connect to the end of the graph.
mLocalPaths.put(pendingXLoc, pendingYLoc); localPaths.put(pendingXLoc, pendingYLoc);
} }
// Clear out any pending points. // Clear out any pending points.
pendingYLoc = PATH_DELIM; pendingYLoc = PATH_DELIM;
mLocalPaths.put(pendingXLoc + 1, PATH_DELIM); localPaths.put(pendingXLoc + 1, PATH_DELIM);
} else { } else {
final int lx = getX(x); final int lx = getX(x);
final int ly = getY(y); final int ly = getY(y);
pendingXLoc = lx; pendingXLoc = lx;
if (mLocalPaths.size() > 0) { if (localPaths.size() > 0) {
int lastX = mLocalPaths.keyAt(mLocalPaths.size() - 1); int lastX = localPaths.keyAt(localPaths.size() - 1);
int lastY = mLocalPaths.valueAt(mLocalPaths.size() - 1); int lastY = localPaths.valueAt(localPaths.size() - 1);
if (lastY != PATH_DELIM && !hasDiff(lastX, lx) && !hasDiff(lastY, ly)) { if (lastY != PATH_DELIM && !hasDiff(lastX, lx) && !hasDiff(lastY, ly)) {
pendingYLoc = ly; pendingYLoc = ly;
continue; continue;
} }
} }
mLocalPaths.put(lx, ly); localPaths.put(lx, ly);
} }
} }
} }
@@ -189,8 +203,9 @@ public class UsageGraph extends View {
} }
private void updateGradient() { private void updateGradient() {
mFillPaint.setShader(new LinearGradient(0, 0, 0, getHeight(), mFillPaint.setShader(
getColor(mAccentColor, .2f), 0, TileMode.CLAMP)); new LinearGradient(0, 0, 0, getHeight(), getColor(mAccentColor, .2f), 0,
TileMode.CLAMP));
} }
private int getColor(int color, float alphaScale) { private int getColor(int color, float alphaScale) {
@@ -207,62 +222,54 @@ public class UsageGraph extends View {
mMiddleDividerTint); mMiddleDividerTint);
drawDivider(canvas.getHeight() - mDividerSize, canvas, -1); drawDivider(canvas.getHeight() - mDividerSize, canvas, -1);
if (mLocalPaths.size() == 0) { if (mLocalPaths.size() == 0 && mProjectedPaths.size() == 0) {
return; return;
} }
if (mShowProjection) { drawLinePath(canvas, mLocalProjectedPaths, mDottedPaint);
drawProjection(canvas); drawFilledPath(canvas, mLocalPaths, mFillPaint);
drawLinePath(canvas, mLocalPaths, mLinePaint);
}
private void drawLinePath(Canvas canvas, SparseIntArray localPaths, Paint paint) {
if (localPaths.size() == 0) {
return;
} }
drawFilledPath(canvas);
drawLinePath(canvas);
}
private void drawProjection(Canvas canvas) {
mPath.reset(); mPath.reset();
int x = mLocalPaths.keyAt(mLocalPaths.size() - 2); mPath.moveTo(localPaths.keyAt(0), localPaths.valueAt(0));
int y = mLocalPaths.valueAt(mLocalPaths.size() - 2); for (int i = 1; i < localPaths.size(); i++) {
mPath.moveTo(x, y); int x = localPaths.keyAt(i);
mPath.lineTo(canvas.getWidth(), mProjectUp ? 0 : canvas.getHeight()); int y = localPaths.valueAt(i);
canvas.drawPath(mPath, mDottedPaint);
}
private void drawLinePath(Canvas canvas) {
mPath.reset();
mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0));
for (int i = 1; i < mLocalPaths.size(); i++) {
int x = mLocalPaths.keyAt(i);
int y = mLocalPaths.valueAt(i);
if (y == PATH_DELIM) { if (y == PATH_DELIM) {
if (++i < mLocalPaths.size()) { if (++i < localPaths.size()) {
mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i)); mPath.moveTo(localPaths.keyAt(i), localPaths.valueAt(i));
} }
} else { } else {
mPath.lineTo(x, y); mPath.lineTo(x, y);
} }
} }
canvas.drawPath(mPath, mLinePaint); canvas.drawPath(mPath, paint);
} }
private void drawFilledPath(Canvas canvas) { private void drawFilledPath(Canvas canvas, SparseIntArray localPaths, Paint paint) {
mPath.reset(); mPath.reset();
float lastStartX = mLocalPaths.keyAt(0); float lastStartX = localPaths.keyAt(0);
mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0)); mPath.moveTo(localPaths.keyAt(0), localPaths.valueAt(0));
for (int i = 1; i < mLocalPaths.size(); i++) { for (int i = 1; i < localPaths.size(); i++) {
int x = mLocalPaths.keyAt(i); int x = localPaths.keyAt(i);
int y = mLocalPaths.valueAt(i); int y = localPaths.valueAt(i);
if (y == PATH_DELIM) { if (y == PATH_DELIM) {
mPath.lineTo(mLocalPaths.keyAt(i - 1), getHeight()); mPath.lineTo(localPaths.keyAt(i - 1), getHeight());
mPath.lineTo(lastStartX, getHeight()); mPath.lineTo(lastStartX, getHeight());
mPath.close(); mPath.close();
if (++i < mLocalPaths.size()) { if (++i < localPaths.size()) {
lastStartX = mLocalPaths.keyAt(i); lastStartX = localPaths.keyAt(i);
mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i)); mPath.moveTo(localPaths.keyAt(i), localPaths.valueAt(i));
} }
} else { } else {
mPath.lineTo(x, y); mPath.lineTo(x, y);
} }
} }
canvas.drawPath(mPath, mFillPaint); canvas.drawPath(mPath, paint);
} }
private void drawDivider(int y, Canvas canvas, int tintColor) { private void drawDivider(int y, Canvas canvas, int tintColor) {

View File

@@ -91,9 +91,12 @@ public class UsageView extends FrameLayout {
mUsageGraph.addPath(points); mUsageGraph.addPath(points);
} }
public void configureGraph(int maxX, int maxY, boolean showProjection, boolean projectUp) { public void addProjectedPath(SparseIntArray points) {
mUsageGraph.addProjectedPath(points);
}
public void configureGraph(int maxX, int maxY) {
mUsageGraph.setMax(maxX, maxY); mUsageGraph.setMax(maxX, maxY);
mUsageGraph.setShowProjection(showProjection, projectUp);
} }
public void setAccentColor(int color) { public void setAccentColor(int color) {