diff options
author | Jeff Sharkey <jsharkey@android.com> | 2011-05-30 16:19:56 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2011-06-09 09:26:30 -0700 |
commit | ab2d8d3a38857b8c155e6c6393c5821f5a341aae (patch) | |
tree | fe7fecb67b8300c245bbbfc0434fd3f45774d03c /src/com/android/settings/widget | |
parent | 32232fd9f2751b8618111831749ace5c9df021e8 (diff) | |
download | packages_apps_settings-ab2d8d3a38857b8c155e6c6393c5821f5a341aae.zip packages_apps_settings-ab2d8d3a38857b8c155e6c6393c5821f5a341aae.tar.gz packages_apps_settings-ab2d8d3a38857b8c155e6c6393c5821f5a341aae.tar.bz2 |
Checkpoint of data usage UI, graphs and lists.
Chart of network usage over time, with draggable "sweep" bars for
inspection region and warning/limits. Talks with NetworkStatsService
for live data, and updates list of application usage as inspection
region changes.
Change-Id: I2a406e6776daf7d74143c07ec683c10fe711c277
Diffstat (limited to 'src/com/android/settings/widget')
-rw-r--r-- | src/com/android/settings/widget/ChartAxis.java | 34 | ||||
-rw-r--r-- | src/com/android/settings/widget/ChartGridView.java | 79 | ||||
-rw-r--r-- | src/com/android/settings/widget/ChartNetworkSeriesView.java | 183 | ||||
-rw-r--r-- | src/com/android/settings/widget/ChartSweepView.java | 165 | ||||
-rw-r--r-- | src/com/android/settings/widget/ChartView.java | 120 | ||||
-rw-r--r-- | src/com/android/settings/widget/InvertedChartAxis.java | 59 |
6 files changed, 640 insertions, 0 deletions
diff --git a/src/com/android/settings/widget/ChartAxis.java b/src/com/android/settings/widget/ChartAxis.java new file mode 100644 index 0000000..0b77ac6 --- /dev/null +++ b/src/com/android/settings/widget/ChartAxis.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * Axis along a {@link ChartView} that knows how to convert between raw point + * and screen coordinate systems. + */ +public interface ChartAxis { + + public void setSize(float size); + + public float convertToPoint(long value); + public long convertToValue(float point); + + public CharSequence getLabel(long value); + + public float[] getTickPoints(); + +} diff --git a/src/com/android/settings/widget/ChartGridView.java b/src/com/android/settings/widget/ChartGridView.java new file mode 100644 index 0000000..be71890 --- /dev/null +++ b/src/com/android/settings/widget/ChartGridView.java @@ -0,0 +1,79 @@ +/* + * 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.view.View; + +import com.google.common.base.Preconditions; + +/** + * Background of {@link ChartView} that renders grid lines as requested by + * {@link ChartAxis#getTickPoints()}. + */ +public class ChartGridView extends View { + + private final ChartAxis mHoriz; + private final ChartAxis mVert; + + private final Paint mPaintHoriz; + private final Paint mPaintVert; + + public ChartGridView(Context context, ChartAxis horiz, ChartAxis vert) { + super(context); + + mHoriz = Preconditions.checkNotNull(horiz, "missing horiz"); + mVert = Preconditions.checkNotNull(vert, "missing vert"); + + setWillNotDraw(false); + + // TODO: convert these colors to resources + mPaintHoriz = new Paint(); + mPaintHoriz.setColor(Color.parseColor("#667bb5")); + mPaintHoriz.setStrokeWidth(2.0f); + mPaintHoriz.setStyle(Style.STROKE); + mPaintHoriz.setAntiAlias(true); + + mPaintVert = new Paint(); + mPaintVert.setColor(Color.parseColor("#28262c")); + mPaintVert.setStrokeWidth(1.0f); + mPaintVert.setStyle(Style.STROKE); + mPaintVert.setAntiAlias(true); + } + + @Override + protected void onDraw(Canvas canvas) { + final int width = getWidth(); + final int height = getHeight(); + + final float[] vertTicks = mVert.getTickPoints(); + for (float y : vertTicks) { + canvas.drawLine(0, y, width, y, mPaintVert); + } + + final float[] horizTicks = mHoriz.getTickPoints(); + for (float x : horizTicks) { + canvas.drawLine(x, 0, x, height, mPaintHoriz); + } + + canvas.drawRect(0, 0, width, height, mPaintHoriz); + } +} diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java new file mode 100644 index 0000000..1008761 --- /dev/null +++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java @@ -0,0 +1,183 @@ +/* + * 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 = false; + + private final ChartAxis mHoriz; + private final ChartAxis mVert; + + private final Paint mPaintStroke; + private final Paint mPaintFill; + private final 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"); + + mPaintStroke = new Paint(); + mPaintStroke.setStrokeWidth(6.0f); + mPaintStroke.setColor(Color.parseColor("#24aae1")); + mPaintStroke.setStyle(Style.STROKE); + mPaintStroke.setAntiAlias(true); + + mPaintFill = new Paint(); + mPaintFill.setColor(Color.parseColor("#c050ade5")); + mPaintFill.setStyle(Style.FILL); + mPaintFill.setAntiAlias(true); + + mPaintFillDisabled = new Paint(); + mPaintFillDisabled.setColor(Color.parseColor("#88566abc")); + mPaintFillDisabled.setStyle(Style.FILL); + mPaintFillDisabled.setAntiAlias(true); + + mPathStroke = new Path(); + mPathFill = new Path(); + } + + public void bindNetworkStats(NetworkStatsHistory stats) { + mStats = stats; + } + + 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. + */ + private void 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; + + 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); + + } +} diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java new file mode 100644 index 0000000..e3130ce --- /dev/null +++ b/src/com/android/settings/widget/ChartSweepView.java @@ -0,0 +1,165 @@ +/* + * 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.view.MotionEvent; +import android.view.View; + +import com.google.common.base.Preconditions; + +/** + * Sweep across a {@link ChartView} at a specific {@link ChartAxis} value, which + * a user can drag. + */ +public class ChartSweepView extends View { + + private final Paint mPaintSweep; + private final Paint mPaintShadow; + + private final ChartAxis mAxis; + private long mValue; + + public interface OnSweepListener { + public void onSweep(ChartSweepView sweep, boolean sweepDone); + } + + private OnSweepListener mListener; + + private boolean mHorizontal; + private MotionEvent mTracking; + + public ChartSweepView(Context context, ChartAxis axis, long value, int color) { + super(context); + + mAxis = Preconditions.checkNotNull(axis, "missing axis"); + mValue = value; + + mPaintSweep = new Paint(); + mPaintSweep.setColor(color); + mPaintSweep.setStrokeWidth(3.0f); + mPaintSweep.setStyle(Style.FILL_AND_STROKE); + mPaintSweep.setAntiAlias(true); + + mPaintShadow = new Paint(); + mPaintShadow.setColor(Color.BLACK); + mPaintShadow.setStrokeWidth(6.0f); + mPaintShadow.setStyle(Style.FILL_AND_STROKE); + mPaintShadow.setAntiAlias(true); + + } + + public void addOnSweepListener(OnSweepListener listener) { + mListener = listener; + } + + private void dispatchOnSweep(boolean sweepDone) { + if (mListener != null) { + mListener.onSweep(this, sweepDone); + } + } + + public ChartAxis getAxis() { + return mAxis; + } + + public long getValue() { + return mValue; + } + + public float getPoint() { + return mAxis.convertToPoint(mValue); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final View parent = (View) getParent(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + mTracking = event.copy(); + return true; + } + case MotionEvent.ACTION_MOVE: { + if (mHorizontal) { + setTranslationY(event.getRawY() - mTracking.getRawY()); + final float point = (getTop() + getTranslationY() + (getHeight() / 2)) + - parent.getPaddingTop(); + mValue = mAxis.convertToValue(point); + dispatchOnSweep(false); + } else { + setTranslationX(event.getRawX() - mTracking.getRawX()); + final float point = (getLeft() + getTranslationX() + (getWidth() / 2)) + - parent.getPaddingLeft(); + mValue = mAxis.convertToValue(point); + dispatchOnSweep(false); + } + return true; + } + case MotionEvent.ACTION_UP: { + mTracking = null; + setTranslationX(0); + setTranslationY(0); + requestLayout(); + dispatchOnSweep(true); + return true; + } + default: { + return false; + } + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // need at least 50px in each direction for grippies + // TODO: provide this value through params + setMeasuredDimension(50, 50); + } + + @Override + protected void onDraw(Canvas canvas) { + + // draw line across larger dimension + final int width = getWidth(); + final int height = getHeight(); + + mHorizontal = width > height; + + if (mHorizontal) { + final int centerY = height / 2; + final int endX = width - height; + + canvas.drawLine(0, centerY, endX, centerY, mPaintShadow); + canvas.drawLine(0, centerY, endX, centerY, mPaintSweep); + canvas.drawCircle(endX, centerY, 4.0f, mPaintShadow); + canvas.drawCircle(endX, centerY, 4.0f, mPaintSweep); + } else { + final int centerX = width / 2; + final int endY = height - width; + + canvas.drawLine(centerX, 0, centerX, endY, mPaintShadow); + canvas.drawLine(centerX, 0, centerX, endY, mPaintSweep); + canvas.drawCircle(centerX, endY, 4.0f, mPaintShadow); + canvas.drawCircle(centerX, endY, 4.0f, mPaintSweep); + } + } + +} diff --git a/src/com/android/settings/widget/ChartView.java b/src/com/android/settings/widget/ChartView.java new file mode 100644 index 0000000..bcb54f0 --- /dev/null +++ b/src/com/android/settings/widget/ChartView.java @@ -0,0 +1,120 @@ +/* + * 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 static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static com.google.common.base.Preconditions.checkNotNull; + +import android.content.Context; +import android.graphics.Rect; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; + +/** + * Container for two-dimensional chart, drawn with a combination of + * {@link ChartGridView}, {@link ChartNetworkSeriesView} and {@link ChartSweepView} + * children. The entire chart uses {@link ChartAxis} to map between raw values + * and screen coordinates. + */ +public class ChartView extends FrameLayout { + private static final String TAG = "ChartView"; + + // TODO: extend something that supports two-dimensional scrolling + + private final ChartAxis mHoriz; + private final ChartAxis mVert; + + private Rect mContent = new Rect(); + + public ChartView(Context context, ChartAxis horiz, ChartAxis vert) { + super(context); + + mHoriz = checkNotNull(horiz, "missing horiz"); + mVert = checkNotNull(vert, "missing vert"); + + setClipToPadding(false); + setClipChildren(false); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + mContent.set(l + getPaddingLeft(), t + getPaddingTop(), r - getPaddingRight(), + b - getPaddingBottom()); + final int width = mContent.width(); + final int height = mContent.height(); + + // no scrolling yet, so tell dimensions to fill exactly + mHoriz.setSize(width); + mVert.setSize(height); + + final Rect parentRect = new Rect(); + final Rect childRect = new Rect(); + + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + final LayoutParams params = (LayoutParams) child.getLayoutParams(); + + parentRect.set(mContent); + + if (child instanceof ChartNetworkSeriesView || child instanceof ChartGridView) { + // series are always laid out to fill entire graph area + // TODO: handle scrolling for series larger than content area + Gravity.apply(params.gravity, width, height, parentRect, childRect); + child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); + + } else if (child instanceof ChartSweepView) { + // sweep is always placed along specific dimension + final ChartSweepView sweep = (ChartSweepView) child; + final ChartAxis axis = sweep.getAxis(); + final float point = sweep.getPoint(); + + if (axis == mHoriz) { + parentRect.left = parentRect.right = (int) point + getPaddingLeft(); + parentRect.bottom += child.getMeasuredWidth(); + Gravity.apply(params.gravity, child.getMeasuredWidth(), parentRect.height(), + parentRect, childRect); + + } else if (axis == mVert) { + parentRect.top = parentRect.bottom = (int) point + getPaddingTop(); + parentRect.right += child.getMeasuredHeight(); + Gravity.apply(params.gravity, parentRect.width(), child.getMeasuredHeight(), + parentRect, childRect); + + } else { + throw new IllegalStateException("unexpected axis"); + } + } + + child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); + } + } + + public static LayoutParams buildChartParams() { + final LayoutParams params = new LayoutParams(MATCH_PARENT, MATCH_PARENT); + params.gravity = Gravity.LEFT | Gravity.BOTTOM; + return params; + } + + public static LayoutParams buildSweepParams() { + final LayoutParams params = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT); + params.gravity = Gravity.CENTER; + return params; + } + +} diff --git a/src/com/android/settings/widget/InvertedChartAxis.java b/src/com/android/settings/widget/InvertedChartAxis.java new file mode 100644 index 0000000..2bda320 --- /dev/null +++ b/src/com/android/settings/widget/InvertedChartAxis.java @@ -0,0 +1,59 @@ +/* + * 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; + +/** + * Utility to invert another {@link ChartAxis}. + */ +public class InvertedChartAxis implements ChartAxis { + private final ChartAxis mWrapped; + private float mSize; + + public InvertedChartAxis(ChartAxis wrapped) { + mWrapped = wrapped; + } + + /** {@inheritDoc} */ + public void setSize(float size) { + mSize = size; + mWrapped.setSize(size); + } + + /** {@inheritDoc} */ + public float convertToPoint(long value) { + return mSize - mWrapped.convertToPoint(value); + } + + /** {@inheritDoc} */ + public long convertToValue(float point) { + return mWrapped.convertToValue(mSize - point); + } + + /** {@inheritDoc} */ + public CharSequence getLabel(long value) { + return mWrapped.getLabel(value); + } + + /** {@inheritDoc} */ + public float[] getTickPoints() { + final float[] points = mWrapped.getTickPoints(); + for (int i = 0; i < points.length; i++) { + points[i] = mSize - points[i]; + } + return points; + } +} |