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
This commit is contained in:
@@ -56,6 +56,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
settings:sweepDrawable="@drawable/data_sweep_warning"
|
settings:sweepDrawable="@drawable/data_sweep_warning"
|
||||||
settings:followAxis="vertical"
|
settings:followAxis="vertical"
|
||||||
|
settings:neighborMargin="40dip"
|
||||||
settings:labelSize="60dip"
|
settings:labelSize="60dip"
|
||||||
settings:labelTemplate="@string/data_usage_sweep_warning"
|
settings:labelTemplate="@string/data_usage_sweep_warning"
|
||||||
settings:labelColor="#f7931d" />
|
settings:labelColor="#f7931d" />
|
||||||
@@ -66,6 +67,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
settings:sweepDrawable="@drawable/data_sweep_limit"
|
settings:sweepDrawable="@drawable/data_sweep_limit"
|
||||||
settings:followAxis="vertical"
|
settings:followAxis="vertical"
|
||||||
|
settings:neighborMargin="40dip"
|
||||||
settings:labelSize="60dip"
|
settings:labelSize="60dip"
|
||||||
settings:labelTemplate="@string/data_usage_sweep_limit"
|
settings:labelTemplate="@string/data_usage_sweep_limit"
|
||||||
settings:labelColor="#c01a2c" />
|
settings:labelColor="#c01a2c" />
|
||||||
@@ -75,13 +77,15 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
settings:sweepDrawable="@drawable/data_sweep_left"
|
settings:sweepDrawable="@drawable/data_sweep_left"
|
||||||
settings:followAxis="horizontal" />
|
settings:followAxis="horizontal"
|
||||||
|
settings:neighborMargin="5dip" />
|
||||||
|
|
||||||
<com.android.settings.widget.ChartSweepView
|
<com.android.settings.widget.ChartSweepView
|
||||||
android:id="@+id/sweep_right"
|
android:id="@+id/sweep_right"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
settings:sweepDrawable="@drawable/data_sweep_right"
|
settings:sweepDrawable="@drawable/data_sweep_right"
|
||||||
settings:followAxis="horizontal" />
|
settings:followAxis="horizontal"
|
||||||
|
settings:neighborMargin="5dip" />
|
||||||
|
|
||||||
</com.android.settings.widget.DataUsageChartView>
|
</com.android.settings.widget.DataUsageChartView>
|
||||||
|
@@ -45,6 +45,18 @@
|
|||||||
android:paddingBottom="8dip"
|
android:paddingBottom="8dip"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
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" />
|
<include layout="@layout/data_usage_detail" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@@ -56,6 +56,7 @@
|
|||||||
<enum name="horizontal" value="0" />
|
<enum name="horizontal" value="0" />
|
||||||
<enum name="vertical" value="1" />
|
<enum name="vertical" value="1" />
|
||||||
</attr>
|
</attr>
|
||||||
|
<attr name="neighborMargin" format="dimension" />
|
||||||
<attr name="labelSize" format="dimension" />
|
<attr name="labelSize" format="dimension" />
|
||||||
<attr name="labelTemplate" format="reference" />
|
<attr name="labelTemplate" format="reference" />
|
||||||
<attr name="labelColor" format="color" />
|
<attr name="labelColor" format="color" />
|
||||||
|
@@ -230,6 +230,8 @@
|
|||||||
<string name="bluetooth_disconnect_title">Disconnect?</string>
|
<string name="bluetooth_disconnect_title">Disconnect?</string>
|
||||||
<!-- Bluetooth settings. Message for disconnecting from all profiles of a bluetooth device. [CHAR LIMIT=NONE] -->
|
<!-- 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:<br><b><xliff:g id="device_name">%1$s</xliff:g></b></string>
|
<string name="bluetooth_disconnect_all_profiles">This will end your connection with:<br><b><xliff:g id="device_name">%1$s</xliff:g></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 -->
|
<!-- Bluetooth settings. Message when connected to a device -->
|
||||||
<string name="bluetooth_connected">Connected</string>
|
<string name="bluetooth_connected">Connected</string>
|
||||||
<!-- Bluetooth settings. Message when a device is disconnected -->
|
<!-- 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>
|
<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]-->
|
<!-- Storage setting. Title for PTP checkbox [CHAR LIMIT=30]-->
|
||||||
<string name="usb_ptp_title">Camera (PTP)</string>
|
<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>
|
<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: -->
|
<!-- Phone info screen, section titles: -->
|
||||||
<string name="battery_status_title">Battery status</string>
|
<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>
|
<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] -->
|
<!-- 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>
|
<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] -->
|
<!-- 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>
|
<string name="data_usage_disable_mobile_limit">Disable mobile data at limit</string>
|
||||||
|
@@ -182,6 +182,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
private DataUsageChartView mChart;
|
private DataUsageChartView mChart;
|
||||||
private TextView mUsageSummary;
|
private TextView mUsageSummary;
|
||||||
|
private TextView mEmpty;
|
||||||
|
|
||||||
private View mAppDetail;
|
private View mAppDetail;
|
||||||
private TextView mAppTitle;
|
private TextView mAppTitle;
|
||||||
@@ -305,6 +306,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary);
|
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
|
// only assign layout transitions once first layout is finished
|
||||||
mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
|
mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
|
||||||
@@ -986,7 +988,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
|
final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
|
||||||
final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
|
final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
|
||||||
final String rangePhrase = formatDateRangeUtc(context, start, end);
|
final String rangePhrase = formatDateRange(context, start, end, null);
|
||||||
|
|
||||||
mUsageSummary.setText(
|
mUsageSummary.setText(
|
||||||
getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
|
getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
|
||||||
@@ -1002,11 +1004,18 @@ public class DataUsageSummary extends Fragment {
|
|||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
|
public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
|
||||||
mAdapter.bindStats(data);
|
mAdapter.bindStats(data);
|
||||||
|
updateEmptyVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public void onLoaderReset(Loader<NetworkStats> loader) {
|
public void onLoaderReset(Loader<NetworkStats> loader) {
|
||||||
mAdapter.bindStats(null);
|
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) {
|
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.start = start;
|
||||||
this.end = end;
|
this.end = end;
|
||||||
}
|
}
|
||||||
@@ -1078,7 +1087,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
private static final java.util.Formatter sFormatter = new java.util.Formatter(
|
private static final java.util.Formatter sFormatter = new java.util.Formatter(
|
||||||
sBuilder, Locale.getDefault());
|
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) {
|
synchronized (sBuilder) {
|
||||||
int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH;
|
int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH;
|
||||||
if (Time.getJulianDay(start, 0) == Time.getJulianDay(end, 0)) {
|
if (Time.getJulianDay(start, 0) == Time.getJulianDay(end, 0)) {
|
||||||
@@ -1087,8 +1096,8 @@ public class DataUsageSummary extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sBuilder.setLength(0);
|
sBuilder.setLength(0);
|
||||||
return DateUtils.formatDateRange(
|
return DateUtils
|
||||||
context, sFormatter, start, end, flags, Time.TIMEZONE_UTC).toString();
|
.formatDateRange(context, sFormatter, start, end, flags, timezone).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1197,7 +1206,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
return position;
|
return mItems.get(position).uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -25,14 +25,26 @@ import android.text.SpannableStringBuilder;
|
|||||||
*/
|
*/
|
||||||
public interface ChartAxis {
|
public interface ChartAxis {
|
||||||
|
|
||||||
|
/** Set range of raw values this axis should cover. */
|
||||||
public void setBounds(long min, long max);
|
public void setBounds(long min, long max);
|
||||||
|
/** Set range of screen points this axis should cover. */
|
||||||
public void setSize(float size);
|
public void setSize(float size);
|
||||||
|
|
||||||
|
/** Convert raw value into screen point. */
|
||||||
public float convertToPoint(long value);
|
public float convertToPoint(long value);
|
||||||
|
/** Convert screen point into raw value. */
|
||||||
public long convertToValue(float point);
|
public long convertToValue(float point);
|
||||||
|
|
||||||
|
/** Build label that describes given raw value. */
|
||||||
public void buildLabel(Resources res, SpannableStringBuilder builder, long value);
|
public void buildLabel(Resources res, SpannableStringBuilder builder, long value);
|
||||||
|
|
||||||
|
/** Return list of tick points for drawing a grid. */
|
||||||
public float[] getTickPoints();
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ import android.content.Context;
|
|||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.DashPathEffect;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Paint.Style;
|
import android.graphics.Paint.Style;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
@@ -41,7 +42,7 @@ import com.google.common.base.Preconditions;
|
|||||||
*/
|
*/
|
||||||
public class ChartNetworkSeriesView extends View {
|
public class ChartNetworkSeriesView extends View {
|
||||||
private static final String TAG = "ChartNetworkSeriesView";
|
private static final String TAG = "ChartNetworkSeriesView";
|
||||||
private static final boolean LOGD = true;
|
private static final boolean LOGD = false;
|
||||||
|
|
||||||
private ChartAxis mHoriz;
|
private ChartAxis mHoriz;
|
||||||
private ChartAxis mVert;
|
private ChartAxis mVert;
|
||||||
@@ -60,6 +61,14 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
private long mPrimaryLeft;
|
private long mPrimaryLeft;
|
||||||
private long mPrimaryRight;
|
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) {
|
public ChartNetworkSeriesView(Context context) {
|
||||||
this(context, null, 0);
|
this(context, null, 0);
|
||||||
}
|
}
|
||||||
@@ -116,6 +125,7 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
mPaintEstimate.setColor(fillSecondary);
|
mPaintEstimate.setColor(fillSecondary);
|
||||||
mPaintEstimate.setStyle(Style.STROKE);
|
mPaintEstimate.setStyle(Style.STROKE);
|
||||||
mPaintEstimate.setAntiAlias(true);
|
mPaintEstimate.setAntiAlias(true);
|
||||||
|
mPaintEstimate.setPathEffect(new DashPathEffect(new float[] { 10, 10 }, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bindNetworkStats(NetworkStatsHistory stats) {
|
public void bindNetworkStats(NetworkStatsHistory stats) {
|
||||||
@@ -149,6 +159,7 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
public void generatePath() {
|
public void generatePath() {
|
||||||
if (LOGD) Log.d(TAG, "generatePath()");
|
if (LOGD) Log.d(TAG, "generatePath()");
|
||||||
|
|
||||||
|
mMax = 0;
|
||||||
mPathStroke.reset();
|
mPathStroke.reset();
|
||||||
mPathFill.reset();
|
mPathFill.reset();
|
||||||
mPathEstimate.reset();
|
mPathEstimate.reset();
|
||||||
@@ -174,8 +185,8 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
for (int i = 0; i < mStats.size(); i++) {
|
for (int i = 0; i < mStats.size(); i++) {
|
||||||
entry = mStats.getValues(i, entry);
|
entry = mStats.getValues(i, entry);
|
||||||
|
|
||||||
lastTime = entry.bucketStart;
|
lastTime = entry.bucketStart + entry.bucketDuration;
|
||||||
final float x = mHoriz.convertToPoint(entry.bucketStart);
|
final float x = mHoriz.convertToPoint(lastTime);
|
||||||
final float y = mVert.convertToPoint(totalData);
|
final float y = mVert.convertToPoint(totalData);
|
||||||
|
|
||||||
// skip until we find first stats on screen
|
// skip until we find first stats on screen
|
||||||
@@ -199,6 +210,16 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
lastY = y;
|
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) {
|
if (LOGD) {
|
||||||
final RectF bounds = new RectF();
|
final RectF bounds = new RectF();
|
||||||
mPathFill.computeBounds(bounds, true);
|
mPathFill.computeBounds(bounds, true);
|
||||||
@@ -210,6 +231,8 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
mPathFill.lineTo(lastX, height);
|
mPathFill.lineTo(lastX, height);
|
||||||
mPathFill.lineTo(firstX, height);
|
mPathFill.lineTo(firstX, height);
|
||||||
|
|
||||||
|
mMax = totalData;
|
||||||
|
|
||||||
// build estimated data
|
// build estimated data
|
||||||
mPathEstimate.moveTo(lastX, lastY);
|
mPathEstimate.moveTo(lastX, lastY);
|
||||||
|
|
||||||
@@ -234,10 +257,29 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
totalData += (longWindow * 7 + shortWindow * 3) / 10;
|
totalData += (longWindow * 7 + shortWindow * 3) / 10;
|
||||||
|
|
||||||
lastX = mHoriz.convertToPoint(lastTime + futureTime);
|
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
|
@Override
|
||||||
@@ -247,10 +289,12 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
|
final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
|
||||||
final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
|
final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
|
||||||
|
|
||||||
save = canvas.save();
|
if (mEstimateVisible) {
|
||||||
canvas.clipRect(0, 0, getWidth(), getHeight());
|
save = canvas.save();
|
||||||
canvas.drawPath(mPathEstimate, mPaintEstimate);
|
canvas.clipRect(0, 0, getWidth(), getHeight());
|
||||||
canvas.restoreToCount(save);
|
canvas.drawPath(mPathEstimate, mPaintEstimate);
|
||||||
|
canvas.restoreToCount(save);
|
||||||
|
}
|
||||||
|
|
||||||
save = canvas.save();
|
save = canvas.save();
|
||||||
canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
|
canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
|
||||||
|
@@ -29,6 +29,7 @@ import android.text.Layout.Alignment;
|
|||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.MathUtils;
|
import android.util.MathUtils;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -48,6 +49,7 @@ public class ChartSweepView extends FrameLayout {
|
|||||||
private Point mSweepOffset = new Point();
|
private Point mSweepOffset = new Point();
|
||||||
|
|
||||||
private Rect mMargins = new Rect();
|
private Rect mMargins = new Rect();
|
||||||
|
private float mNeighborMargin;
|
||||||
|
|
||||||
private int mFollowAxis;
|
private int mFollowAxis;
|
||||||
|
|
||||||
@@ -65,7 +67,6 @@ public class ChartSweepView extends FrameLayout {
|
|||||||
private long mValidBefore;
|
private long mValidBefore;
|
||||||
private ChartSweepView mValidAfterDynamic;
|
private ChartSweepView mValidAfterDynamic;
|
||||||
private ChartSweepView mValidBeforeDynamic;
|
private ChartSweepView mValidBeforeDynamic;
|
||||||
private long mValidBufferArea;
|
|
||||||
|
|
||||||
public static final int HORIZONTAL = 0;
|
public static final int HORIZONTAL = 0;
|
||||||
public static final int VERTICAL = 1;
|
public static final int VERTICAL = 1;
|
||||||
@@ -93,6 +94,7 @@ public class ChartSweepView extends FrameLayout {
|
|||||||
|
|
||||||
setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
|
setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
|
||||||
setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
|
setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
|
||||||
|
setNeighborMargin(a.getDimensionPixelSize(R.styleable.ChartSweepView_neighborMargin, 0));
|
||||||
|
|
||||||
setLabelSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
|
setLabelSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
|
||||||
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
|
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
|
||||||
@@ -271,16 +273,18 @@ public class ChartSweepView extends FrameLayout {
|
|||||||
mValidBefore = validBefore;
|
mValidBefore = validBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNeighborMargin(float neighborMargin) {
|
||||||
|
mNeighborMargin = neighborMargin;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set valid range this sweep can move within, defined by the given
|
* Set valid range this sweep can move within, defined by the given
|
||||||
* {@link ChartSweepView}. The most restrictive combination of all valid
|
* {@link ChartSweepView}. The most restrictive combination of all valid
|
||||||
* ranges is used.
|
* ranges is used.
|
||||||
*/
|
*/
|
||||||
public void setValidRangeDynamic(
|
public void setValidRangeDynamic(ChartSweepView validAfter, ChartSweepView validBefore) {
|
||||||
ChartSweepView validAfter, ChartSweepView validBefore, long bufferArea) {
|
|
||||||
mValidAfterDynamic = validAfter;
|
mValidAfterDynamic = validAfter;
|
||||||
mValidBeforeDynamic = validBefore;
|
mValidBeforeDynamic = validBefore;
|
||||||
mValidBufferArea = bufferArea;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -316,9 +320,7 @@ public class ChartSweepView extends FrameLayout {
|
|||||||
getParent().requestDisallowInterceptTouchEvent(true);
|
getParent().requestDisallowInterceptTouchEvent(true);
|
||||||
|
|
||||||
// content area of parent
|
// content area of parent
|
||||||
final Rect parentContent = new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
|
final Rect parentContent = getParentContentRect();
|
||||||
parent.getWidth() - parent.getPaddingRight(),
|
|
||||||
parent.getHeight() - parent.getPaddingBottom());
|
|
||||||
final Rect clampRect = computeClampRect(parentContent);
|
final Rect clampRect = computeClampRect(parentContent);
|
||||||
|
|
||||||
if (mFollowAxis == VERTICAL) {
|
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
|
@Override
|
||||||
public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
|
public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
|
||||||
// ignored to keep LayoutTransition from animating us
|
// ignored to keep LayoutTransition from animating us
|
||||||
@@ -368,18 +397,14 @@ public class ChartSweepView extends FrameLayout {
|
|||||||
// ignored to keep LayoutTransition from animating us
|
// ignored to keep LayoutTransition from animating us
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getValidAfterValue() {
|
private long getValidAfterDynamic() {
|
||||||
final ChartSweepView dynamic = mValidAfterDynamic;
|
final ChartSweepView dynamic = mValidAfterDynamic;
|
||||||
final boolean dynamicEnabled = dynamic != null && dynamic.isEnabled();
|
return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MIN_VALUE;
|
||||||
return Math.max(mValidAfter,
|
|
||||||
dynamicEnabled ? dynamic.getValue() + mValidBufferArea : Long.MIN_VALUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getValidBeforeValue() {
|
private long getValidBeforeDynamic() {
|
||||||
final ChartSweepView dynamic = mValidBeforeDynamic;
|
final ChartSweepView dynamic = mValidBeforeDynamic;
|
||||||
final boolean dynamicEnabled = dynamic != null && dynamic.isEnabled();
|
return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MAX_VALUE;
|
||||||
return Math.min(mValidBefore,
|
|
||||||
dynamicEnabled ? dynamic.getValue() - mValidBufferArea : Long.MAX_VALUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -388,22 +413,36 @@ public class ChartSweepView extends FrameLayout {
|
|||||||
* style rules.
|
* style rules.
|
||||||
*/
|
*/
|
||||||
private Rect computeClampRect(Rect parentContent) {
|
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());
|
rect.intersect(dynamicRect);
|
||||||
float validBeforePoint = mAxis.convertToPoint(getValidBeforeValue());
|
return rect;
|
||||||
if (validAfterPoint > validBeforePoint) {
|
}
|
||||||
float swap = validBeforePoint;
|
|
||||||
validBeforePoint = validAfterPoint;
|
private Rect buildClampRect(
|
||||||
validAfterPoint = swap;
|
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) {
|
if (mFollowAxis == VERTICAL) {
|
||||||
clampRect.bottom = clampRect.top + (int) validBeforePoint;
|
if (beforeValid) clampRect.bottom = clampRect.top + (int) beforePoint;
|
||||||
clampRect.top += validAfterPoint;
|
if (afterValid) clampRect.top += afterPoint;
|
||||||
} else {
|
} else {
|
||||||
clampRect.right = clampRect.left + (int) validBeforePoint;
|
if (beforeValid) clampRect.right = clampRect.left + (int) beforePoint;
|
||||||
clampRect.left += validAfterPoint;
|
if (afterValid) clampRect.left += afterPoint;
|
||||||
}
|
}
|
||||||
return clampRect;
|
return clampRect;
|
||||||
}
|
}
|
||||||
|
@@ -16,12 +16,12 @@
|
|||||||
|
|
||||||
package com.android.settings.widget;
|
package com.android.settings.widget;
|
||||||
|
|
||||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.net.NetworkPolicy;
|
import android.net.NetworkPolicy;
|
||||||
import android.net.NetworkStatsHistory;
|
import android.net.NetworkStatsHistory;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
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 MB_IN_BYTES = KB_IN_BYTES * 1024;
|
||||||
private static final long GB_IN_BYTES = MB_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 ChartGridView mGrid;
|
||||||
private ChartNetworkSeriesView mSeries;
|
private ChartNetworkSeriesView mSeries;
|
||||||
@@ -56,6 +57,11 @@ public class DataUsageChartView extends ChartView {
|
|||||||
private ChartSweepView mSweepWarning;
|
private ChartSweepView mSweepWarning;
|
||||||
private ChartSweepView mSweepLimit;
|
private ChartSweepView mSweepLimit;
|
||||||
|
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
|
/** Current maximum value of {@link #mVert}. */
|
||||||
|
private long mVertMax;
|
||||||
|
|
||||||
public interface DataUsageChartListener {
|
public interface DataUsageChartListener {
|
||||||
public void onInspectRangeChanged();
|
public void onInspectRangeChanged();
|
||||||
public void onWarningChanged();
|
public void onWarningChanged();
|
||||||
@@ -75,6 +81,18 @@ public class DataUsageChartView extends ChartView {
|
|||||||
public DataUsageChartView(Context context, AttributeSet attrs, int defStyle) {
|
public DataUsageChartView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
|
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
|
@Override
|
||||||
@@ -92,19 +110,15 @@ public class DataUsageChartView extends ChartView {
|
|||||||
mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
|
mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
|
||||||
|
|
||||||
// prevent sweeps from crossing each other
|
// prevent sweeps from crossing each other
|
||||||
mSweepLeft.setValidRangeDynamic(null, mSweepRight, HOUR_IN_MILLIS);
|
mSweepLeft.setValidRangeDynamic(null, mSweepRight);
|
||||||
mSweepRight.setValidRangeDynamic(mSweepLeft, null, HOUR_IN_MILLIS);
|
mSweepRight.setValidRangeDynamic(mSweepLeft, null);
|
||||||
|
mSweepWarning.setValidRangeDynamic(null, mSweepLimit);
|
||||||
|
mSweepLimit.setValidRangeDynamic(mSweepWarning, null);
|
||||||
|
|
||||||
// TODO: assign these ranges as user changes data axis
|
mSweepLeft.addOnSweepListener(mHorizListener);
|
||||||
mSweepWarning.setValidRange(0L, 5 * GB_IN_BYTES);
|
mSweepRight.addOnSweepListener(mHorizListener);
|
||||||
mSweepWarning.setValidRangeDynamic(null, mSweepLimit, MB_IN_BYTES);
|
mSweepWarning.addOnSweepListener(mVertListener);
|
||||||
mSweepLimit.setValidRange(0L, 5 * GB_IN_BYTES);
|
mSweepLimit.addOnSweepListener(mVertListener);
|
||||||
mSweepLimit.setValidRangeDynamic(mSweepWarning, null, MB_IN_BYTES);
|
|
||||||
|
|
||||||
mSweepLeft.addOnSweepListener(mSweepListener);
|
|
||||||
mSweepRight.addOnSweepListener(mSweepListener);
|
|
||||||
mSweepWarning.addOnSweepListener(mWarningListener);
|
|
||||||
mSweepLimit.addOnSweepListener(mLimitListener);
|
|
||||||
|
|
||||||
// tell everyone about our axis
|
// tell everyone about our axis
|
||||||
mGrid.init(mHoriz, mVert);
|
mGrid.init(mHoriz, mVert);
|
||||||
@@ -125,6 +139,8 @@ public class DataUsageChartView extends ChartView {
|
|||||||
public void bindNetworkStats(NetworkStatsHistory stats) {
|
public void bindNetworkStats(NetworkStatsHistory stats) {
|
||||||
mSeries.bindNetworkStats(stats);
|
mSeries.bindNetworkStats(stats);
|
||||||
mHistory = stats;
|
mHistory = stats;
|
||||||
|
updateVertAxisBounds(null);
|
||||||
|
updateEstimateVisible();
|
||||||
updatePrimaryRange();
|
updatePrimaryRange();
|
||||||
requestLayout();
|
requestLayout();
|
||||||
}
|
}
|
||||||
@@ -132,6 +148,11 @@ public class DataUsageChartView extends ChartView {
|
|||||||
public void bindDetailNetworkStats(NetworkStatsHistory stats) {
|
public void bindDetailNetworkStats(NetworkStatsHistory stats) {
|
||||||
mDetailSeries.bindNetworkStats(stats);
|
mDetailSeries.bindNetworkStats(stats);
|
||||||
mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE);
|
mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE);
|
||||||
|
if (mHistory != null) {
|
||||||
|
mDetailSeries.setEndTime(mHistory.getEnd());
|
||||||
|
}
|
||||||
|
updateVertAxisBounds(null);
|
||||||
|
updateEstimateVisible();
|
||||||
updatePrimaryRange();
|
updatePrimaryRange();
|
||||||
requestLayout();
|
requestLayout();
|
||||||
}
|
}
|
||||||
@@ -139,17 +160,20 @@ public class DataUsageChartView extends ChartView {
|
|||||||
public void bindNetworkPolicy(NetworkPolicy policy) {
|
public void bindNetworkPolicy(NetworkPolicy policy) {
|
||||||
if (policy == null) {
|
if (policy == null) {
|
||||||
mSweepLimit.setVisibility(View.INVISIBLE);
|
mSweepLimit.setVisibility(View.INVISIBLE);
|
||||||
|
mSweepLimit.setValue(-1);
|
||||||
mSweepWarning.setVisibility(View.INVISIBLE);
|
mSweepWarning.setVisibility(View.INVISIBLE);
|
||||||
|
mSweepWarning.setValue(-1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
|
if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
|
||||||
mSweepLimit.setVisibility(View.VISIBLE);
|
mSweepLimit.setVisibility(View.VISIBLE);
|
||||||
mSweepLimit.setValue(policy.limitBytes);
|
|
||||||
mSweepLimit.setEnabled(true);
|
mSweepLimit.setEnabled(true);
|
||||||
|
mSweepLimit.setValue(policy.limitBytes);
|
||||||
} else {
|
} else {
|
||||||
mSweepLimit.setVisibility(View.VISIBLE);
|
mSweepLimit.setVisibility(View.VISIBLE);
|
||||||
mSweepLimit.setEnabled(false);
|
mSweepLimit.setEnabled(false);
|
||||||
|
mSweepLimit.setValue(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
|
if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
|
||||||
@@ -157,12 +181,81 @@ public class DataUsageChartView extends ChartView {
|
|||||||
mSweepWarning.setValue(policy.warningBytes);
|
mSweepWarning.setValue(policy.warningBytes);
|
||||||
} else {
|
} else {
|
||||||
mSweepWarning.setVisibility(View.INVISIBLE);
|
mSweepWarning.setVisibility(View.INVISIBLE);
|
||||||
|
mSweepWarning.setValue(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateVertAxisBounds(null);
|
||||||
requestLayout();
|
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) {
|
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
||||||
updatePrimaryRange();
|
updatePrimaryRange();
|
||||||
|
|
||||||
@@ -173,18 +266,31 @@ public class DataUsageChartView extends ChartView {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private OnSweepListener mWarningListener = new OnSweepListener() {
|
private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
|
||||||
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
if (force || !mHandler.hasMessages(MSG_UPDATE_AXIS, sweep)) {
|
||||||
if (sweepDone && mListener != null) {
|
mHandler.sendMessageDelayed(
|
||||||
mListener.onWarningChanged();
|
mHandler.obtainMessage(MSG_UPDATE_AXIS, sweep), DELAY_MILLIS);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private OnSweepListener mLimitListener = new OnSweepListener() {
|
private void clearUpdateAxisDelayed(ChartSweepView sweep) {
|
||||||
|
mHandler.removeMessages(MSG_UPDATE_AXIS, sweep);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnSweepListener mVertListener = new OnSweepListener() {
|
||||||
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
||||||
if (sweepDone && mListener != null) {
|
if (sweepDone) {
|
||||||
mListener.onLimitChanged();
|
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);
|
mSweepLeft.setValue(sweepMin);
|
||||||
mSweepRight.setValue(sweepMax);
|
mSweepRight.setValue(sweepMax);
|
||||||
updatePrimaryRange();
|
|
||||||
|
|
||||||
requestLayout();
|
requestLayout();
|
||||||
mSeries.generatePath();
|
mSeries.generatePath();
|
||||||
mSeries.invalidate();
|
mSeries.invalidate();
|
||||||
|
|
||||||
|
updateVertAxisBounds(null);
|
||||||
|
updateEstimateVisible();
|
||||||
|
updatePrimaryRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePrimaryRange() {
|
private void updatePrimaryRange() {
|
||||||
@@ -321,6 +430,12 @@ public class DataUsageChartView extends ChartView {
|
|||||||
}
|
}
|
||||||
return tickPoints;
|
return tickPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public int shouldAdjustAxis(long value) {
|
||||||
|
// time axis never adjusts
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DataAxis implements ChartAxis {
|
public static class DataAxis implements ChartAxis {
|
||||||
@@ -328,12 +443,6 @@ public class DataUsageChartView extends ChartView {
|
|||||||
private long mMax;
|
private long mMax;
|
||||||
private float mSize;
|
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} */
|
/** {@inheritDoc} */
|
||||||
public void setBounds(long min, long max) {
|
public void setBounds(long min, long max) {
|
||||||
mMin = min;
|
mMin = min;
|
||||||
@@ -347,19 +456,19 @@ public class DataUsageChartView extends ChartView {
|
|||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public float convertToPoint(long value) {
|
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(
|
final double fraction = Math.pow(
|
||||||
10, 0.36884343106175160321 * Math.log10(value) + -3.62828151137812282556);
|
10, 0.36884343106175121463 * Math.log10(normalized) + -0.04328199452018252624);
|
||||||
return (float) fraction * mSize;
|
return (float) (fraction * mSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public long convertToValue(float point) {
|
public long convertToValue(float point) {
|
||||||
final double y = point / mSize;
|
final double normalized = point / mSize;
|
||||||
// TODO: this assumes range of [0,5]GB
|
final double fraction = 1.3102228476089056629
|
||||||
final double fraction = 6.869341163271789302 * Math.pow(10, 9)
|
* Math.pow(normalized, 2.7111774693164631640);
|
||||||
* Math.pow(y, 2.71117746931646030774);
|
return (long) (mMin + (fraction * (mMax - mMin)));
|
||||||
return (long) fraction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Object sSpanSize = new Object();
|
private static final Object sSpanSize = new Object();
|
||||||
@@ -393,17 +502,31 @@ public class DataUsageChartView extends ChartView {
|
|||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public float[] getTickPoints() {
|
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;
|
long value = mMin;
|
||||||
for (int i = 0; i < tickPoints.length; i++) {
|
for (int i = 0; i < tickPoints.length; i++) {
|
||||||
tickPoints[i] = convertToPoint(value);
|
tickPoints[i] = convertToPoint(value);
|
||||||
value += jump;
|
value += tickJump;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tickPoints;
|
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(
|
private static int[] findOrCreateSpan(
|
||||||
|
@@ -64,4 +64,9 @@ public class InvertedChartAxis implements ChartAxis {
|
|||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public int shouldAdjustAxis(long value) {
|
||||||
|
return mWrapped.shouldAdjustAxis(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user