Moved app details back into single Fragment to support animations and template tabs. Show the network in background behind app details chart series to match designs. Clamping sweeps at axis boundaries. Bug: 4813014, 4598460, 4818029 Change-Id: I72c0b21ee1d595e4da31d293ae0dab9e801041f3
380 lines
12 KiB
Java
380 lines
12 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.net.NetworkPolicy;
|
|
import android.net.NetworkStatsHistory;
|
|
import android.text.format.DateUtils;
|
|
import android.util.AttributeSet;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.widget.ChartSweepView.OnSweepListener;
|
|
|
|
/**
|
|
* Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along
|
|
* with {@link ChartSweepView} for inspection ranges and warning/limits.
|
|
*/
|
|
public class DataUsageChartView extends ChartView {
|
|
|
|
private static final long KB_IN_BYTES = 1024;
|
|
private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
|
|
private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
|
|
|
|
// TODO: enforce that sweeps cant cross each other
|
|
|
|
private ChartGridView mGrid;
|
|
private ChartNetworkSeriesView mSeries;
|
|
private ChartNetworkSeriesView mDetailSeries;
|
|
|
|
private ChartSweepView mSweepLeft;
|
|
private ChartSweepView mSweepRight;
|
|
private ChartSweepView mSweepWarning;
|
|
private ChartSweepView mSweepLimit;
|
|
|
|
public interface DataUsageChartListener {
|
|
public void onInspectRangeChanged();
|
|
public void onWarningChanged();
|
|
public void onLimitChanged();
|
|
}
|
|
|
|
private DataUsageChartListener mListener;
|
|
|
|
public DataUsageChartView(Context context) {
|
|
this(context, null, 0);
|
|
}
|
|
|
|
public DataUsageChartView(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public DataUsageChartView(Context context, AttributeSet attrs, int defStyle) {
|
|
super(context, attrs, defStyle);
|
|
init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
|
|
}
|
|
|
|
@Override
|
|
protected void onFinishInflate() {
|
|
super.onFinishInflate();
|
|
|
|
mGrid = (ChartGridView) findViewById(R.id.grid);
|
|
mSeries = (ChartNetworkSeriesView) findViewById(R.id.series);
|
|
mDetailSeries = (ChartNetworkSeriesView) findViewById(R.id.detail_series);
|
|
mDetailSeries.setVisibility(View.GONE);
|
|
|
|
mSweepLeft = (ChartSweepView) findViewById(R.id.sweep_left);
|
|
mSweepRight = (ChartSweepView) findViewById(R.id.sweep_right);
|
|
mSweepLimit = (ChartSweepView) findViewById(R.id.sweep_limit);
|
|
mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
|
|
|
|
mSweepLeft.addOnSweepListener(mSweepListener);
|
|
mSweepRight.addOnSweepListener(mSweepListener);
|
|
mSweepWarning.addOnSweepListener(mWarningListener);
|
|
mSweepLimit.addOnSweepListener(mLimitListener);
|
|
|
|
// tell everyone about our axis
|
|
mGrid.init(mHoriz, mVert);
|
|
mSeries.init(mHoriz, mVert);
|
|
mDetailSeries.init(mHoriz, mVert);
|
|
mSweepLeft.init(mHoriz);
|
|
mSweepRight.init(mHoriz);
|
|
mSweepWarning.init(mVert);
|
|
mSweepLimit.init(mVert);
|
|
|
|
setActivated(false);
|
|
}
|
|
|
|
public void setListener(DataUsageChartListener listener) {
|
|
mListener = listener;
|
|
}
|
|
|
|
public void bindNetworkStats(NetworkStatsHistory stats) {
|
|
mSeries.bindNetworkStats(stats);
|
|
updatePrimaryRange();
|
|
requestLayout();
|
|
}
|
|
|
|
public void bindDetailNetworkStats(NetworkStatsHistory stats) {
|
|
mDetailSeries.bindNetworkStats(stats);
|
|
mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE);
|
|
updatePrimaryRange();
|
|
requestLayout();
|
|
}
|
|
|
|
public void bindNetworkPolicy(NetworkPolicy policy) {
|
|
if (policy == null) {
|
|
mSweepLimit.setVisibility(View.INVISIBLE);
|
|
mSweepWarning.setVisibility(View.INVISIBLE);
|
|
return;
|
|
}
|
|
|
|
if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
|
|
mSweepLimit.setVisibility(View.VISIBLE);
|
|
mSweepLimit.setValue(policy.limitBytes);
|
|
mSweepLimit.setEnabled(true);
|
|
} else {
|
|
// TODO: set limit default based on axis maximum
|
|
mSweepLimit.setVisibility(View.VISIBLE);
|
|
mSweepLimit.setValue(5 * GB_IN_BYTES);
|
|
mSweepLimit.setEnabled(false);
|
|
}
|
|
|
|
if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
|
|
mSweepWarning.setVisibility(View.VISIBLE);
|
|
mSweepWarning.setValue(policy.warningBytes);
|
|
} else {
|
|
mSweepWarning.setVisibility(View.INVISIBLE);
|
|
}
|
|
|
|
requestLayout();
|
|
}
|
|
|
|
private OnSweepListener mSweepListener = new OnSweepListener() {
|
|
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
|
updatePrimaryRange();
|
|
|
|
// update detail list only when done sweeping
|
|
if (sweepDone && mListener != null) {
|
|
mListener.onInspectRangeChanged();
|
|
}
|
|
}
|
|
};
|
|
|
|
private OnSweepListener mWarningListener = new OnSweepListener() {
|
|
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
|
if (sweepDone && mListener != null) {
|
|
mListener.onWarningChanged();
|
|
}
|
|
}
|
|
};
|
|
|
|
private OnSweepListener mLimitListener = new OnSweepListener() {
|
|
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
|
if (sweepDone && mListener != null) {
|
|
mListener.onLimitChanged();
|
|
}
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
if (isActivated()) return false;
|
|
switch (event.getAction()) {
|
|
case MotionEvent.ACTION_DOWN: {
|
|
return true;
|
|
}
|
|
case MotionEvent.ACTION_UP: {
|
|
setActivated(true);
|
|
return true;
|
|
}
|
|
default: {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return current inspection range (start and end time) based on internal
|
|
* {@link ChartSweepView} positions.
|
|
*/
|
|
public long[] getInspectRange() {
|
|
final long start = mSweepLeft.getValue();
|
|
final long end = mSweepRight.getValue();
|
|
return new long[] { start, end };
|
|
}
|
|
|
|
public long getWarningBytes() {
|
|
return mSweepWarning.getValue();
|
|
}
|
|
|
|
public long getLimitBytes() {
|
|
return mSweepLimit.getValue();
|
|
}
|
|
|
|
/**
|
|
* Set the exact time range that should be displayed, updating how
|
|
* {@link ChartNetworkSeriesView} paints. Moves inspection ranges to be the
|
|
* last "week" of available data, without triggering listener events.
|
|
*/
|
|
public void setVisibleRange(long start, long end, long dataBoundary) {
|
|
mHoriz.setBounds(start, end);
|
|
|
|
// default sweeps to last week of data
|
|
final long halfRange = (end + start) / 2;
|
|
final long sweepMax = Math.min(end, dataBoundary);
|
|
final long sweepMin = Math.max(start, (sweepMax - DateUtils.WEEK_IN_MILLIS));
|
|
|
|
mSweepLeft.setValue(sweepMin);
|
|
mSweepRight.setValue(sweepMax);
|
|
updatePrimaryRange();
|
|
|
|
requestLayout();
|
|
mSeries.generatePath();
|
|
mSeries.invalidate();
|
|
}
|
|
|
|
private void updatePrimaryRange() {
|
|
final long left = mSweepLeft.getValue();
|
|
final long right = mSweepRight.getValue();
|
|
|
|
// prefer showing primary range on detail series, when available
|
|
if (mDetailSeries.getVisibility() == View.VISIBLE) {
|
|
mDetailSeries.setPrimaryRange(left, right);
|
|
mSeries.setPrimaryRange(0, 0);
|
|
} else {
|
|
mSeries.setPrimaryRange(left, right);
|
|
}
|
|
}
|
|
|
|
public static class TimeAxis implements ChartAxis {
|
|
private static final long TICK_INTERVAL = DateUtils.DAY_IN_MILLIS * 7;
|
|
|
|
private long mMin;
|
|
private long mMax;
|
|
private float mSize;
|
|
|
|
public TimeAxis() {
|
|
final long currentTime = System.currentTimeMillis();
|
|
setBounds(currentTime - DateUtils.DAY_IN_MILLIS * 30, currentTime);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void setBounds(long min, long max) {
|
|
mMin = min;
|
|
mMax = max;
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void setSize(float size) {
|
|
this.mSize = size;
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public float convertToPoint(long value) {
|
|
return (mSize * (value - mMin)) / (mMax - mMin);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public long convertToValue(float point) {
|
|
return (long) (mMin + ((point * (mMax - mMin)) / mSize));
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public CharSequence getLabel(long value) {
|
|
// TODO: convert to string
|
|
return Long.toString(value);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public CharSequence getShortLabel(long value) {
|
|
// TODO: convert to string
|
|
return Long.toString(value);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public float[] getTickPoints() {
|
|
// tick mark for every week
|
|
final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL);
|
|
final float[] tickPoints = new float[tickCount];
|
|
for (int i = 0; i < tickCount; i++) {
|
|
tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * i));
|
|
}
|
|
return tickPoints;
|
|
}
|
|
}
|
|
|
|
public static class DataAxis implements ChartAxis {
|
|
private long mMin;
|
|
private long mMax;
|
|
private long mMinLog;
|
|
private long mMaxLog;
|
|
private float mSize;
|
|
|
|
public DataAxis() {
|
|
// TODO: adapt ranges to show when history >5GB, and handle 4G
|
|
// interfaces with higher limits.
|
|
setBounds(1 * MB_IN_BYTES, 5 * GB_IN_BYTES);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void setBounds(long min, long max) {
|
|
mMin = min;
|
|
mMax = max;
|
|
mMinLog = (long) Math.log(mMin);
|
|
mMaxLog = (long) Math.log(mMax);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void setSize(float size) {
|
|
this.mSize = size;
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public float convertToPoint(long value) {
|
|
return (mSize * (value - mMin)) / (mMax - mMin);
|
|
|
|
// TODO: finish tweaking log scale
|
|
// if (value > mMin) {
|
|
// return (float) ((mSize * (Math.log(value) - mMinLog)) / (mMaxLog - mMinLog));
|
|
// } else {
|
|
// return 0;
|
|
// }
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public long convertToValue(float point) {
|
|
return (long) (mMin + ((point * (mMax - mMin)) / mSize));
|
|
|
|
// TODO: finish tweaking log scale
|
|
// return (long) Math.pow(Math.E, (mMinLog + ((point * (mMaxLog - mMinLog)) / mSize)));
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public CharSequence getLabel(long value) {
|
|
|
|
// TODO: use exploded string here
|
|
|
|
|
|
// TODO: convert to string
|
|
return Long.toString(value);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public CharSequence getShortLabel(long value) {
|
|
// TODO: convert to string
|
|
return Long.toString(value);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public float[] getTickPoints() {
|
|
final float[] tickPoints = new float[16];
|
|
|
|
long value = mMax;
|
|
float mult = 0.8f;
|
|
for (int i = 0; i < tickPoints.length; i++) {
|
|
tickPoints[i] = convertToPoint(value);
|
|
value = (long) (value * mult);
|
|
mult *= 0.9;
|
|
}
|
|
return tickPoints;
|
|
}
|
|
}
|
|
|
|
}
|