Files
app_Settings/src/com/android/settings/widget/ChartNetworkSeriesView.java
Jeff Sharkey 4dfa66001d First pass at detailed app data usage, policy.
Fragment to show application data usage details, including chart with
inspection ranges.  Button that invokes ACTION_MANAGE_NETWORK_USAGE
towards application, and UID-specific policy controls.  Fragment is
launched when clicking list items from data usage summary page.

Change-Id: Ie1564aa8af98e1a7083817a997059a5a7b1caa50
2011-06-13 00:42:22 -07:00

197 lines
6.0 KiB
Java

/*
* Copyright (C) 2011 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 com.android.settings.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.RectF;
import android.net.NetworkStatsHistory;
import android.util.Log;
import android.view.View;
import com.google.common.base.Preconditions;
/**
* {@link NetworkStatsHistory} series to render inside a {@link ChartView},
* using {@link ChartAxis} to map into screen coordinates.
*/
public class ChartNetworkSeriesView extends View {
private static final String TAG = "ChartNetworkSeriesView";
private static final boolean LOGD = true;
private final ChartAxis mHoriz;
private final ChartAxis mVert;
private Paint mPaintStroke;
private Paint mPaintFill;
private Paint mPaintFillDisabled;
private NetworkStatsHistory mStats;
private Path mPathStroke;
private Path mPathFill;
private ChartSweepView mSweep1;
private ChartSweepView mSweep2;
public ChartNetworkSeriesView(Context context, ChartAxis horiz, ChartAxis vert) {
super(context);
mHoriz = Preconditions.checkNotNull(horiz, "missing horiz");
mVert = Preconditions.checkNotNull(vert, "missing vert");
setChartColor(Color.parseColor("#24aae1"), Color.parseColor("#c050ade5"),
Color.parseColor("#88566abc"));
mPathStroke = new Path();
mPathFill = new Path();
}
public void setChartColor(int stroke, int fill, int disabled) {
mPaintStroke = new Paint();
mPaintStroke.setStrokeWidth(6.0f);
mPaintStroke.setColor(stroke);
mPaintStroke.setStyle(Style.STROKE);
mPaintStroke.setAntiAlias(true);
mPaintFill = new Paint();
mPaintFill.setColor(fill);
mPaintFill.setStyle(Style.FILL);
mPaintFill.setAntiAlias(true);
mPaintFillDisabled = new Paint();
mPaintFillDisabled.setColor(disabled);
mPaintFillDisabled.setStyle(Style.FILL);
mPaintFillDisabled.setAntiAlias(true);
}
public void bindNetworkStats(NetworkStatsHistory stats) {
mStats = stats;
mPathStroke.reset();
mPathFill.reset();
}
public void bindSweepRange(ChartSweepView sweep1, ChartSweepView sweep2) {
// TODO: generalize to support vertical sweeps
// TODO: enforce that both sweeps are along same dimension
mSweep1 = Preconditions.checkNotNull(sweep1, "missing sweep1");
mSweep2 = Preconditions.checkNotNull(sweep2, "missing sweep2");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
generatePath();
}
/**
* Erase any existing {@link Path} and generate series outline based on
* currently bound {@link NetworkStatsHistory} data.
*/
public void generatePath() {
if (LOGD) Log.d(TAG, "generatePath()");
mPathStroke.reset();
mPathFill.reset();
// bail when not enough stats to render
if (mStats == null || mStats.bucketCount < 2) return;
final int width = getWidth();
final int height = getHeight();
boolean started = false;
float firstX = 0;
float lastX = 0;
float lastY = 0;
// TODO: count fractional data from first bucket crossing start;
// currently it only accepts first full bucket.
long totalData = 0;
for (int i = 0; i < mStats.bucketCount; i++) {
final float x = mHoriz.convertToPoint(mStats.bucketStart[i]);
final float y = mVert.convertToPoint(totalData);
// skip until we find first stats on screen
if (i > 0 && !started && x > 0) {
mPathStroke.moveTo(lastX, lastY);
mPathFill.moveTo(lastX, lastY);
started = true;
firstX = x;
}
if (started) {
mPathStroke.lineTo(x, y);
mPathFill.lineTo(x, y);
totalData += mStats.rx[i] + mStats.tx[i];
}
// skip if beyond view
if (x > width) break;
lastX = x;
lastY = y;
}
if (LOGD) {
final RectF bounds = new RectF();
mPathFill.computeBounds(bounds, true);
Log.d(TAG, "onLayout() rendered with bounds=" + bounds.toString());
}
// drop to bottom of graph from current location
mPathFill.lineTo(lastX, height);
mPathFill.lineTo(firstX, height);
}
@Override
protected void onDraw(Canvas canvas) {
// clip to sweep area
final float sweep1 = mSweep1.getPoint();
final float sweep2 = mSweep2.getPoint();
final float sweepLeft = Math.min(sweep1, sweep2);
final float sweepRight = Math.max(sweep1, sweep2);
int save;
save = canvas.save();
canvas.clipRect(0, 0, sweepLeft, getHeight());
canvas.drawPath(mPathFill, mPaintFillDisabled);
canvas.restoreToCount(save);
save = canvas.save();
canvas.clipRect(sweepRight, 0, getWidth(), getHeight());
canvas.drawPath(mPathFill, mPaintFillDisabled);
canvas.restoreToCount(save);
save = canvas.save();
canvas.clipRect(sweepLeft, 0, sweepRight, getHeight());
canvas.drawPath(mPathFill, mPaintFill);
canvas.drawPath(mPathStroke, mPaintStroke);
canvas.restoreToCount(save);
}
}