Implement general projection curve support for UsageGraph.
Previously, projections were hard-coded in UsageGraph as lines from the last known point to the corner of the graph. This change replaces that with support for arbitrary projection curves. Logic for hiding/showing the projection is now gone; if the client does not want a projection, it simply does not supply one. There are two active clients of this code: the data usage graph and the battery usage graph. The data graph does not use projections and is essentially unchanged. The battery graph now implements its linear extrapolation directly in BatteryInfo. Bug: 38400320 Test: make SettingsUnitTests SettingsGoogleUnitTests Test: manual (screenshots in comments) Change-Id: I754e66f6b18ecb8b936143399f8e9e3368fc1ce4
This commit is contained in:
@@ -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),
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -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));
|
|
||||||
}
|
}
|
||||||
mPaths.put(points.keyAt(points.size() - 1) + 1, PATH_DELIM);
|
|
||||||
calculateLocalPaths();
|
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));
|
||||||
|
}
|
||||||
|
// Add a delimiting value immediately after the last point.
|
||||||
|
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);
|
||||||
drawFilledPath(canvas);
|
|
||||||
drawLinePath(canvas);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawProjection(Canvas canvas) {
|
private void drawLinePath(Canvas canvas, SparseIntArray localPaths, Paint paint) {
|
||||||
mPath.reset();
|
if (localPaths.size() == 0) {
|
||||||
int x = mLocalPaths.keyAt(mLocalPaths.size() - 2);
|
return;
|
||||||
int y = mLocalPaths.valueAt(mLocalPaths.size() - 2);
|
|
||||||
mPath.moveTo(x, y);
|
|
||||||
mPath.lineTo(canvas.getWidth(), mProjectUp ? 0 : canvas.getHeight());
|
|
||||||
canvas.drawPath(mPath, mDottedPaint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawLinePath(Canvas canvas) {
|
|
||||||
mPath.reset();
|
mPath.reset();
|
||||||
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) {
|
||||||
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) {
|
||||||
|
@@ -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) {
|
||||||
|
Reference in New Issue
Block a user