summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2011-08-01 15:29:30 -0700
committerJeff Sharkey <jsharkey@android.com>2011-08-01 16:36:38 -0700
commite2afc0f283f58ce60c107643978bfff25ec5d5c1 (patch)
tree4a5abc2ce9a3483e9ced7401c89b2ab7fbf32a9f
parenta861ebffe0dc6eacc83343f97044da4c0370c1b2 (diff)
downloadpackages_apps_settings-e2afc0f283f58ce60c107643978bfff25ec5d5c1.zip
packages_apps_settings-e2afc0f283f58ce60c107643978bfff25ec5d5c1.tar.gz
packages_apps_settings-e2afc0f283f58ce60c107643978bfff25ec5d5c1.tar.bz2
Data usage axis grow/shrink, other fixes.
When dragging vertical sweeps near edges, grow or shrink axis scale to give users access to larger limits. Triggers 10% for each 250ms that user continues holding. Change axis math to support arbitrary ranges beyond [0,5GB]. Show "empty" message when no application details found. Added strings that didn't appear in default language. Better sweep margins using dip instead of scale units. Format time ranges in local time instead of UTC. Only show dashed estimate when it would reach near warning or limit. Extend app usage series until "now" when buckets missing. Bug: 5096685, 5092538, 5058158, 5058114, 5058024, 4643457 Change-Id: I45cf33f7f3baeba1bfa5b21f31cb0a12006f62fa
-rw-r--r--res/layout/data_usage_chart.xml8
-rw-r--r--res/layout/data_usage_header.xml12
-rw-r--r--res/values/attrs.xml1
-rw-r--r--res/values/strings.xml8
-rw-r--r--src/com/android/settings/DataUsageSummary.java21
-rw-r--r--src/com/android/settings/widget/ChartAxis.java12
-rw-r--r--src/com/android/settings/widget/ChartNetworkSeriesView.java62
-rw-r--r--src/com/android/settings/widget/ChartSweepView.java91
-rw-r--r--src/com/android/settings/widget/DataUsageChartView.java211
-rw-r--r--src/com/android/settings/widget/InvertedChartAxis.java5
10 files changed, 343 insertions, 88 deletions
diff --git a/res/layout/data_usage_chart.xml b/res/layout/data_usage_chart.xml
index 3409d64..3ae8a03 100644
--- a/res/layout/data_usage_chart.xml
+++ b/res/layout/data_usage_chart.xml
@@ -56,6 +56,7 @@
android:layout_height="wrap_content"
settings:sweepDrawable="@drawable/data_sweep_warning"
settings:followAxis="vertical"
+ settings:neighborMargin="40dip"
settings:labelSize="60dip"
settings:labelTemplate="@string/data_usage_sweep_warning"
settings:labelColor="#f7931d" />
@@ -66,6 +67,7 @@
android:layout_height="wrap_content"
settings:sweepDrawable="@drawable/data_sweep_limit"
settings:followAxis="vertical"
+ settings:neighborMargin="40dip"
settings:labelSize="60dip"
settings:labelTemplate="@string/data_usage_sweep_limit"
settings:labelColor="#c01a2c" />
@@ -75,13 +77,15 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
settings:sweepDrawable="@drawable/data_sweep_left"
- settings:followAxis="horizontal" />
+ settings:followAxis="horizontal"
+ settings:neighborMargin="5dip" />
<com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_right"
android:layout_width="wrap_content"
android:layout_height="match_parent"
settings:sweepDrawable="@drawable/data_sweep_right"
- settings:followAxis="horizontal" />
+ settings:followAxis="horizontal"
+ settings:neighborMargin="5dip" />
</com.android.settings.widget.DataUsageChartView>
diff --git a/res/layout/data_usage_header.xml b/res/layout/data_usage_header.xml
index 52f56c1..9602898 100644
--- a/res/layout/data_usage_header.xml
+++ b/res/layout/data_usage_header.xml
@@ -45,6 +45,18 @@
android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceSmall" />
+ <TextView
+ android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:paddingLeft="16dip"
+ android:paddingRight="16dip"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip"
+ android:text="@string/data_usage_empty"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
<include layout="@layout/data_usage_detail" />
</LinearLayout>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index a0a6c77..6c63d13 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -56,6 +56,7 @@
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
+ <attr name="neighborMargin" format="dimension" />
<attr name="labelSize" format="dimension" />
<attr name="labelTemplate" format="reference" />
<attr name="labelColor" format="color" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e151640..2302dcb 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -230,6 +230,8 @@
<string name="bluetooth_disconnect_title">Disconnect?</string>
<!-- Bluetooth settings. Message for disconnecting from all profiles of a bluetooth device. [CHAR LIMIT=NONE] -->
<string name="bluetooth_disconnect_all_profiles">This will end your connection with:&lt;br>&lt;b><xliff:g id="device_name">%1$s</xliff:g>&lt;/b></string>
+ <!-- Bluetooth settings. Message for disconnecting from all profiles of a bluetooth device. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_disconnect_blank">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> will be disconnected."</string>
<!-- Bluetooth settings. Message when connected to a device -->
<string name="bluetooth_connected">Connected</string>
<!-- Bluetooth settings. Message when a device is disconnected -->
@@ -1788,8 +1790,10 @@
<string name="usb_mtp_summary">Lets you transfer media files on Windows, or using Android File Transfer on Mac (see www.android.com/filetransfer)</string>
<!-- Storage setting. Title for PTP checkbox [CHAR LIMIT=30]-->
<string name="usb_ptp_title">Camera (PTP)</string>
- <!-- Storage setting. Summary for PTP checkbox [CHAR LIMIT=NONE]-->
+ <!-- Storage setting. Label for installer CD [CHAR LIMIT=30]-->
<string name="usb_ptp_summary">Lets you transfer photos using camera software, and transfer any files on computers that don\'t support MTP</string>
+ <!-- Storage setting. Summary for PTP checkbox [CHAR LIMIT=NONE]-->
+ <string name="usb_label_installer_cd">"Install file-transfer tools"</string>
<!-- Phone info screen, section titles: -->
<string name="battery_status_title">Battery status</string>
@@ -3409,6 +3413,8 @@ found in the list of installed applications.</string>
<string name="data_usage_change_cycle">Change cycle\u2026</string>
<!-- Body of dialog prompting user to change numerical day of month that data usage cycle should reset. [CHAR LIMIT=64] -->
<string name="data_usage_pick_cycle_day">Day of month to reset data usage cycle:</string>
+ <!-- Label shown when no applications used data during selected time period. [CHAR LIMIT=48] -->
+ <string name="data_usage_empty">No applications used data during this period.</string>
<!-- Checkbox label that will disable mobile network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
<string name="data_usage_disable_mobile_limit">Disable mobile data at limit</string>
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index d87080f..8581421 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -182,6 +182,7 @@ public class DataUsageSummary extends Fragment {
private DataUsageChartView mChart;
private TextView mUsageSummary;
+ private TextView mEmpty;
private View mAppDetail;
private TextView mAppTitle;
@@ -305,6 +306,7 @@ public class DataUsageSummary extends Fragment {
}
mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary);
+ mEmpty = (TextView) mHeader.findViewById(android.R.id.empty);
// only assign layout transitions once first layout is finished
mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
@@ -986,7 +988,7 @@ public class DataUsageSummary extends Fragment {
final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
- final String rangePhrase = formatDateRangeUtc(context, start, end);
+ final String rangePhrase = formatDateRange(context, start, end, null);
mUsageSummary.setText(
getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
@@ -1002,11 +1004,18 @@ public class DataUsageSummary extends Fragment {
/** {@inheritDoc} */
public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
mAdapter.bindStats(data);
+ updateEmptyVisible();
}
/** {@inheritDoc} */
public void onLoaderReset(Loader<NetworkStats> loader) {
mAdapter.bindStats(null);
+ updateEmptyVisible();
+ }
+
+ private void updateEmptyVisible() {
+ final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode();
+ mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
}
};
@@ -1063,7 +1072,7 @@ public class DataUsageSummary extends Fragment {
}
public CycleItem(Context context, long start, long end) {
- this.label = formatDateRangeUtc(context, start, end);
+ this.label = formatDateRange(context, start, end, Time.TIMEZONE_UTC);
this.start = start;
this.end = end;
}
@@ -1078,7 +1087,7 @@ public class DataUsageSummary extends Fragment {
private static final java.util.Formatter sFormatter = new java.util.Formatter(
sBuilder, Locale.getDefault());
- private static String formatDateRangeUtc(Context context, long start, long end) {
+ private static String formatDateRange(Context context, long start, long end, String timezone) {
synchronized (sBuilder) {
int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH;
if (Time.getJulianDay(start, 0) == Time.getJulianDay(end, 0)) {
@@ -1087,8 +1096,8 @@ public class DataUsageSummary extends Fragment {
}
sBuilder.setLength(0);
- return DateUtils.formatDateRange(
- context, sFormatter, start, end, flags, Time.TIMEZONE_UTC).toString();
+ return DateUtils
+ .formatDateRange(context, sFormatter, start, end, flags, timezone).toString();
}
}
@@ -1197,7 +1206,7 @@ public class DataUsageSummary extends Fragment {
@Override
public long getItemId(int position) {
- return position;
+ return mItems.get(position).uid;
}
@Override
diff --git a/src/com/android/settings/widget/ChartAxis.java b/src/com/android/settings/widget/ChartAxis.java
index 463541f..4e0da1d 100644
--- a/src/com/android/settings/widget/ChartAxis.java
+++ b/src/com/android/settings/widget/ChartAxis.java
@@ -25,14 +25,26 @@ import android.text.SpannableStringBuilder;
*/
public interface ChartAxis {
+ /** Set range of raw values this axis should cover. */
public void setBounds(long min, long max);
+ /** Set range of screen points this axis should cover. */
public void setSize(float size);
+ /** Convert raw value into screen point. */
public float convertToPoint(long value);
+ /** Convert screen point into raw value. */
public long convertToValue(float point);
+ /** Build label that describes given raw value. */
public void buildLabel(Resources res, SpannableStringBuilder builder, long value);
+ /** Return list of tick points for drawing a grid. */
public float[] getTickPoints();
+ /**
+ * Test if given raw value should cause the axis to grow or shrink;
+ * returning positive value to grow and negative to shrink.
+ */
+ public int shouldAdjustAxis(long value);
+
}
diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java
index 51c3c2c..481f7cc 100644
--- a/src/com/android/settings/widget/ChartNetworkSeriesView.java
+++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java
@@ -23,6 +23,7 @@ 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;
@@ -41,7 +42,7 @@ import com.google.common.base.Preconditions;
*/
public class ChartNetworkSeriesView extends View {
private static final String TAG = "ChartNetworkSeriesView";
- private static final boolean LOGD = true;
+ private static final boolean LOGD = false;
private ChartAxis mHoriz;
private ChartAxis mVert;
@@ -60,6 +61,14 @@ public class ChartNetworkSeriesView extends View {
private long mPrimaryLeft;
private long mPrimaryRight;
+ /** Series will be extended to reach this end time. */
+ private long mEndTime = Long.MIN_VALUE;
+
+ private boolean mEstimateVisible = false;
+
+ private long mMax;
+ private long mMaxEstimate;
+
public ChartNetworkSeriesView(Context context) {
this(context, null, 0);
}
@@ -116,6 +125,7 @@ public class ChartNetworkSeriesView extends View {
mPaintEstimate.setColor(fillSecondary);
mPaintEstimate.setStyle(Style.STROKE);
mPaintEstimate.setAntiAlias(true);
+ mPaintEstimate.setPathEffect(new DashPathEffect(new float[] { 10, 10 }, 1));
}
public void bindNetworkStats(NetworkStatsHistory stats) {
@@ -149,6 +159,7 @@ public class ChartNetworkSeriesView extends View {
public void generatePath() {
if (LOGD) Log.d(TAG, "generatePath()");
+ mMax = 0;
mPathStroke.reset();
mPathFill.reset();
mPathEstimate.reset();
@@ -174,8 +185,8 @@ public class ChartNetworkSeriesView extends View {
for (int i = 0; i < mStats.size(); i++) {
entry = mStats.getValues(i, entry);
- lastTime = entry.bucketStart;
- final float x = mHoriz.convertToPoint(entry.bucketStart);
+ lastTime = entry.bucketStart + entry.bucketDuration;
+ final float x = mHoriz.convertToPoint(lastTime);
final float y = mVert.convertToPoint(totalData);
// skip until we find first stats on screen
@@ -199,6 +210,16 @@ public class ChartNetworkSeriesView extends View {
lastY = y;
}
+ // when data falls short, extend to requested end time
+ if (lastTime < mEndTime) {
+ lastX = mHoriz.convertToPoint(mEndTime);
+
+ if (started) {
+ mPathStroke.lineTo(lastX, lastY);
+ mPathFill.lineTo(lastX, lastY);
+ }
+ }
+
if (LOGD) {
final RectF bounds = new RectF();
mPathFill.computeBounds(bounds, true);
@@ -210,6 +231,8 @@ public class ChartNetworkSeriesView extends View {
mPathFill.lineTo(lastX, height);
mPathFill.lineTo(firstX, height);
+ mMax = totalData;
+
// build estimated data
mPathEstimate.moveTo(lastX, lastY);
@@ -234,10 +257,29 @@ public class ChartNetworkSeriesView extends View {
totalData += (longWindow * 7 + shortWindow * 3) / 10;
lastX = mHoriz.convertToPoint(lastTime + futureTime);
- final float y = mVert.convertToPoint(totalData);
+ lastY = mVert.convertToPoint(totalData);
- mPathEstimate.lineTo(lastX, y);
+ mPathEstimate.lineTo(lastX, lastY);
}
+
+ mMaxEstimate = totalData;
+ }
+
+ public void setEndTime(long endTime) {
+ mEndTime = endTime;
+ }
+
+ public void setEstimateVisible(boolean estimateVisible) {
+ mEstimateVisible = estimateVisible;
+ invalidate();
+ }
+
+ public long getMaxEstimate() {
+ return mMaxEstimate;
+ }
+
+ public long getMaxVisible() {
+ return mEstimateVisible ? mMaxEstimate : mMax;
}
@Override
@@ -247,10 +289,12 @@ public class ChartNetworkSeriesView extends View {
final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
- save = canvas.save();
- canvas.clipRect(0, 0, getWidth(), getHeight());
- canvas.drawPath(mPathEstimate, mPaintEstimate);
- canvas.restoreToCount(save);
+ 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());
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 99c35bd..b5e044f 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -29,6 +29,7 @@ import android.text.Layout.Alignment;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
@@ -48,6 +49,7 @@ public class ChartSweepView extends FrameLayout {
private Point mSweepOffset = new Point();
private Rect mMargins = new Rect();
+ private float mNeighborMargin;
private int mFollowAxis;
@@ -65,7 +67,6 @@ public class ChartSweepView extends FrameLayout {
private long mValidBefore;
private ChartSweepView mValidAfterDynamic;
private ChartSweepView mValidBeforeDynamic;
- private long mValidBufferArea;
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
@@ -93,6 +94,7 @@ public class ChartSweepView extends FrameLayout {
setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
+ setNeighborMargin(a.getDimensionPixelSize(R.styleable.ChartSweepView_neighborMargin, 0));
setLabelSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
@@ -271,16 +273,18 @@ public class ChartSweepView extends FrameLayout {
mValidBefore = validBefore;
}
+ public void setNeighborMargin(float neighborMargin) {
+ mNeighborMargin = neighborMargin;
+ }
+
/**
* Set valid range this sweep can move within, defined by the given
* {@link ChartSweepView}. The most restrictive combination of all valid
* ranges is used.
*/
- public void setValidRangeDynamic(
- ChartSweepView validAfter, ChartSweepView validBefore, long bufferArea) {
+ public void setValidRangeDynamic(ChartSweepView validAfter, ChartSweepView validBefore) {
mValidAfterDynamic = validAfter;
mValidBeforeDynamic = validBefore;
- mValidBufferArea = bufferArea;
}
@Override
@@ -316,9 +320,7 @@ public class ChartSweepView extends FrameLayout {
getParent().requestDisallowInterceptTouchEvent(true);
// content area of parent
- final Rect parentContent = new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
- parent.getWidth() - parent.getPaddingRight(),
- parent.getHeight() - parent.getPaddingBottom());
+ final Rect parentContent = getParentContentRect();
final Rect clampRect = computeClampRect(parentContent);
if (mFollowAxis == VERTICAL) {
@@ -358,6 +360,33 @@ public class ChartSweepView extends FrameLayout {
}
}
+ /**
+ * Update {@link #mValue} based on current position, including any
+ * {@link #onTouchEvent(MotionEvent)} in progress. Typically used when
+ * {@link ChartAxis} changes during sweep adjustment.
+ */
+ public void updateValueFromPosition() {
+ final Rect parentContent = getParentContentRect();
+ if (mFollowAxis == VERTICAL) {
+ final float effectiveY = getY() - mMargins.top - parentContent.top;
+ setValue(mAxis.convertToValue(effectiveY));
+ } else {
+ final float effectiveX = getX() - mMargins.left - parentContent.left;
+ setValue(mAxis.convertToValue(effectiveX));
+ }
+ }
+
+ public int shouldAdjustAxis() {
+ return mAxis.shouldAdjustAxis(getValue());
+ }
+
+ private Rect getParentContentRect() {
+ final View parent = (View) getParent();
+ return new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
+ parent.getWidth() - parent.getPaddingRight(),
+ parent.getHeight() - parent.getPaddingBottom());
+ }
+
@Override
public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
// ignored to keep LayoutTransition from animating us
@@ -368,18 +397,14 @@ public class ChartSweepView extends FrameLayout {
// ignored to keep LayoutTransition from animating us
}
- private long getValidAfterValue() {
+ private long getValidAfterDynamic() {
final ChartSweepView dynamic = mValidAfterDynamic;
- final boolean dynamicEnabled = dynamic != null && dynamic.isEnabled();
- return Math.max(mValidAfter,
- dynamicEnabled ? dynamic.getValue() + mValidBufferArea : Long.MIN_VALUE);
+ return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MIN_VALUE;
}
- private long getValidBeforeValue() {
+ private long getValidBeforeDynamic() {
final ChartSweepView dynamic = mValidBeforeDynamic;
- final boolean dynamicEnabled = dynamic != null && dynamic.isEnabled();
- return Math.min(mValidBefore,
- dynamicEnabled ? dynamic.getValue() - mValidBufferArea : Long.MAX_VALUE);
+ return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MAX_VALUE;
}
/**
@@ -388,22 +413,36 @@ public class ChartSweepView extends FrameLayout {
* style rules.
*/
private Rect computeClampRect(Rect parentContent) {
- final Rect clampRect = new Rect(parentContent);
+ // create two rectangles, and pick most restrictive combination
+ final Rect rect = buildClampRect(parentContent, mValidAfter, mValidBefore, 0f);
+ final Rect dynamicRect = buildClampRect(
+ parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
- float validAfterPoint = mAxis.convertToPoint(getValidAfterValue());
- float validBeforePoint = mAxis.convertToPoint(getValidBeforeValue());
- if (validAfterPoint > validBeforePoint) {
- float swap = validBeforePoint;
- validBeforePoint = validAfterPoint;
- validAfterPoint = swap;
+ rect.intersect(dynamicRect);
+ return rect;
+ }
+
+ private Rect buildClampRect(
+ Rect parentContent, long afterValue, long beforeValue, float margin) {
+ if (mAxis instanceof InvertedChartAxis) {
+ long temp = beforeValue;
+ beforeValue = afterValue;
+ afterValue = temp;
}
+ final boolean afterValid = afterValue != Long.MIN_VALUE && afterValue != Long.MAX_VALUE;
+ final boolean beforeValid = beforeValue != Long.MIN_VALUE && beforeValue != Long.MAX_VALUE;
+
+ final float afterPoint = mAxis.convertToPoint(afterValue) + margin;
+ final float beforePoint = mAxis.convertToPoint(beforeValue) - margin;
+
+ final Rect clampRect = new Rect(parentContent);
if (mFollowAxis == VERTICAL) {
- clampRect.bottom = clampRect.top + (int) validBeforePoint;
- clampRect.top += validAfterPoint;
+ if (beforeValid) clampRect.bottom = clampRect.top + (int) beforePoint;
+ if (afterValid) clampRect.top += afterPoint;
} else {
- clampRect.right = clampRect.left + (int) validBeforePoint;
- clampRect.left += validAfterPoint;
+ if (beforeValid) clampRect.right = clampRect.left + (int) beforePoint;
+ if (afterValid) clampRect.left += afterPoint;
}
return clampRect;
}
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java
index 839171e..b2ad844 100644
--- a/src/com/android/settings/widget/DataUsageChartView.java
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -16,12 +16,12 @@
package com.android.settings.widget;
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
-
import android.content.Context;
import android.content.res.Resources;
import android.net.NetworkPolicy;
import android.net.NetworkStatsHistory;
+import android.os.Handler;
+import android.os.Message;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -43,7 +43,8 @@ public class DataUsageChartView extends ChartView {
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 static final int MSG_UPDATE_AXIS = 100;
+ private static final long DELAY_MILLIS = 250;
private ChartGridView mGrid;
private ChartNetworkSeriesView mSeries;
@@ -56,6 +57,11 @@ public class DataUsageChartView extends ChartView {
private ChartSweepView mSweepWarning;
private ChartSweepView mSweepLimit;
+ private Handler mHandler;
+
+ /** Current maximum value of {@link #mVert}. */
+ private long mVertMax;
+
public interface DataUsageChartListener {
public void onInspectRangeChanged();
public void onWarningChanged();
@@ -75,6 +81,18 @@ public class DataUsageChartView extends ChartView {
public DataUsageChartView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
+
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ final ChartSweepView sweep = (ChartSweepView) msg.obj;
+ updateVertAxisBounds(sweep);
+ updateEstimateVisible();
+
+ // we keep dispatching repeating updates until sweep is dropped
+ sendUpdateAxisDelayed(sweep, true);
+ }
+ };
}
@Override
@@ -92,19 +110,15 @@ public class DataUsageChartView extends ChartView {
mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
// prevent sweeps from crossing each other
- mSweepLeft.setValidRangeDynamic(null, mSweepRight, HOUR_IN_MILLIS);
- mSweepRight.setValidRangeDynamic(mSweepLeft, null, HOUR_IN_MILLIS);
-
- // TODO: assign these ranges as user changes data axis
- mSweepWarning.setValidRange(0L, 5 * GB_IN_BYTES);
- mSweepWarning.setValidRangeDynamic(null, mSweepLimit, MB_IN_BYTES);
- mSweepLimit.setValidRange(0L, 5 * GB_IN_BYTES);
- mSweepLimit.setValidRangeDynamic(mSweepWarning, null, MB_IN_BYTES);
+ mSweepLeft.setValidRangeDynamic(null, mSweepRight);
+ mSweepRight.setValidRangeDynamic(mSweepLeft, null);
+ mSweepWarning.setValidRangeDynamic(null, mSweepLimit);
+ mSweepLimit.setValidRangeDynamic(mSweepWarning, null);
- mSweepLeft.addOnSweepListener(mSweepListener);
- mSweepRight.addOnSweepListener(mSweepListener);
- mSweepWarning.addOnSweepListener(mWarningListener);
- mSweepLimit.addOnSweepListener(mLimitListener);
+ mSweepLeft.addOnSweepListener(mHorizListener);
+ mSweepRight.addOnSweepListener(mHorizListener);
+ mSweepWarning.addOnSweepListener(mVertListener);
+ mSweepLimit.addOnSweepListener(mVertListener);
// tell everyone about our axis
mGrid.init(mHoriz, mVert);
@@ -125,6 +139,8 @@ public class DataUsageChartView extends ChartView {
public void bindNetworkStats(NetworkStatsHistory stats) {
mSeries.bindNetworkStats(stats);
mHistory = stats;
+ updateVertAxisBounds(null);
+ updateEstimateVisible();
updatePrimaryRange();
requestLayout();
}
@@ -132,6 +148,11 @@ public class DataUsageChartView extends ChartView {
public void bindDetailNetworkStats(NetworkStatsHistory stats) {
mDetailSeries.bindNetworkStats(stats);
mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE);
+ if (mHistory != null) {
+ mDetailSeries.setEndTime(mHistory.getEnd());
+ }
+ updateVertAxisBounds(null);
+ updateEstimateVisible();
updatePrimaryRange();
requestLayout();
}
@@ -139,17 +160,20 @@ public class DataUsageChartView extends ChartView {
public void bindNetworkPolicy(NetworkPolicy policy) {
if (policy == null) {
mSweepLimit.setVisibility(View.INVISIBLE);
+ mSweepLimit.setValue(-1);
mSweepWarning.setVisibility(View.INVISIBLE);
+ mSweepWarning.setValue(-1);
return;
}
if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
mSweepLimit.setVisibility(View.VISIBLE);
- mSweepLimit.setValue(policy.limitBytes);
mSweepLimit.setEnabled(true);
+ mSweepLimit.setValue(policy.limitBytes);
} else {
mSweepLimit.setVisibility(View.VISIBLE);
mSweepLimit.setEnabled(false);
+ mSweepLimit.setValue(-1);
}
if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
@@ -157,12 +181,81 @@ public class DataUsageChartView extends ChartView {
mSweepWarning.setValue(policy.warningBytes);
} else {
mSweepWarning.setVisibility(View.INVISIBLE);
+ mSweepWarning.setValue(-1);
}
+ updateVertAxisBounds(null);
requestLayout();
}
- private OnSweepListener mSweepListener = new OnSweepListener() {
+ /**
+ * Update {@link #mVert} to both show data from {@link NetworkStatsHistory}
+ * and controls from {@link NetworkPolicy}.
+ */
+ private void updateVertAxisBounds(ChartSweepView activeSweep) {
+ final long max = mVertMax;
+ final long newMax;
+ if (activeSweep != null) {
+ final int adjustAxis = activeSweep.shouldAdjustAxis();
+ if (adjustAxis > 0) {
+ // hovering around upper edge, grow axis
+ newMax = max * 11 / 10;
+ } else if (adjustAxis < 0) {
+ // hovering around lower edge, shrink axis
+ newMax = max * 9 / 10;
+ } else {
+ newMax = max;
+ }
+
+ } else {
+ // try showing all known data and policy
+ final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
+ final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
+ newMax = Math.max(maxVisible, 2 * GB_IN_BYTES);
+ }
+
+ // only invalidate when vertMax actually changed
+ if (newMax != mVertMax) {
+ mVertMax = newMax;
+
+ mVert.setBounds(0L, newMax);
+ mSweepWarning.setValidRange(0L, newMax);
+ mSweepLimit.setValidRange(0L, newMax);
+
+ mSeries.generatePath();
+ mDetailSeries.generatePath();
+
+ mGrid.invalidate();
+ mSeries.invalidate();
+ mDetailSeries.invalidate();
+
+ // since we just changed axis, make sweep recalculate its value
+ if (activeSweep != null) {
+ activeSweep.updateValueFromPosition();
+ }
+ }
+ }
+
+ /**
+ * Control {@link ChartNetworkSeriesView#setEstimateVisible(boolean)} based
+ * on how close estimate comes to {@link #mSweepWarning}.
+ */
+ private void updateEstimateVisible() {
+ final long maxEstimate = mSeries.getMaxEstimate();
+
+ // show estimate when near warning/limit
+ long interestLine = Long.MAX_VALUE;
+ if (mSweepWarning.isEnabled()) {
+ interestLine = mSweepWarning.getValue();
+ } else if (mSweepLimit.isEnabled()) {
+ interestLine = mSweepLimit.getValue();
+ }
+
+ final boolean estimateVisible = (maxEstimate >= interestLine * 7 / 10);
+ mSeries.setEstimateVisible(estimateVisible);
+ }
+
+ private OnSweepListener mHorizListener = new OnSweepListener() {
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
updatePrimaryRange();
@@ -173,18 +266,31 @@ public class DataUsageChartView extends ChartView {
}
};
- private OnSweepListener mWarningListener = new OnSweepListener() {
- public void onSweep(ChartSweepView sweep, boolean sweepDone) {
- if (sweepDone && mListener != null) {
- mListener.onWarningChanged();
- }
+ private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
+ if (force || !mHandler.hasMessages(MSG_UPDATE_AXIS, sweep)) {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_UPDATE_AXIS, sweep), DELAY_MILLIS);
}
- };
+ }
+
+ private void clearUpdateAxisDelayed(ChartSweepView sweep) {
+ mHandler.removeMessages(MSG_UPDATE_AXIS, sweep);
+ }
- private OnSweepListener mLimitListener = new OnSweepListener() {
+ private OnSweepListener mVertListener = new OnSweepListener() {
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
- if (sweepDone && mListener != null) {
- mListener.onLimitChanged();
+ if (sweepDone) {
+ clearUpdateAxisDelayed(sweep);
+ updateEstimateVisible();
+
+ if (sweep == mSweepWarning && mListener != null) {
+ mListener.onWarningChanged();
+ } else if (sweep == mSweepLimit && mListener != null) {
+ mListener.onLimitChanged();
+ }
+ } else {
+ // while moving, kick off delayed grow/shrink axis updates
+ sendUpdateAxisDelayed(sweep, false);
}
}
};
@@ -252,11 +358,14 @@ public class DataUsageChartView extends ChartView {
mSweepLeft.setValue(sweepMin);
mSweepRight.setValue(sweepMax);
- updatePrimaryRange();
requestLayout();
mSeries.generatePath();
mSeries.invalidate();
+
+ updateVertAxisBounds(null);
+ updateEstimateVisible();
+ updatePrimaryRange();
}
private void updatePrimaryRange() {
@@ -321,6 +430,12 @@ public class DataUsageChartView extends ChartView {
}
return tickPoints;
}
+
+ /** {@inheritDoc} */
+ public int shouldAdjustAxis(long value) {
+ // time axis never adjusts
+ return 0;
+ }
}
public static class DataAxis implements ChartAxis {
@@ -328,12 +443,6 @@ public class DataUsageChartView extends ChartView {
private long mMax;
private float mSize;
- public DataAxis() {
- // TODO: adapt ranges to show when history >5GB, and handle 4G
- // interfaces with higher limits.
- setBounds(0, 5 * GB_IN_BYTES);
- }
-
/** {@inheritDoc} */
public void setBounds(long min, long max) {
mMin = min;
@@ -347,19 +456,19 @@ public class DataUsageChartView extends ChartView {
/** {@inheritDoc} */
public float convertToPoint(long value) {
- // TODO: this assumes range of [0,5]GB
+ // derived polynomial fit to make lower values more visible
+ final double normalized = ((double) value - mMin) / (mMax - mMin);
final double fraction = Math.pow(
- 10, 0.36884343106175160321 * Math.log10(value) + -3.62828151137812282556);
- return (float) fraction * mSize;
+ 10, 0.36884343106175121463 * Math.log10(normalized) + -0.04328199452018252624);
+ return (float) (fraction * mSize);
}
/** {@inheritDoc} */
public long convertToValue(float point) {
- final double y = point / mSize;
- // TODO: this assumes range of [0,5]GB
- final double fraction = 6.869341163271789302 * Math.pow(10, 9)
- * Math.pow(y, 2.71117746931646030774);
- return (long) fraction;
+ final double normalized = point / mSize;
+ final double fraction = 1.3102228476089056629
+ * Math.pow(normalized, 2.7111774693164631640);
+ return (long) (mMin + (fraction * (mMax - mMin)));
}
private static final Object sSpanSize = new Object();
@@ -393,17 +502,31 @@ public class DataUsageChartView extends ChartView {
/** {@inheritDoc} */
public float[] getTickPoints() {
- final float[] tickPoints = new float[16];
+ final long range = mMax - mMin;
+ final long tickJump = 256 * MB_IN_BYTES;
- final long jump = ((mMax - mMin) / tickPoints.length);
+ final int tickCount = (int) (range / tickJump);
+ final float[] tickPoints = new float[tickCount];
long value = mMin;
for (int i = 0; i < tickPoints.length; i++) {
tickPoints[i] = convertToPoint(value);
- value += jump;
+ value += tickJump;
}
return tickPoints;
}
+
+ /** {@inheritDoc} */
+ public int shouldAdjustAxis(long value) {
+ final float point = convertToPoint(value);
+ if (point < mSize * 0.1) {
+ return -1;
+ } else if (point > mSize * 0.85) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
}
private static int[] findOrCreateSpan(
diff --git a/src/com/android/settings/widget/InvertedChartAxis.java b/src/com/android/settings/widget/InvertedChartAxis.java
index e589da9..96aec7b 100644
--- a/src/com/android/settings/widget/InvertedChartAxis.java
+++ b/src/com/android/settings/widget/InvertedChartAxis.java
@@ -64,4 +64,9 @@ public class InvertedChartAxis implements ChartAxis {
}
return points;
}
+
+ /** {@inheritDoc} */
+ public int shouldAdjustAxis(long value) {
+ return mWrapped.shouldAdjustAxis(value);
+ }
}