Chromium Code Reviews| Index: third_party/android_misc/java/src/org/chromium/third_party/android/datausagechart/ChartNetworkSeriesView.java |
| diff --git a/third_party/android_misc/java/src/org/chromium/third_party/android/datausagechart/ChartNetworkSeriesView.java b/third_party/android_misc/java/src/org/chromium/third_party/android/datausagechart/ChartNetworkSeriesView.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5192455344aaf8ed69002e54206d06933b04cb7d |
| --- /dev/null |
| +++ b/third_party/android_misc/java/src/org/chromium/third_party/android/datausagechart/ChartNetworkSeriesView.java |
| @@ -0,0 +1,314 @@ |
| +/* |
| + * Copyright (C) 2013 The Android Open Source Project |
| + * |
| + * Licensed under the Apache License, Version 2.0 (the "License"); |
| + * you may not use this file except in compliance with the License. |
| + * You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.chromium.third_party.android.datausagechart; |
| + |
| +import android.content.Context; |
| +import android.content.res.TypedArray; |
| +import android.graphics.Canvas; |
| +import android.graphics.Color; |
| +import android.graphics.DashPathEffect; |
| +import android.graphics.Paint; |
| +import android.graphics.Paint.Style; |
| +import android.graphics.Path; |
| +import android.graphics.RectF; |
| +import android.util.AttributeSet; |
| +import android.util.Log; |
| +import android.view.View; |
| + |
| +import org.chromium.third_party.android.R; |
| + |
| +/** |
| + * {@link NetworkStatsHistory} series to render inside a {@link ChartView}, |
| + * using {@link ChartAxis} to map into screen coordinates. |
| + * This is derived from com.android.settings.widget.ChartNetworkSeriesView. |
| + */ |
| +public class ChartNetworkSeriesView extends View { |
| + private static final String TAG = "ChartNetworkSeriesView"; |
| + private static final boolean LOGD = false; |
| + |
| + private ChartAxis mHoriz; |
| + private ChartAxis mVert; |
| + |
| + private Paint mPaintStroke; |
| + private Paint mPaintFill; |
| + private Paint mPaintFillSecondary; |
| + private Paint mPaintEstimate; |
| + |
| + private NetworkStatsHistory mStats; |
| + |
| + private Path mPathStroke; |
| + private Path mPathFill; |
| + private Path mPathEstimate; |
| + |
| + private long mStart; |
| + private long mEnd; |
| + |
| + private long mPrimaryLeft; |
| + private long mPrimaryRight; |
| + |
| + /** Series will be extended to reach this end time. */ |
| + private long mEndTime = Long.MIN_VALUE; |
| + |
| + private boolean mPathValid = false; |
| + private boolean mEstimateVisible = false; |
| + |
| + private long mMax; |
| + private long mMaxEstimate; |
| + |
| + public ChartNetworkSeriesView(Context context) { |
| + this(context, null, 0); |
| + } |
| + |
| + public ChartNetworkSeriesView(Context context, AttributeSet attrs) { |
| + this(context, attrs, 0); |
| + } |
| + |
| + public ChartNetworkSeriesView(Context context, AttributeSet attrs, int defStyle) { |
| + super(context, attrs, defStyle); |
| + |
| + final TypedArray a = context.obtainStyledAttributes( |
| + attrs, R.styleable.ChartNetworkSeriesView, defStyle, 0); |
| + |
| + final int stroke = a.getColor(R.styleable.ChartNetworkSeriesView_strokeColor, Color.RED); |
| + final int fill = a.getColor(R.styleable.ChartNetworkSeriesView_fillColor, Color.RED); |
| + final int fillSecondary = a.getColor( |
| + R.styleable.ChartNetworkSeriesView_fillColorSecondary, Color.RED); |
| + |
| + setChartColor(stroke, fill, fillSecondary); |
| + setWillNotDraw(false); |
| + |
| + a.recycle(); |
| + |
| + mPathStroke = new Path(); |
| + mPathFill = new Path(); |
| + mPathEstimate = new Path(); |
| + } |
| + |
| + void init(ChartAxis horiz, ChartAxis vert) { |
| + if (horiz == null) throw new NullPointerException("missing horiz"); |
| + if (vert == null) throw new NullPointerException("missing vert"); |
| + mHoriz = horiz; |
| + mVert = vert; |
| + } |
| + |
| + public void setChartColor(int stroke, int fill, int fillSecondary) { |
| + mPaintStroke = new Paint(); |
| + mPaintStroke.setStrokeWidth(4.0f * getResources().getDisplayMetrics().density); |
| + mPaintStroke.setColor(stroke); |
| + mPaintStroke.setStyle(Style.STROKE); |
| + mPaintStroke.setAntiAlias(true); |
| + |
| + mPaintFill = new Paint(); |
| + mPaintFill.setColor(fill); |
| + mPaintFill.setStyle(Style.FILL); |
| + mPaintFill.setAntiAlias(true); |
| + |
| + mPaintFillSecondary = new Paint(); |
| + mPaintFillSecondary.setColor(fillSecondary); |
| + mPaintFillSecondary.setStyle(Style.FILL); |
| + mPaintFillSecondary.setAntiAlias(true); |
| + |
| + mPaintEstimate = new Paint(); |
| + mPaintEstimate.setStrokeWidth(3.0f); |
| + mPaintEstimate.setColor(fillSecondary); |
| + mPaintEstimate.setStyle(Style.STROKE); |
| + mPaintEstimate.setAntiAlias(true); |
| + mPaintEstimate.setPathEffect(new DashPathEffect(new float[] { 10, 10 }, 1)); |
| + } |
| + |
| + public void bindNetworkStats(NetworkStatsHistory stats) { |
| + mStats = stats; |
| + invalidatePath(); |
| + invalidate(); |
| + } |
| + |
| + public void setBounds(long start, long end) { |
| + mStart = start; |
| + mEnd = end; |
| + } |
| + |
| + /** |
| + * Set the range to paint with {@link #mPaintFill}, leaving the remaining |
|
bengr
2015/01/26 19:24:58
Sets
|
| + * area to be painted with {@link #mPaintFillSecondary}. |
| + */ |
| + public void setPrimaryRange(long left, long right) { |
| + mPrimaryLeft = left; |
| + mPrimaryRight = right; |
| + invalidate(); |
| + } |
| + |
| + public void invalidatePath() { |
| + mPathValid = false; |
| + mMax = 0; |
| + invalidate(); |
| + } |
| + |
| + /** |
| + * Erase any existing {@link Path} and generate series outline based on |
|
bengr
2015/01/26 19:24:58
Erases ... generates
|
| + * currently bound {@link NetworkStatsHistory} data. |
| + */ |
| + private void generatePath() { |
| + if (LOGD) Log.d(TAG, "generatePath()"); |
| + |
| + mMax = 0; |
| + mPathStroke.reset(); |
| + mPathFill.reset(); |
| + mPathEstimate.reset(); |
| + mPathValid = true; |
| + |
| + // bail when not enough stats to render |
| + if (mStats == null || mStats.size() < 2) { |
| + return; |
| + } |
| + |
| + final int height = getHeight(); |
| + |
| + boolean started = false; |
| + float lastX = 0; |
| + float lastY = height; |
| + long lastTime = mHoriz.convertToValue(lastX); |
| + |
| + // move into starting position |
| + mPathStroke.moveTo(lastX, lastY); |
| + mPathFill.moveTo(lastX, lastY); |
| + |
| + // TODO: count fractional data from first bucket crossing start; |
|
bengr
2015/01/26 19:24:58
// TODO(bengr): Count
newt (away)
2015/01/27 02:35:53
Done.
|
| + // currently it only accepts first full bucket. |
| + |
| + long totalData = 0; |
| + |
| + NetworkStatsHistory.Entry entry = null; |
| + |
| + final int start = mStats.getIndexBefore(mStart); |
| + final int end = mStats.getIndexAfter(mEnd); |
| + for (int i = start; i <= end; i++) { |
| + entry = mStats.getValues(i, entry); |
| + |
| + final long startTime = entry.bucketStart; |
| + final long endTime = startTime + entry.bucketDuration; |
| + |
| + final float startX = mHoriz.convertToPoint(startTime); |
| + final float endX = mHoriz.convertToPoint(endTime); |
| + |
| + // skip until we find first stats on screen |
|
bengr
2015/01/26 19:24:58
Skip until the first stats are found on screen.
|
| + if (endX < 0) continue; |
| + |
| + // increment by current bucket total |
|
bengr
2015/01/26 19:24:58
Imcrement .. total.
|
| + totalData += entry.rxBytes + entry.txBytes; |
| + |
| + final float startY = lastY; |
| + final float endY = mVert.convertToPoint(totalData); |
| + |
| + if (lastTime != startTime) { |
| + // gap in buckets; line to start of current bucket |
|
bengr
2015/01/26 19:24:58
Gap .. bucket.
|
| + mPathStroke.lineTo(startX, startY); |
| + mPathFill.lineTo(startX, startY); |
| + } |
| + |
| + // always draw to end of current bucket |
|
bengr
2015/01/26 19:24:58
Always .. bucket.
|
| + mPathStroke.lineTo(endX, endY); |
| + mPathFill.lineTo(endX, endY); |
| + |
| + lastX = endX; |
| + lastY = endY; |
| + lastTime = endTime; |
| + } |
| + |
| + // when data falls short, extend to requested end time |
|
bengr
2015/01/26 19:24:57
When .. time.
|
| + if (lastTime < mEndTime) { |
| + lastX = mHoriz.convertToPoint(mEndTime); |
| + |
| + mPathStroke.lineTo(lastX, lastY); |
| + mPathFill.lineTo(lastX, lastY); |
| + } |
| + |
| + if (LOGD) { |
| + final RectF bounds = new RectF(); |
| + mPathFill.computeBounds(bounds, true); |
| + Log.d(TAG, "onLayout() rendered with bounds=" + bounds.toString() + " and totalData=" |
| + + totalData); |
| + } |
| + |
| + // drop to bottom of graph from current location |
|
bengr
2015/01/26 19:24:58
Drop .. location.
|
| + mPathFill.lineTo(lastX, height); |
| + mPathFill.lineTo(0, height); |
| + |
| + mMax = totalData; |
| + |
| + invalidate(); |
| + } |
| + |
| + public void setEndTime(long endTime) { |
| + mEndTime = endTime; |
| + } |
| + |
| + public void setEstimateVisible(boolean estimateVisible) { |
| + mEstimateVisible = false; |
| + invalidate(); |
| + } |
| + |
| + public long getMaxEstimate() { |
| + return mMaxEstimate; |
| + } |
| + |
| + public long getMaxVisible() { |
| + final long maxVisible = mEstimateVisible ? mMaxEstimate : mMax; |
| + if (maxVisible <= 0 && mStats != null) { |
| + // haven't generated path yet; fall back to raw data |
| + final NetworkStatsHistory.Entry entry = mStats.getValues(mStart, mEnd, null); |
| + return entry.rxBytes + entry.txBytes; |
| + } else { |
| + return maxVisible; |
| + } |
| + } |
| + |
| + @Override |
| + protected void onDraw(Canvas canvas) { |
| + int save; |
| + |
| + if (!mPathValid) { |
| + generatePath(); |
| + } |
| + |
| + final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft); |
| + final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight); |
| + |
| + if (mEstimateVisible) { |
| + save = canvas.save(); |
| + canvas.clipRect(0, 0, getWidth(), getHeight()); |
| + canvas.drawPath(mPathEstimate, mPaintEstimate); |
| + canvas.restoreToCount(save); |
| + } |
| + |
| + save = canvas.save(); |
| + canvas.clipRect(0, 0, primaryLeftPoint, getHeight()); |
| + canvas.drawPath(mPathFill, mPaintFillSecondary); |
| + canvas.restoreToCount(save); |
| + |
| + save = canvas.save(); |
| + canvas.clipRect(primaryRightPoint, 0, getWidth(), getHeight()); |
| + canvas.drawPath(mPathFill, mPaintFillSecondary); |
| + canvas.restoreToCount(save); |
| + |
| + save = canvas.save(); |
| + canvas.clipRect(primaryLeftPoint, 0, primaryRightPoint, getHeight()); |
| + canvas.drawPath(mPathFill, mPaintFill); |
| + canvas.drawPath(mPathStroke, mPaintStroke); |
| + canvas.restoreToCount(save); |
| + |
| + } |
| +} |