Merge "Remove obsolete chart view widgets."
This commit is contained in:
committed by
Android (Google) Code Review
commit
12964a7ec0
@@ -1501,22 +1501,6 @@
|
|||||||
column="9"/>
|
column="9"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
|
||||||
id="HardCodedColor"
|
|
||||||
severity="Error"
|
|
||||||
message="Avoid using hardcoded color"
|
|
||||||
category="Correctness"
|
|
||||||
priority="4"
|
|
||||||
summary="Using hardcoded color"
|
|
||||||
explanation="Hardcoded color values are bad because theme changes cannot be uniformly applied.Instead use the theme specific colors such as `?android:attr/textColorPrimary` in attributes.
This ensures that a theme change from a light to a dark theme can be uniformlyapplied across the app."
|
|
||||||
errorLine1=" settings:fillColorSecondary="#ff80cbc4""
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="res/layout/data_usage_chart.xml"
|
|
||||||
line="47"
|
|
||||||
column="9"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
id="HardCodedColor"
|
id="HardCodedColor"
|
||||||
severity="Error"
|
severity="Error"
|
||||||
|
@@ -1,86 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- NOTE: this explicitly uses right/left padding, since the
|
|
||||||
graph isn't swapped in RTL languages -->
|
|
||||||
<com.android.settings.widget.ChartDataUsageView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
|
|
||||||
android:id="@+id/chart"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/data_usage_chart_height"
|
|
||||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
|
||||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
|
||||||
android:paddingTop="16dp"
|
|
||||||
android:paddingBottom="24dp">
|
|
||||||
|
|
||||||
<com.android.settings.widget.ChartGridView
|
|
||||||
android:id="@+id/grid"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="start|bottom"
|
|
||||||
android:paddingBottom="24dp"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Material.Caption"
|
|
||||||
settings:borderDrawable="@drawable/data_grid_border" />
|
|
||||||
|
|
||||||
<com.android.settings.widget.ChartNetworkSeriesView
|
|
||||||
android:id="@+id/series"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="start|bottom"
|
|
||||||
settings:strokeColor="#00000000"
|
|
||||||
settings:fillColor="?android:attr/colorAccent"
|
|
||||||
settings:fillColorSecondary="#ff80cbc4"
|
|
||||||
settings:safeRegion="3dp" />
|
|
||||||
|
|
||||||
<com.android.settings.widget.ChartNetworkSeriesView
|
|
||||||
android:id="@+id/detail_series"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="start|bottom"
|
|
||||||
settings:strokeColor="#00000000"
|
|
||||||
settings:fillColor="?android:attr/colorAccent"
|
|
||||||
settings:fillColorSecondary="?android:attr/colorAccent"
|
|
||||||
settings:safeRegion="3dp" />
|
|
||||||
|
|
||||||
<com.android.settings.widget.ChartSweepView
|
|
||||||
android:id="@+id/sweep_warning"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:nextFocusUp="@+id/sweep_limit"
|
|
||||||
settings:sweepDrawable="@drawable/data_sweep_warning"
|
|
||||||
settings:followAxis="vertical"
|
|
||||||
settings:neighborMargin="5dip"
|
|
||||||
settings:labelSize="60dip"
|
|
||||||
settings:labelTemplate="@string/data_usage_sweep_warning"
|
|
||||||
settings:labelColor="?android:attr/textColorSecondary"
|
|
||||||
settings:safeRegion="4dp" />
|
|
||||||
|
|
||||||
<com.android.settings.widget.ChartSweepView
|
|
||||||
android:id="@+id/sweep_limit"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:nextFocusDown="@+id/sweep_warning"
|
|
||||||
settings:sweepDrawable="@drawable/data_sweep_limit"
|
|
||||||
settings:followAxis="vertical"
|
|
||||||
settings:neighborMargin="5dip"
|
|
||||||
settings:labelSize="60dip"
|
|
||||||
settings:labelTemplate="@string/data_usage_sweep_limit"
|
|
||||||
settings:labelColor="?android:attr/colorError"
|
|
||||||
settings:safeRegion="4dp" />
|
|
||||||
|
|
||||||
</com.android.settings.widget.ChartDataUsageView>
|
|
@@ -50,13 +50,6 @@
|
|||||||
<attr name="android:textAppearance" />
|
<attr name="android:textAppearance" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="ChartNetworkSeriesView">
|
|
||||||
<attr name="strokeColor" format="color" />
|
|
||||||
<attr name="fillColor" format="color" />
|
|
||||||
<attr name="fillColorSecondary" format="color" />
|
|
||||||
<attr name="safeRegion" />
|
|
||||||
</declare-styleable>
|
|
||||||
|
|
||||||
<attr name="apnPreferenceStyle" format="reference" />
|
<attr name="apnPreferenceStyle" format="reference" />
|
||||||
|
|
||||||
<attr name="footerPreferenceStyle" format="reference" />
|
<attr name="footerPreferenceStyle" format="reference" />
|
||||||
|
@@ -14,18 +14,15 @@
|
|||||||
|
|
||||||
package com.android.settings.datausage;
|
package com.android.settings.datausage;
|
||||||
|
|
||||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
|
||||||
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
|
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
|
||||||
import static android.net.TrafficStats.UID_REMOVED;
|
import static android.net.TrafficStats.UID_REMOVED;
|
||||||
import static android.net.TrafficStats.UID_TETHERING;
|
import static android.net.TrafficStats.UID_TETHERING;
|
||||||
import static android.telephony.TelephonyManager.SIM_STATE_READY;
|
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.UserInfo;
|
import android.content.pm.UserInfo;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.INetworkStatsSession;
|
import android.net.INetworkStatsSession;
|
||||||
import android.net.NetworkPolicy;
|
import android.net.NetworkPolicy;
|
||||||
import android.net.NetworkStats;
|
import android.net.NetworkStats;
|
||||||
@@ -35,13 +32,11 @@ import android.net.TrafficStats;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.SystemProperties;
|
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.telephony.SubscriptionInfo;
|
import android.telephony.SubscriptionInfo;
|
||||||
import android.telephony.SubscriptionManager;
|
import android.telephony.SubscriptionManager;
|
||||||
import android.telephony.TelephonyManager;
|
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
@@ -28,7 +28,6 @@ import android.content.pm.UserInfo;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkPolicy;
|
import android.net.NetworkPolicy;
|
||||||
import android.net.NetworkStatsHistory;
|
|
||||||
import android.net.NetworkTemplate;
|
import android.net.NetworkTemplate;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -252,8 +251,7 @@ public class DataUsageListV2 extends DataUsageBaseFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update body content based on current tab. Loads
|
* Update body content based on current tab. Loads network cycle data from system, and
|
||||||
* {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
|
|
||||||
* binds them to visible controls.
|
* binds them to visible controls.
|
||||||
*/
|
*/
|
||||||
private void updateBody() {
|
private void updateBody() {
|
||||||
|
@@ -1,606 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2011 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.widget;
|
|
||||||
|
|
||||||
import static android.net.TrafficStats.MB_IN_BYTES;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.net.NetworkPolicy;
|
|
||||||
import android.net.NetworkStatsHistory;
|
|
||||||
import android.net.TrafficStats;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.text.format.Formatter;
|
|
||||||
import android.text.format.Formatter.BytesResult;
|
|
||||||
import android.text.format.Time;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.MathUtils;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.widget.ChartSweepView.OnSweepListener;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along
|
|
||||||
* with {@link ChartSweepView} for inspection ranges and warning/limits.
|
|
||||||
*/
|
|
||||||
public class ChartDataUsageView extends ChartView {
|
|
||||||
|
|
||||||
private static final int MSG_UPDATE_AXIS = 100;
|
|
||||||
private static final long DELAY_MILLIS = 250;
|
|
||||||
|
|
||||||
private ChartGridView mGrid;
|
|
||||||
private ChartNetworkSeriesView mSeries;
|
|
||||||
private ChartNetworkSeriesView mDetailSeries;
|
|
||||||
|
|
||||||
private NetworkStatsHistory mHistory;
|
|
||||||
|
|
||||||
private ChartSweepView mSweepWarning;
|
|
||||||
private ChartSweepView mSweepLimit;
|
|
||||||
|
|
||||||
private long mInspectStart;
|
|
||||||
private long mInspectEnd;
|
|
||||||
|
|
||||||
private Handler mHandler;
|
|
||||||
|
|
||||||
/** Current maximum value of {@link #mVert}. */
|
|
||||||
private long mVertMax;
|
|
||||||
|
|
||||||
public interface DataUsageChartListener {
|
|
||||||
public void onWarningChanged();
|
|
||||||
public void onLimitChanged();
|
|
||||||
public void requestWarningEdit();
|
|
||||||
public void requestLimitEdit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private DataUsageChartListener mListener;
|
|
||||||
|
|
||||||
public ChartDataUsageView(Context context) {
|
|
||||||
this(context, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChartDataUsageView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChartDataUsageView(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
|
|
||||||
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);
|
|
||||||
|
|
||||||
mSweepLimit = (ChartSweepView) findViewById(R.id.sweep_limit);
|
|
||||||
mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
|
|
||||||
|
|
||||||
// prevent sweeps from crossing each other
|
|
||||||
mSweepWarning.setValidRangeDynamic(null, mSweepLimit);
|
|
||||||
mSweepLimit.setValidRangeDynamic(mSweepWarning, null);
|
|
||||||
|
|
||||||
// mark neighbors for checking touch events against
|
|
||||||
mSweepLimit.setNeighbors(mSweepWarning);
|
|
||||||
mSweepWarning.setNeighbors(mSweepLimit);
|
|
||||||
|
|
||||||
mSweepWarning.addOnSweepListener(mVertListener);
|
|
||||||
mSweepLimit.addOnSweepListener(mVertListener);
|
|
||||||
|
|
||||||
mSweepWarning.setDragInterval(5 * MB_IN_BYTES);
|
|
||||||
mSweepLimit.setDragInterval(5 * MB_IN_BYTES);
|
|
||||||
|
|
||||||
// tell everyone about our axis
|
|
||||||
mGrid.init(mHoriz, mVert);
|
|
||||||
mSeries.init(mHoriz, mVert);
|
|
||||||
mDetailSeries.init(mHoriz, mVert);
|
|
||||||
mSweepWarning.init(mVert);
|
|
||||||
mSweepLimit.init(mVert);
|
|
||||||
|
|
||||||
setActivated(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setListener(DataUsageChartListener listener) {
|
|
||||||
mListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bindNetworkStats(NetworkStatsHistory stats) {
|
|
||||||
mSeries.bindNetworkStats(stats);
|
|
||||||
mHistory = stats;
|
|
||||||
updateVertAxisBounds(null);
|
|
||||||
updateEstimateVisible();
|
|
||||||
updatePrimaryRange();
|
|
||||||
requestLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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.setEnabled(true);
|
|
||||||
mSweepLimit.setValue(policy.limitBytes);
|
|
||||||
} else {
|
|
||||||
mSweepLimit.setVisibility(View.INVISIBLE);
|
|
||||||
mSweepLimit.setEnabled(false);
|
|
||||||
mSweepLimit.setValue(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
|
|
||||||
mSweepWarning.setVisibility(View.VISIBLE);
|
|
||||||
mSweepWarning.setValue(policy.warningBytes);
|
|
||||||
} else {
|
|
||||||
mSweepWarning.setVisibility(View.INVISIBLE);
|
|
||||||
mSweepWarning.setValue(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateVertAxisBounds(null);
|
|
||||||
requestLayout();
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update {@link #mVert} to both show data from {@link NetworkStatsHistory}
|
|
||||||
* and controls from {@link NetworkPolicy}.
|
|
||||||
*/
|
|
||||||
private void updateVertAxisBounds(ChartSweepView activeSweep) {
|
|
||||||
final long max = mVertMax;
|
|
||||||
|
|
||||||
long newMax = 0;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// always show known data and policy lines
|
|
||||||
final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
|
|
||||||
final long maxSeries = Math.max(mSeries.getMaxVisible(), mDetailSeries.getMaxVisible());
|
|
||||||
final long maxVisible = Math.max(maxSeries, maxSweep) * 12 / 10;
|
|
||||||
final long maxDefault = Math.max(maxVisible, 50 * MB_IN_BYTES);
|
|
||||||
newMax = Math.max(maxDefault, newMax);
|
|
||||||
|
|
||||||
// only invalidate when vertMax actually changed
|
|
||||||
if (newMax != mVertMax) {
|
|
||||||
mVertMax = newMax;
|
|
||||||
|
|
||||||
final boolean changed = mVert.setBounds(0L, newMax);
|
|
||||||
mSweepWarning.setValidRange(0L, newMax);
|
|
||||||
mSweepLimit.setValidRange(0L, newMax);
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
mSeries.invalidatePath();
|
|
||||||
mDetailSeries.invalidatePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
mGrid.invalidate();
|
|
||||||
|
|
||||||
// since we just changed axis, make sweep recalculate its value
|
|
||||||
if (activeSweep != null) {
|
|
||||||
activeSweep.updateValueFromPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
// layout other sweeps to match changed axis
|
|
||||||
// TODO: find cleaner way of doing this, such as requesting full
|
|
||||||
// layout and making activeSweep discard its tracking MotionEvent.
|
|
||||||
if (mSweepLimit != activeSweep) {
|
|
||||||
layoutSweep(mSweepLimit);
|
|
||||||
}
|
|
||||||
if (mSweepWarning != activeSweep) {
|
|
||||||
layoutSweep(mSweepWarning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interestLine < 0) {
|
|
||||||
interestLine = Long.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean estimateVisible = (maxEstimate >= interestLine * 7 / 10);
|
|
||||||
mSeries.setEstimateVisible(estimateVisible);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 mVertListener = new OnSweepListener() {
|
|
||||||
@Override
|
|
||||||
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void requestEdit(ChartSweepView sweep) {
|
|
||||||
if (sweep == mSweepWarning && mListener != null) {
|
|
||||||
mListener.requestWarningEdit();
|
|
||||||
} else if (sweep == mSweepLimit && mListener != null) {
|
|
||||||
mListener.requestLimitEdit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getInspectStart() {
|
|
||||||
return mInspectStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getInspectEnd() {
|
|
||||||
return mInspectEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getWarningBytes() {
|
|
||||||
return mSweepWarning.getLabelValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLimitBytes() {
|
|
||||||
return mSweepLimit.getLabelValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 visibleStart, long visibleEnd) {
|
|
||||||
final boolean changed = mHoriz.setBounds(visibleStart, visibleEnd);
|
|
||||||
mGrid.setBounds(visibleStart, visibleEnd);
|
|
||||||
mSeries.setBounds(visibleStart, visibleEnd);
|
|
||||||
mDetailSeries.setBounds(visibleStart, visibleEnd);
|
|
||||||
|
|
||||||
mInspectStart = visibleStart;
|
|
||||||
mInspectEnd = visibleEnd;
|
|
||||||
|
|
||||||
requestLayout();
|
|
||||||
if (changed) {
|
|
||||||
mSeries.invalidatePath();
|
|
||||||
mDetailSeries.invalidatePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateVertAxisBounds(null);
|
|
||||||
updateEstimateVisible();
|
|
||||||
updatePrimaryRange();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePrimaryRange() {
|
|
||||||
// prefer showing primary range on detail series, when available
|
|
||||||
if (mDetailSeries.getVisibility() == View.VISIBLE) {
|
|
||||||
mSeries.setSecondary(true);
|
|
||||||
} else {
|
|
||||||
mSeries.setSecondary(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TimeAxis implements ChartAxis {
|
|
||||||
private static final int FIRST_DAY_OF_WEEK = Calendar.getInstance().getFirstDayOfWeek() - 1;
|
|
||||||
|
|
||||||
private long mMin;
|
|
||||||
private long mMax;
|
|
||||||
private float mSize;
|
|
||||||
|
|
||||||
public TimeAxis() {
|
|
||||||
final long currentTime = System.currentTimeMillis();
|
|
||||||
setBounds(currentTime - DateUtils.DAY_IN_MILLIS * 30, currentTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(mMin, mMax, mSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean setBounds(long min, long max) {
|
|
||||||
if (mMin != min || mMax != max) {
|
|
||||||
mMin = min;
|
|
||||||
mMax = max;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean setSize(float size) {
|
|
||||||
if (mSize != size) {
|
|
||||||
mSize = size;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float convertToPoint(long value) {
|
|
||||||
return (mSize * (value - mMin)) / (mMax - mMin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long convertToValue(float point) {
|
|
||||||
return (long) (mMin + ((point * (mMax - mMin)) / mSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long buildLabel(Resources res, SpannableStringBuilder builder, long value) {
|
|
||||||
// TODO: convert to better string
|
|
||||||
builder.replace(0, builder.length(), Long.toString(value));
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float[] getTickPoints() {
|
|
||||||
final float[] ticks = new float[32];
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
// tick mark for first day of each week
|
|
||||||
final Time time = new Time();
|
|
||||||
time.set(mMax);
|
|
||||||
time.monthDay -= time.weekDay - FIRST_DAY_OF_WEEK;
|
|
||||||
time.hour = time.minute = time.second = 0;
|
|
||||||
|
|
||||||
time.normalize(true);
|
|
||||||
long timeMillis = time.toMillis(true);
|
|
||||||
while (timeMillis > mMin) {
|
|
||||||
if (timeMillis <= mMax) {
|
|
||||||
ticks[i++] = convertToPoint(timeMillis);
|
|
||||||
}
|
|
||||||
time.monthDay -= 7;
|
|
||||||
time.normalize(true);
|
|
||||||
timeMillis = time.toMillis(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Arrays.copyOf(ticks, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int shouldAdjustAxis(long value) {
|
|
||||||
// time axis never adjusts
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DataAxis implements ChartAxis {
|
|
||||||
private long mMin;
|
|
||||||
private long mMax;
|
|
||||||
private float mSize;
|
|
||||||
|
|
||||||
private static final boolean LOG_SCALE = false;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(mMin, mMax, mSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean setBounds(long min, long max) {
|
|
||||||
if (mMin != min || mMax != max) {
|
|
||||||
mMin = min;
|
|
||||||
mMax = max;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean setSize(float size) {
|
|
||||||
if (mSize != size) {
|
|
||||||
mSize = size;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float convertToPoint(long value) {
|
|
||||||
if (LOG_SCALE) {
|
|
||||||
// derived polynomial fit to make lower values more visible
|
|
||||||
final double normalized = ((double) value - mMin) / (mMax - mMin);
|
|
||||||
final double fraction = Math.pow(10,
|
|
||||||
0.36884343106175121463 * Math.log10(normalized) + -0.04328199452018252624);
|
|
||||||
return (float) (fraction * mSize);
|
|
||||||
} else {
|
|
||||||
return (mSize * (value - mMin)) / (mMax - mMin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long convertToValue(float point) {
|
|
||||||
if (LOG_SCALE) {
|
|
||||||
final double normalized = point / mSize;
|
|
||||||
final double fraction = 1.3102228476089056629
|
|
||||||
* Math.pow(normalized, 2.7111774693164631640);
|
|
||||||
return (long) (mMin + (fraction * (mMax - mMin)));
|
|
||||||
} else {
|
|
||||||
return (long) (mMin + ((point * (mMax - mMin)) / mSize));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Object sSpanSize = new Object();
|
|
||||||
private static final Object sSpanUnit = new Object();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long buildLabel(Resources res, SpannableStringBuilder builder, long value) {
|
|
||||||
value = MathUtils.constrain(value, 0, TrafficStats.TB_IN_BYTES);
|
|
||||||
final BytesResult result = Formatter.formatBytes(res, value,
|
|
||||||
Formatter.FLAG_SHORTER | Formatter.FLAG_CALCULATE_ROUNDED);
|
|
||||||
setText(builder, sSpanSize, result.value, "^1");
|
|
||||||
setText(builder, sSpanUnit, result.units, "^2");
|
|
||||||
return result.roundedBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float[] getTickPoints() {
|
|
||||||
final long range = mMax - mMin;
|
|
||||||
|
|
||||||
// target about 16 ticks on screen, rounded to nearest power of 2
|
|
||||||
final long tickJump = roundUpToPowerOfTwo(range / 16);
|
|
||||||
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 += tickJump;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tickPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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 void setText(
|
|
||||||
SpannableStringBuilder builder, Object key, CharSequence text, String bootstrap) {
|
|
||||||
int start = builder.getSpanStart(key);
|
|
||||||
int end = builder.getSpanEnd(key);
|
|
||||||
if (start == -1) {
|
|
||||||
start = TextUtils.indexOf(builder, bootstrap);
|
|
||||||
end = start + bootstrap.length();
|
|
||||||
builder.setSpan(key, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
|
||||||
}
|
|
||||||
builder.replace(start, end, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long roundUpToPowerOfTwo(long i) {
|
|
||||||
// NOTE: borrowed from Hashtable.roundUpToPowerOfTwo()
|
|
||||||
|
|
||||||
i--; // If input is a power of two, shift its high-order bit right
|
|
||||||
|
|
||||||
// "Smear" the high-order bit all the way to the right
|
|
||||||
i |= i >>> 1;
|
|
||||||
i |= i >>> 2;
|
|
||||||
i |= i >>> 4;
|
|
||||||
i |= i >>> 8;
|
|
||||||
i |= i >>> 16;
|
|
||||||
i |= i >>> 32;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
|
|
||||||
return i > 0 ? i : Long.MAX_VALUE;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,340 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2011 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.widget;
|
|
||||||
|
|
||||||
import static android.text.format.DateUtils.DAY_IN_MILLIS;
|
|
||||||
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
|
||||||
|
|
||||||
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;
|
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.net.NetworkStatsHistory;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.android.internal.util.Preconditions;
|
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link NetworkStatsHistory} series to render inside a {@link ChartView},
|
|
||||||
* using {@link ChartAxis} to map into screen coordinates.
|
|
||||||
*/
|
|
||||||
public class ChartNetworkSeriesView extends View {
|
|
||||||
private static final String TAG = "ChartNetworkSeriesView";
|
|
||||||
private static final boolean LOGD = false;
|
|
||||||
|
|
||||||
private static final boolean ESTIMATE_ENABLED = false;
|
|
||||||
|
|
||||||
private ChartAxis mHoriz;
|
|
||||||
private ChartAxis mVert;
|
|
||||||
|
|
||||||
private Paint mPaintStroke;
|
|
||||||
private Paint mPaintFill;
|
|
||||||
private Paint mPaintFillSecondary;
|
|
||||||
private Paint mPaintEstimate;
|
|
||||||
|
|
||||||
private NetworkStatsHistory mStats;
|
|
||||||
|
|
||||||
private Path mPathStroke;
|
|
||||||
private Path mPathFill;
|
|
||||||
private Path mPathEstimate;
|
|
||||||
|
|
||||||
private int mSafeRegion;
|
|
||||||
|
|
||||||
private long mStart;
|
|
||||||
private long mEnd;
|
|
||||||
|
|
||||||
/** Series will be extended to reach this end time. */
|
|
||||||
private long mEndTime = Long.MIN_VALUE;
|
|
||||||
|
|
||||||
private boolean mPathValid = false;
|
|
||||||
private boolean mEstimateVisible = false;
|
|
||||||
private boolean mSecondary = false;
|
|
||||||
|
|
||||||
private long mMax;
|
|
||||||
private long mMaxEstimate;
|
|
||||||
|
|
||||||
public ChartNetworkSeriesView(Context context) {
|
|
||||||
this(context, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChartNetworkSeriesView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChartNetworkSeriesView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
|
|
||||||
final TypedArray a = context.obtainStyledAttributes(
|
|
||||||
attrs, R.styleable.ChartNetworkSeriesView, defStyle, 0);
|
|
||||||
|
|
||||||
final int stroke = a.getColor(R.styleable.ChartNetworkSeriesView_strokeColor, Color.RED);
|
|
||||||
final int fill = a.getColor(R.styleable.ChartNetworkSeriesView_fillColor, Color.RED);
|
|
||||||
final int fillSecondary = a.getColor(
|
|
||||||
R.styleable.ChartNetworkSeriesView_fillColorSecondary, Color.RED);
|
|
||||||
final int safeRegion = a.getDimensionPixelSize(
|
|
||||||
R.styleable.ChartNetworkSeriesView_safeRegion, 0);
|
|
||||||
|
|
||||||
setChartColor(stroke, fill, fillSecondary);
|
|
||||||
setSafeRegion(safeRegion);
|
|
||||||
setWillNotDraw(false);
|
|
||||||
|
|
||||||
a.recycle();
|
|
||||||
|
|
||||||
mPathStroke = new Path();
|
|
||||||
mPathFill = new Path();
|
|
||||||
mPathEstimate = new Path();
|
|
||||||
}
|
|
||||||
|
|
||||||
void init(ChartAxis horiz, ChartAxis vert) {
|
|
||||||
mHoriz = Preconditions.checkNotNull(horiz, "missing horiz");
|
|
||||||
mVert = Preconditions.checkNotNull(vert, "missing vert");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChartColor(int stroke, int fill, int fillSecondary) {
|
|
||||||
mPaintStroke = new Paint();
|
|
||||||
mPaintStroke.setStrokeWidth(4.0f * getResources().getDisplayMetrics().density);
|
|
||||||
mPaintStroke.setColor(stroke);
|
|
||||||
mPaintStroke.setStyle(Style.STROKE);
|
|
||||||
mPaintStroke.setAntiAlias(true);
|
|
||||||
|
|
||||||
mPaintFill = new Paint();
|
|
||||||
mPaintFill.setColor(fill);
|
|
||||||
mPaintFill.setStyle(Style.FILL);
|
|
||||||
mPaintFill.setAntiAlias(true);
|
|
||||||
|
|
||||||
mPaintFillSecondary = new Paint();
|
|
||||||
mPaintFillSecondary.setColor(fillSecondary);
|
|
||||||
mPaintFillSecondary.setStyle(Style.FILL);
|
|
||||||
mPaintFillSecondary.setAntiAlias(true);
|
|
||||||
|
|
||||||
mPaintEstimate = new Paint();
|
|
||||||
mPaintEstimate.setStrokeWidth(3.0f);
|
|
||||||
mPaintEstimate.setColor(fillSecondary);
|
|
||||||
mPaintEstimate.setStyle(Style.STROKE);
|
|
||||||
mPaintEstimate.setAntiAlias(true);
|
|
||||||
mPaintEstimate.setPathEffect(new DashPathEffect(new float[] { 10, 10 }, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSafeRegion(int safeRegion) {
|
|
||||||
mSafeRegion = safeRegion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bindNetworkStats(NetworkStatsHistory stats) {
|
|
||||||
mStats = stats;
|
|
||||||
invalidatePath();
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBounds(long start, long end) {
|
|
||||||
mStart = start;
|
|
||||||
mEnd = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSecondary(boolean secondary) {
|
|
||||||
mSecondary = secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void invalidatePath() {
|
|
||||||
mPathValid = false;
|
|
||||||
mMax = 0;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erase any existing {@link Path} and generate series outline based on
|
|
||||||
* currently bound {@link NetworkStatsHistory} data.
|
|
||||||
*/
|
|
||||||
private void generatePath() {
|
|
||||||
if (LOGD) Log.d(TAG, "generatePath()");
|
|
||||||
|
|
||||||
mMax = 0;
|
|
||||||
mPathStroke.reset();
|
|
||||||
mPathFill.reset();
|
|
||||||
mPathEstimate.reset();
|
|
||||||
mPathValid = true;
|
|
||||||
|
|
||||||
// bail when not enough stats to render
|
|
||||||
if (mStats == null || mStats.size() < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int width = getWidth();
|
|
||||||
final int height = getHeight();
|
|
||||||
|
|
||||||
boolean started = false;
|
|
||||||
float lastX = 0;
|
|
||||||
float lastY = height;
|
|
||||||
long lastTime = mHoriz.convertToValue(lastX);
|
|
||||||
|
|
||||||
// move into starting position
|
|
||||||
mPathStroke.moveTo(lastX, lastY);
|
|
||||||
mPathFill.moveTo(lastX, lastY);
|
|
||||||
|
|
||||||
// TODO: count fractional data from first bucket crossing start;
|
|
||||||
// currently it only accepts first full bucket.
|
|
||||||
|
|
||||||
long totalData = 0;
|
|
||||||
|
|
||||||
NetworkStatsHistory.Entry entry = null;
|
|
||||||
|
|
||||||
final int start = mStats.getIndexBefore(mStart);
|
|
||||||
final int end = mStats.getIndexAfter(mEnd);
|
|
||||||
for (int i = start; i <= end; i++) {
|
|
||||||
entry = mStats.getValues(i, entry);
|
|
||||||
|
|
||||||
final long startTime = entry.bucketStart;
|
|
||||||
final long endTime = startTime + entry.bucketDuration;
|
|
||||||
|
|
||||||
final float startX = mHoriz.convertToPoint(startTime);
|
|
||||||
final float endX = mHoriz.convertToPoint(endTime);
|
|
||||||
|
|
||||||
// skip until we find first stats on screen
|
|
||||||
if (endX < 0) continue;
|
|
||||||
|
|
||||||
// increment by current bucket total
|
|
||||||
totalData += entry.rxBytes + entry.txBytes;
|
|
||||||
|
|
||||||
final float startY = lastY;
|
|
||||||
final float endY = mVert.convertToPoint(totalData);
|
|
||||||
|
|
||||||
if (lastTime != startTime) {
|
|
||||||
// gap in buckets; line to start of current bucket
|
|
||||||
mPathStroke.lineTo(startX, startY);
|
|
||||||
mPathFill.lineTo(startX, startY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// always draw to end of current bucket
|
|
||||||
mPathStroke.lineTo(endX, endY);
|
|
||||||
mPathFill.lineTo(endX, endY);
|
|
||||||
|
|
||||||
lastX = endX;
|
|
||||||
lastY = endY;
|
|
||||||
lastTime = endTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// when data falls short, extend to requested end time
|
|
||||||
if (lastTime < mEndTime) {
|
|
||||||
lastX = mHoriz.convertToPoint(mEndTime);
|
|
||||||
|
|
||||||
mPathStroke.lineTo(lastX, lastY);
|
|
||||||
mPathFill.lineTo(lastX, lastY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOGD) {
|
|
||||||
final RectF bounds = new RectF();
|
|
||||||
mPathFill.computeBounds(bounds, true);
|
|
||||||
Log.d(TAG, "onLayout() rendered with bounds=" + bounds.toString() + " and totalData="
|
|
||||||
+ totalData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// drop to bottom of graph from current location
|
|
||||||
mPathFill.lineTo(lastX, height);
|
|
||||||
mPathFill.lineTo(0, height);
|
|
||||||
|
|
||||||
mMax = totalData;
|
|
||||||
|
|
||||||
if (ESTIMATE_ENABLED) {
|
|
||||||
// build estimated data
|
|
||||||
mPathEstimate.moveTo(lastX, lastY);
|
|
||||||
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
final long bucketDuration = mStats.getBucketDuration();
|
|
||||||
|
|
||||||
// long window is average over two weeks
|
|
||||||
entry = mStats.getValues(lastTime - WEEK_IN_MILLIS * 2, lastTime, now, entry);
|
|
||||||
final long longWindow = (entry.rxBytes + entry.txBytes) * bucketDuration
|
|
||||||
/ entry.bucketDuration;
|
|
||||||
|
|
||||||
long futureTime = 0;
|
|
||||||
while (lastX < width) {
|
|
||||||
futureTime += bucketDuration;
|
|
||||||
|
|
||||||
// short window is day average last week
|
|
||||||
final long lastWeekTime = lastTime - WEEK_IN_MILLIS + (futureTime % WEEK_IN_MILLIS);
|
|
||||||
entry = mStats.getValues(lastWeekTime - DAY_IN_MILLIS, lastWeekTime, now, entry);
|
|
||||||
final long shortWindow = (entry.rxBytes + entry.txBytes) * bucketDuration
|
|
||||||
/ entry.bucketDuration;
|
|
||||||
|
|
||||||
totalData += (longWindow * 7 + shortWindow * 3) / 10;
|
|
||||||
|
|
||||||
lastX = mHoriz.convertToPoint(lastTime + futureTime);
|
|
||||||
lastY = mVert.convertToPoint(totalData);
|
|
||||||
|
|
||||||
mPathEstimate.lineTo(lastX, lastY);
|
|
||||||
}
|
|
||||||
|
|
||||||
mMaxEstimate = totalData;
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEndTime(long endTime) {
|
|
||||||
mEndTime = endTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEstimateVisible(boolean estimateVisible) {
|
|
||||||
mEstimateVisible = ESTIMATE_ENABLED ? estimateVisible : false;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMaxEstimate() {
|
|
||||||
return mMaxEstimate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMaxVisible() {
|
|
||||||
final long maxVisible = mEstimateVisible ? mMaxEstimate : mMax;
|
|
||||||
if (maxVisible <= 0 && mStats != null) {
|
|
||||||
// haven't generated path yet; fall back to raw data
|
|
||||||
final NetworkStatsHistory.Entry entry = mStats.getValues(mStart, mEnd, null);
|
|
||||||
return entry.rxBytes + entry.txBytes;
|
|
||||||
} else {
|
|
||||||
return maxVisible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas) {
|
|
||||||
int save;
|
|
||||||
|
|
||||||
if (!mPathValid) {
|
|
||||||
generatePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mEstimateVisible) {
|
|
||||||
save = canvas.save();
|
|
||||||
canvas.clipRect(0, 0, getWidth(), getHeight());
|
|
||||||
canvas.drawPath(mPathEstimate, mPaintEstimate);
|
|
||||||
canvas.restoreToCount(save);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Paint paintFill = mSecondary ? mPaintFillSecondary : mPaintFill;
|
|
||||||
|
|
||||||
save = canvas.save();
|
|
||||||
canvas.clipRect(mSafeRegion, 0, getWidth(), getHeight() - mSafeRegion);
|
|
||||||
canvas.drawPath(mPathFill, paintFill);
|
|
||||||
canvas.restoreToCount(save);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -30,8 +30,8 @@ import com.android.settings.R;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Container for two-dimensional chart, drawn with a combination of
|
* Container for two-dimensional chart, drawn with a combination of
|
||||||
* {@link ChartGridView}, {@link ChartNetworkSeriesView} and {@link ChartSweepView}
|
* {@link ChartGridView} and {@link ChartSweepView} children. The entire chart uses
|
||||||
* children. The entire chart uses {@link ChartAxis} to map between raw values
|
* {@link ChartAxis} to map between raw values
|
||||||
* and screen coordinates.
|
* and screen coordinates.
|
||||||
*/
|
*/
|
||||||
public class ChartView extends FrameLayout {
|
public class ChartView extends FrameLayout {
|
||||||
@@ -112,13 +112,7 @@ public class ChartView extends FrameLayout {
|
|||||||
|
|
||||||
parentRect.set(mContent);
|
parentRect.set(mContent);
|
||||||
|
|
||||||
if (child instanceof ChartNetworkSeriesView) {
|
if (child instanceof ChartGridView) {
|
||||||
// series are always laid out to fill entire graph area
|
|
||||||
// TODO: handle scrolling for series larger than content area
|
|
||||||
Gravity.apply(params.gravity, width, height, parentRect, childRect);
|
|
||||||
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
|
||||||
|
|
||||||
} else if (child instanceof ChartGridView) {
|
|
||||||
// Grid uses some extra room for labels
|
// Grid uses some extra room for labels
|
||||||
Gravity.apply(params.gravity, width, height, parentRect, childRect);
|
Gravity.apply(params.gravity, width, height, parentRect, childRect);
|
||||||
child.layout(childRect.left, childRect.top, childRect.right,
|
child.layout(childRect.left, childRect.top, childRect.right,
|
||||||
|
Reference in New Issue
Block a user