summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/settings/DataUsageSummary.java185
-rw-r--r--src/com/android/settings/net/NetworkPolicyEditor.java85
-rw-r--r--src/com/android/settings/widget/ChartDataUsageView.java (renamed from src/com/android/settings/widget/DataUsageChartView.java)59
-rw-r--r--src/com/android/settings/widget/ChartSweepView.java112
-rw-r--r--src/com/android/settings/widget/ChartView.java54
5 files changed, 358 insertions, 137 deletions
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index a6170c7..eb74788 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -122,12 +122,13 @@ import android.widget.TextView;
import com.android.internal.telephony.Phone;
import com.android.settings.net.NetworkPolicyEditor;
import com.android.settings.net.SummaryForAllUidLoader;
-import com.android.settings.widget.DataUsageChartView;
-import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
+import com.android.settings.widget.ChartDataUsageView;
+import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
import com.android.settings.widget.PieChartView;
import com.google.android.collect.Lists;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
@@ -195,7 +196,7 @@ public class DataUsageSummary extends Fragment {
private Spinner mCycleSpinner;
private CycleAdapter mCycleAdapter;
- private DataUsageChartView mChart;
+ private ChartDataUsageView mChart;
private TextView mUsageSummary;
private TextView mEmpty;
@@ -216,8 +217,7 @@ public class DataUsageSummary extends Fragment {
private NetworkTemplate mTemplate = null;
- private static final int UID_NONE = -1;
- private int mUid = UID_NONE;
+ private int[] mAppDetailUids = null;
private Intent mAppSettingsIntent;
@@ -307,7 +307,7 @@ public class DataUsageSummary extends Fragment {
mCycleSpinner.setAdapter(mCycleAdapter);
mCycleSpinner.setOnItemSelectedListener(mCycleListener);
- mChart = (DataUsageChartView) mHeader.findViewById(R.id.chart);
+ mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
mChart.setListener(mChartListener);
{
@@ -611,8 +611,9 @@ public class DataUsageSummary extends Fragment {
mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
} else if (TAB_WIFI.equals(currentTab)) {
+ // wifi doesn't have any controls
mDataEnabledView.setVisibility(View.GONE);
- setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_wifi_limit);
+ mDisableAtLimitView.setVisibility(View.GONE);
mTemplate = buildTemplateWifi();
} else if (TAB_ETHERNET.equals(currentTab)) {
@@ -649,12 +650,16 @@ public class DataUsageSummary extends Fragment {
}
private boolean isAppDetailMode() {
- return mUid != UID_NONE;
+ return mAppDetailUids != null;
+ }
+
+ private int getAppDetailPrimaryUid() {
+ return mAppDetailUids[0];
}
/**
- * Update UID details panels to match {@link #mUid}, showing or hiding them
- * depending on {@link #isAppDetailMode()}.
+ * Update UID details panels to match {@link #mAppDetailUids}, showing or
+ * hiding them depending on {@link #isAppDetailMode()}.
*/
private void updateAppDetail() {
final Context context = getActivity();
@@ -681,7 +686,8 @@ public class DataUsageSummary extends Fragment {
mChart.bindNetworkPolicy(null);
// show icon and all labels appearing under this app
- final UidDetail detail = resolveDetailForUid(context, mUid);
+ final int primaryUid = getAppDetailPrimaryUid();
+ final UidDetail detail = resolveDetailForUid(context, primaryUid);
mAppIcon.setImageDrawable(detail.icon);
mAppTitles.removeAllViews();
@@ -695,7 +701,7 @@ public class DataUsageSummary extends Fragment {
// enable settings button when package provides it
// TODO: target torwards entire UID instead of just first package
- final String[] packageNames = pm.getPackagesForUid(mUid);
+ final String[] packageNames = pm.getPackagesForUid(primaryUid);
if (packageNames != null && packageNames.length > 0) {
mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
mAppSettingsIntent.setPackage(packageNames[0]);
@@ -709,12 +715,40 @@ public class DataUsageSummary extends Fragment {
mAppSettings.setEnabled(false);
}
+ updateDetailHistory();
+ updateDetailData();
+
+ if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid)
+ && !getRestrictBackground() && isBandwidthControlEnabled()) {
+ setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
+ setPreferenceSummary(mAppRestrictView,
+ getString(R.string.data_usage_app_restrict_background_summary,
+ buildLimitedNetworksList()));
+
+ mAppRestrictView.setVisibility(View.VISIBLE);
+ mAppRestrict.setChecked(getAppRestrictBackground());
+
+ } else {
+ mAppRestrictView.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Update {@link #mDetailHistory} and related values based on
+ * {@link #mAppDetailUids}.
+ */
+ private void updateDetailHistory() {
try {
+ mDetailHistoryDefault = null;
+ mDetailHistoryForeground = null;
+
// load stats for current uid and template
- mDetailHistoryDefault = mStatsService.getHistoryForUid(
- mTemplate, mUid, SET_DEFAULT, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
- mDetailHistoryForeground = mStatsService.getHistoryForUid(
- mTemplate, mUid, SET_FOREGROUND, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
+ for (int uid : mAppDetailUids) {
+ mDetailHistoryDefault = collectHistoryForUid(
+ uid, SET_DEFAULT, mDetailHistoryDefault);
+ mDetailHistoryForeground = collectHistoryForUid(
+ uid, SET_FOREGROUND, mDetailHistoryForeground);
+ }
} catch (RemoteException e) {
// since we can't do much without history, and we don't want to
// leave with half-baked UI, we bail hard.
@@ -727,23 +761,24 @@ public class DataUsageSummary extends Fragment {
// bind chart to historical stats
mChart.bindDetailNetworkStats(mDetailHistory);
+ }
- updateDetailData();
-
- if (NetworkPolicyManager.isUidValidForPolicy(context, mUid) && !getRestrictBackground()
- && isBandwidthControlEnabled()) {
- setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
- setPreferenceSummary(mAppRestrictView,
- getString(R.string.data_usage_app_restrict_background_summary,
- buildLimitedNetworksList()));
-
- mAppRestrictView.setVisibility(View.VISIBLE);
- mAppRestrict.setChecked(getAppRestrictBackground());
-
+ /**
+ * Collect {@link NetworkStatsHistory} for the requested UID, combining with
+ * an existing {@link NetworkStatsHistory} if provided.
+ */
+ private NetworkStatsHistory collectHistoryForUid(
+ int uid, int set, NetworkStatsHistory existing)
+ throws RemoteException {
+ final NetworkStatsHistory history = mStatsService.getHistoryForUid(
+ mTemplate, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
+
+ if (existing != null) {
+ existing.recordEntireHistory(history);
+ return existing;
} else {
- mAppRestrictView.setVisibility(View.GONE);
+ return history;
}
-
}
private void setPolicyCycleDay(int cycleDay) {
@@ -764,8 +799,8 @@ public class DataUsageSummary extends Fragment {
updatePolicy(false);
}
- private boolean isNetworkPolicyModifiable() {
- return isBandwidthControlEnabled() && mDataEnabled.isChecked();
+ private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
+ return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked();
}
private boolean isBandwidthControlEnabled() {
@@ -810,9 +845,10 @@ public class DataUsageSummary extends Fragment {
}
private boolean getAppRestrictBackground() {
+ final int primaryUid = getAppDetailPrimaryUid();
final int uidPolicy;
try {
- uidPolicy = mPolicyService.getUidPolicy(mUid);
+ uidPolicy = mPolicyService.getUidPolicy(primaryUid);
} catch (RemoteException e) {
// since we can't do much without policy, we bail hard.
throw new RuntimeException("problem reading network policy", e);
@@ -823,9 +859,10 @@ public class DataUsageSummary extends Fragment {
private void setAppRestrictBackground(boolean restrictBackground) {
if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
+ final int primaryUid = getAppDetailPrimaryUid();
try {
- mPolicyService.setUidPolicy(
- mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
+ mPolicyService.setUidPolicy(primaryUid,
+ restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
} catch (RemoteException e) {
throw new RuntimeException("unable to save policy", e);
}
@@ -851,8 +888,8 @@ public class DataUsageSummary extends Fragment {
}
}
- final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate, true);
- if (isNetworkPolicyModifiable()) {
+ final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
+ if (isNetworkPolicyModifiable(policy)) {
mDisableAtLimitView.setVisibility(View.VISIBLE);
mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
if (!isAppDetailMode()) {
@@ -912,19 +949,28 @@ public class DataUsageSummary extends Fragment {
}
// one last cycle entry to modify policy cycle day
- mCycleAdapter.setChangePossible(isNetworkPolicyModifiable());
+ mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy));
}
if (!hasCycles) {
- // no valid cycles; show all data
- // TODO: offer simple ranges like "last week" etc
- mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
+ // no policy defined cycles; show entry for each four-week period
+ long cycleEnd = historyEnd;
+ while (cycleEnd > historyStart) {
+ final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
+ mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
+ cycleEnd = cycleStart;
+ }
+
mCycleAdapter.setChangePossible(false);
}
// force pick the current cycle (first item)
- mCycleSpinner.setSelection(0);
- mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
+ if (mCycleAdapter.getCount() > 0) {
+ mCycleSpinner.setSelection(0);
+ mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
+ } else {
+ updateDetailData();
+ }
}
private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
@@ -985,8 +1031,10 @@ public class DataUsageSummary extends Fragment {
private OnItemClickListener mListListener = new OnItemClickListener() {
/** {@inheritDoc} */
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ final Context context = view.getContext();
final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
- AppDetailsFragment.show(DataUsageSummary.this, app.uid);
+ final UidDetail detail = resolveDetailForUid(context, app.uids[0]);
+ AppDetailsFragment.show(DataUsageSummary.this, app.uids, detail.label);
}
};
@@ -1065,6 +1113,7 @@ public class DataUsageSummary extends Fragment {
entry = mHistory.getValues(start, end, now, null);
// kick off loader for detailed stats
+ // TODO: delay loader until animation is finished
getLoaderManager().restartLoader(LOADER_SUMMARY,
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
}
@@ -1222,9 +1271,20 @@ public class DataUsageSummary extends Fragment {
}
private static class AppUsageItem implements Comparable<AppUsageItem> {
- public int uid;
+ public int[] uids;
public long total;
+ public AppUsageItem(int uid) {
+ uids = new int[] { uid };
+ }
+
+ public void addUid(int uid) {
+ if (contains(uids, uid)) return;
+ final int length = uids.length;
+ uids = Arrays.copyOf(uids, length + 1);
+ uids[length] = uid;
+ }
+
/** {@inheritDoc} */
public int compareTo(AppUsageItem another) {
return Long.compare(another.total, total);
@@ -1244,9 +1304,7 @@ public class DataUsageSummary extends Fragment {
public void bindStats(NetworkStats stats) {
mItems.clear();
- final AppUsageItem systemItem = new AppUsageItem();
- systemItem.uid = android.os.Process.SYSTEM_UID;
-
+ final AppUsageItem systemItem = new AppUsageItem(android.os.Process.SYSTEM_UID);
final SparseArray<AppUsageItem> knownUids = new SparseArray<AppUsageItem>();
NetworkStats.Entry entry = null;
@@ -1260,8 +1318,7 @@ public class DataUsageSummary extends Fragment {
if (isApp || uid == TrafficStats.UID_REMOVED) {
AppUsageItem item = knownUids.get(uid);
if (item == null) {
- item = new AppUsageItem();
- item.uid = uid;
+ item = new AppUsageItem(uid);
knownUids.put(uid, item);
mItems.add(item);
}
@@ -1269,6 +1326,7 @@ public class DataUsageSummary extends Fragment {
item.total += entry.rxBytes + entry.txBytes;
} else {
systemItem.total += entry.rxBytes + entry.txBytes;
+ systemItem.addUid(uid);
}
}
@@ -1293,7 +1351,7 @@ public class DataUsageSummary extends Fragment {
@Override
public long getItemId(int position) {
- return mItems.get(position).uid;
+ return mItems.get(position).uids[0];
}
@Override
@@ -1312,7 +1370,7 @@ public class DataUsageSummary extends Fragment {
android.R.id.progress);
final AppUsageItem item = mItems.get(position);
- final UidDetail detail = resolveDetailForUid(context, item.uid);
+ final UidDetail detail = resolveDetailForUid(context, item.uids[0]);
icon.setImageDrawable(detail.icon);
title.setText(detail.label);
@@ -1323,7 +1381,6 @@ public class DataUsageSummary extends Fragment {
return convertView;
}
-
}
/**
@@ -1331,11 +1388,11 @@ public class DataUsageSummary extends Fragment {
* {@link DataUsageSummary}.
*/
public static class AppDetailsFragment extends Fragment {
- private static final String EXTRA_UID = "uid";
+ private static final String EXTRA_UIDS = "uids";
- public static void show(DataUsageSummary parent, int uid) {
+ public static void show(DataUsageSummary parent, int[] uids, CharSequence label) {
final Bundle args = new Bundle();
- args.putInt(EXTRA_UID, uid);
+ args.putIntArray(EXTRA_UIDS, uids);
final AppDetailsFragment fragment = new AppDetailsFragment();
fragment.setArguments(args);
@@ -1344,6 +1401,7 @@ public class DataUsageSummary extends Fragment {
final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
ft.add(fragment, TAG_APP_DETAILS);
ft.addToBackStack(TAG_APP_DETAILS);
+ ft.setBreadCrumbTitle(label);
ft.commit();
}
@@ -1351,7 +1409,7 @@ public class DataUsageSummary extends Fragment {
public void onStart() {
super.onStart();
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- target.mUid = getArguments().getInt(EXTRA_UID);
+ target.mAppDetailUids = getArguments().getIntArray(EXTRA_UIDS);
target.updateBody();
}
@@ -1359,7 +1417,7 @@ public class DataUsageSummary extends Fragment {
public void onStop() {
super.onStop();
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- target.mUid = UID_NONE;
+ target.mAppDetailUids = null;
target.updateBody();
}
}
@@ -1441,7 +1499,7 @@ public class DataUsageSummary extends Fragment {
private static final String EXTRA_CYCLE_DAY = "cycleDay";
public static void show(DataUsageSummary parent) {
- final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate, false);
+ final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
final Bundle args = new Bundle();
args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
@@ -1807,4 +1865,13 @@ public class DataUsageSummary extends Fragment {
summary.setVisibility(View.VISIBLE);
summary.setText(string);
}
+
+ private static boolean contains(int[] haystack, int needle) {
+ for (int value : haystack) {
+ if (value == needle) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java
index 723c5cc..161e6ee 100644
--- a/src/com/android/settings/net/NetworkPolicyEditor.java
+++ b/src/com/android/settings/net/NetworkPolicyEditor.java
@@ -21,6 +21,7 @@ import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
import static android.net.NetworkTemplate.buildTemplateMobile4g;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
@@ -40,7 +41,7 @@ import java.util.ArrayList;
/**
* Utility class to modify list of {@link NetworkPolicy}. Specifically knows
- * about which policies can coexist.
+ * about which policies can coexist. Not thread safe.
*/
public class NetworkPolicyEditor {
// TODO: be more robust when missing policies from service
@@ -53,64 +54,80 @@ public class NetworkPolicyEditor {
}
public void read() {
+ final NetworkPolicy[] policies;
try {
- final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies();
- mPolicies.clear();
- for (NetworkPolicy policy : policies) {
- // TODO: find better place to clamp these
- if (policy.limitBytes < -1) {
- policy.limitBytes = LIMIT_DISABLED;
- }
- if (policy.warningBytes < -1) {
- policy.warningBytes = WARNING_DISABLED;
- }
-
- mPolicies.add(policy);
- }
+ policies = mPolicyService.getNetworkPolicies();
} catch (RemoteException e) {
throw new RuntimeException("problem reading policies", e);
}
+
+ boolean modified = false;
+ mPolicies.clear();
+ for (NetworkPolicy policy : policies) {
+ // TODO: find better place to clamp these
+ if (policy.limitBytes < -1) {
+ policy.limitBytes = LIMIT_DISABLED;
+ modified = true;
+ }
+ if (policy.warningBytes < -1) {
+ policy.warningBytes = WARNING_DISABLED;
+ modified = true;
+ }
+
+ // drop any WIFI policies that were defined
+ if (policy.template.getMatchRule() == MATCH_WIFI) {
+ modified = true;
+ continue;
+ }
+
+ mPolicies.add(policy);
+ }
+
+ // when we cleaned policies above, write back changes
+ if (modified) writeAsync();
}
public void writeAsync() {
// TODO: consider making more robust by passing through service
+ final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
- write();
+ write(policies);
return null;
}
}.execute();
}
- public void write() {
+ public void write(NetworkPolicy[] policies) {
try {
- final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
mPolicyService.setNetworkPolicies(policies);
} catch (RemoteException e) {
- throw new RuntimeException("problem reading policies", e);
+ throw new RuntimeException("problem writing policies", e);
}
}
public boolean hasLimitedPolicy(NetworkTemplate template) {
- final NetworkPolicy policy = getPolicy(template, false);
+ final NetworkPolicy policy = getPolicy(template);
return policy != null && policy.limitBytes != LIMIT_DISABLED;
}
- public NetworkPolicy getPolicy(NetworkTemplate template, boolean createDefault) {
+ public NetworkPolicy getOrCreatePolicy(NetworkTemplate template) {
+ NetworkPolicy policy = getPolicy(template);
+ if (policy == null) {
+ policy = buildDefaultPolicy(template);
+ mPolicies.add(policy);
+ }
+ return policy;
+ }
+
+ public NetworkPolicy getPolicy(NetworkTemplate template) {
for (NetworkPolicy policy : mPolicies) {
if (policy.template.equals(template)) {
return policy;
}
}
-
- if (createDefault) {
- final NetworkPolicy policy = buildDefaultPolicy(template);
- mPolicies.add(policy);
- return policy;
- } else {
- return null;
- }
+ return null;
}
private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) {
@@ -124,21 +141,21 @@ public class NetworkPolicyEditor {
}
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
- final NetworkPolicy policy = getPolicy(template, true);
+ final NetworkPolicy policy = getOrCreatePolicy(template);
policy.cycleDay = cycleDay;
policy.lastSnooze = SNOOZE_NEVER;
writeAsync();
}
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
- final NetworkPolicy policy = getPolicy(template, true);
+ final NetworkPolicy policy = getOrCreatePolicy(template);
policy.warningBytes = warningBytes;
policy.lastSnooze = SNOOZE_NEVER;
writeAsync();
}
public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
- final NetworkPolicy policy = getPolicy(template, true);
+ final NetworkPolicy policy = getOrCreatePolicy(template);
policy.limitBytes = limitBytes;
policy.lastSnooze = SNOOZE_NEVER;
writeAsync();
@@ -176,8 +193,8 @@ public class NetworkPolicyEditor {
} else if (beforeSplit && !split) {
// combine, picking most restrictive policy
- final NetworkPolicy policy3g = getPolicy(template3g, false);
- final NetworkPolicy policy4g = getPolicy(template4g, false);
+ final NetworkPolicy policy3g = getPolicy(template3g);
+ final NetworkPolicy policy4g = getPolicy(template4g);
final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g
: policy4g;
@@ -190,7 +207,7 @@ public class NetworkPolicyEditor {
} else if (!beforeSplit && split) {
// duplicate existing policy into two rules
- final NetworkPolicy policyAll = getPolicy(templateAll, false);
+ final NetworkPolicy policyAll = getPolicy(templateAll);
mPolicies.remove(policyAll);
mPolicies.add(
new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.warningBytes,
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/ChartDataUsageView.java
index f6ae5a0..9554368 100644
--- a/src/com/android/settings/widget/DataUsageChartView.java
+++ b/src/com/android/settings/widget/ChartDataUsageView.java
@@ -27,6 +27,7 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -37,7 +38,7 @@ import com.android.settings.widget.ChartSweepView.OnSweepListener;
* Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along
* with {@link ChartSweepView} for inspection ranges and warning/limits.
*/
-public class DataUsageChartView extends ChartView {
+public class ChartDataUsageView extends ChartView {
private static final long KB_IN_BYTES = 1024;
private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
@@ -46,6 +47,8 @@ public class DataUsageChartView extends ChartView {
private static final int MSG_UPDATE_AXIS = 100;
private static final long DELAY_MILLIS = 250;
+ private static final boolean LIMIT_SWEEPS_TO_VALID_DATA = false;
+
private ChartGridView mGrid;
private ChartNetworkSeriesView mSeries;
private ChartNetworkSeriesView mDetailSeries;
@@ -70,15 +73,15 @@ public class DataUsageChartView extends ChartView {
private DataUsageChartListener mListener;
- public DataUsageChartView(Context context) {
+ public ChartDataUsageView(Context context) {
this(context, null, 0);
}
- public DataUsageChartView(Context context, AttributeSet attrs) {
+ public ChartDataUsageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public DataUsageChartView(Context context, AttributeSet attrs, int defStyle) {
+ public ChartDataUsageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
@@ -186,6 +189,7 @@ public class DataUsageChartView extends ChartView {
updateVertAxisBounds(null);
requestLayout();
+ invalidate();
}
/**
@@ -194,7 +198,8 @@ public class DataUsageChartView extends ChartView {
*/
private void updateVertAxisBounds(ChartSweepView activeSweep) {
final long max = mVertMax;
- final long newMax;
+
+ long newMax = 0;
if (activeSweep != null) {
final int adjustAxis = activeSweep.shouldAdjustAxis();
if (adjustAxis > 0) {
@@ -206,14 +211,14 @@ public class DataUsageChartView extends ChartView {
} 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);
}
+ // always show known data and policy lines
+ final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
+ final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
+ final long maxDefault = Math.max(maxVisible, 2 * GB_IN_BYTES);
+ newMax = Math.max(maxDefault, newMax);
+
// only invalidate when vertMax actually changed
if (newMax != mVertMax) {
mVertMax = newMax;
@@ -231,6 +236,16 @@ public class DataUsageChartView extends ChartView {
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);
+ }
}
}
@@ -346,9 +361,14 @@ public class DataUsageChartView extends ChartView {
final long validStart = Math.max(visibleStart, getStatsStart());
final long validEnd = Math.min(visibleEnd, getStatsEnd());
- // prevent time sweeps from leaving valid data
- mSweepLeft.setValidRange(validStart, validEnd);
- mSweepRight.setValidRange(validStart, validEnd);
+ if (LIMIT_SWEEPS_TO_VALID_DATA) {
+ // prevent time sweeps from leaving valid data
+ mSweepLeft.setValidRange(validStart, validEnd);
+ mSweepRight.setValidRange(validStart, validEnd);
+ } else {
+ mSweepLeft.setValidRange(visibleStart, visibleEnd);
+ mSweepRight.setValidRange(visibleStart, visibleEnd);
+ }
// default sweeps to last week of data
final long halfRange = (visibleEnd + visibleStart) / 2;
@@ -424,7 +444,7 @@ public class DataUsageChartView extends ChartView {
final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL);
final float[] tickPoints = new float[tickCount];
for (int i = 0; i < tickCount; i++) {
- tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * i));
+ tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * (i + 1)));
}
return tickPoints;
}
@@ -501,7 +521,14 @@ public class DataUsageChartView extends ChartView {
/** {@inheritDoc} */
public float[] getTickPoints() {
final long range = mMax - mMin;
- final long tickJump = 256 * MB_IN_BYTES;
+ final long tickJump;
+ if (range < 6 * GB_IN_BYTES) {
+ tickJump = 256 * MB_IN_BYTES;
+ } else if (range < 12 * GB_IN_BYTES) {
+ tickJump = 512 * MB_IN_BYTES;
+ } else {
+ tickJump = 1 * GB_IN_BYTES;
+ }
final int tickCount = (int) (range / tickJump);
final float[] tickPoints = new float[tickCount];
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 81aeb84..0d91a76 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -21,6 +21,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Paint.Style;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -42,8 +43,14 @@ import com.google.common.base.Preconditions;
*/
public class ChartSweepView extends View {
+ private static final boolean DRAW_OUTLINE = false;
+
private Drawable mSweep;
private Rect mSweepPadding = new Rect();
+
+ /** Offset of content inside this view. */
+ private Point mContentOffset = new Point();
+ /** Offset of {@link #mSweep} inside this view. */
private Point mSweepOffset = new Point();
private Rect mMargins = new Rect();
@@ -66,6 +73,8 @@ public class ChartSweepView extends View {
private ChartSweepView mValidAfterDynamic;
private ChartSweepView mValidBeforeDynamic;
+ private Paint mOutlinePaint = new Paint();
+
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
@@ -98,6 +107,10 @@ public class ChartSweepView extends View {
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
+ mOutlinePaint.setColor(Color.RED);
+ mOutlinePaint.setStrokeWidth(1f);
+ mOutlinePaint.setStyle(Style.STROKE);
+
a.recycle();
setWillNotDraw(false);
@@ -123,11 +136,11 @@ public class ChartSweepView extends View {
if (mFollowAxis == VERTICAL) {
final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
- mSweepPadding.bottom;
- return mSweepPadding.top + (targetHeight / 2);
+ return mSweepPadding.top + (targetHeight / 2) + mSweepOffset.y;
} else {
final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
- mSweepPadding.right;
- return mSweepPadding.left + (targetWidth / 2);
+ return mSweepPadding.left + (targetWidth / 2) + mSweepOffset.x;
}
}
@@ -195,6 +208,7 @@ public class ChartSweepView extends View {
paint.density = getResources().getDisplayMetrics().density;
paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
paint.setColor(mLabelColor);
+ paint.setShadowLayer(4 * paint.density, 0, 0, Color.BLACK);
mLabelTemplate = new SpannableStringBuilder(template);
mLabelLayout = new DynamicLayout(
@@ -283,6 +297,26 @@ public class ChartSweepView extends View {
mValidBeforeDynamic = validBefore;
}
+ /**
+ * Test if given {@link MotionEvent} is closer to another
+ * {@link ChartSweepView} compared to ourselves.
+ */
+ public boolean isTouchCloserTo(MotionEvent eventInParent, ChartSweepView another) {
+ if (another == null) return false;
+
+ if (mFollowAxis == HORIZONTAL) {
+ final float selfDist = Math.abs(eventInParent.getX() - (getX() + getTargetInset()));
+ final float anotherDist = Math.abs(
+ eventInParent.getX() - (another.getX() + another.getTargetInset()));
+ return anotherDist < selfDist;
+ } else {
+ final float selfDist = Math.abs(eventInParent.getY() - (getY() + getTargetInset()));
+ final float anotherDist = Math.abs(
+ eventInParent.getY() - (another.getY() + another.getTargetInset()));
+ return anotherDist < selfDist;
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) return false;
@@ -294,9 +328,18 @@ public class ChartSweepView extends View {
// only start tracking when in sweet spot
final boolean accept;
if (mFollowAxis == VERTICAL) {
- accept = event.getX() > getWidth() - (mSweepPadding.right * 2);
+ accept = event.getX() > getWidth() - (mSweepPadding.right * 3);
} else {
- accept = event.getY() > getHeight() - (mSweepPadding.bottom * 2);
+ accept = event.getY() > getHeight() - (mSweepPadding.bottom * 3);
+ }
+
+ final MotionEvent eventInParent = event.copy();
+ eventInParent.offsetLocation(getLeft(), getTop());
+
+ // ignore event when closer to a neighbor
+ if (isTouchCloserTo(eventInParent, mValidAfterDynamic)
+ || isTouchCloserTo(eventInParent, mValidBeforeDynamic)) {
+ return false;
}
if (accept) {
@@ -460,6 +503,7 @@ public class ChartSweepView extends View {
final int templateHeight = mLabelLayout.getHeight();
mSweepOffset.x = 0;
+ mSweepOffset.y = 0;
mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
@@ -485,6 +529,23 @@ public class ChartSweepView extends View {
mMargins.bottom = mSweepPadding.bottom;
}
+ mContentOffset.x = 0;
+ mContentOffset.y = 0;
+
+ // make touch target area larger
+ if (mFollowAxis == HORIZONTAL) {
+ final int widthBefore = getMeasuredWidth();
+ final int widthAfter = widthBefore * 3;
+ setMeasuredDimension(widthAfter, getMeasuredHeight());
+ mContentOffset.offset((widthAfter - widthBefore) / 2, 0);
+ } else {
+ final int heightBefore = getMeasuredHeight();
+ final int heightAfter = heightBefore * 3;
+ setMeasuredDimension(getMeasuredWidth(), heightAfter);
+ mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
+ }
+
+ mSweepOffset.offset(mContentOffset.x, mContentOffset.y);
mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
}
@@ -493,9 +554,43 @@ public class ChartSweepView extends View {
final int width = getWidth();
final int height = getHeight();
+ if (DRAW_OUTLINE) {
+ canvas.drawRect(0, 0, width, height, mOutlinePaint);
+ }
+
+ // when overlapping with neighbor, split difference and push label
+ float margin;
+ float labelOffset = 0;
+ if (mFollowAxis == VERTICAL) {
+ if (mValidAfterDynamic != null) {
+ margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
+ if (margin < 0) {
+ labelOffset = margin / 2;
+ }
+ } else if (mValidBeforeDynamic != null) {
+ margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
+ if (margin < 0) {
+ labelOffset = -margin / 2;
+ }
+ }
+ } else {
+ // TODO: implement horizontal labels
+ }
+
+ // when offsetting label, neighbor probably needs to offset too
+ if (labelOffset != 0) {
+ if (mValidAfterDynamic != null) mValidAfterDynamic.invalidate();
+ if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidate();
+ }
+
final int labelSize;
if (isEnabled() && mLabelLayout != null) {
- mLabelLayout.draw(canvas);
+ final int count = canvas.save();
+ {
+ canvas.translate(mContentOffset.x, mContentOffset.y + labelOffset);
+ mLabelLayout.draw(canvas);
+ }
+ canvas.restoreToCount(count);
labelSize = mLabelSize;
} else {
labelSize = 0;
@@ -512,4 +607,11 @@ public class ChartSweepView extends View {
mSweep.draw(canvas);
}
+ public static float getLabelTop(ChartSweepView view) {
+ return view.getY() + view.mContentOffset.y;
+ }
+
+ public static float getLabelBottom(ChartSweepView view) {
+ return getLabelTop(view) + view.mLabelLayout.getHeight();
+ }
}
diff --git a/src/com/android/settings/widget/ChartView.java b/src/com/android/settings/widget/ChartView.java
index e3a658a..f410d57 100644
--- a/src/com/android/settings/widget/ChartView.java
+++ b/src/com/android/settings/widget/ChartView.java
@@ -36,8 +36,6 @@ import com.android.settings.R;
* and screen coordinates.
*/
public class ChartView extends FrameLayout {
- private static final String TAG = "ChartView";
-
// TODO: extend something that supports two-dimensional scrolling
private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT;
@@ -122,29 +120,39 @@ public class ChartView extends FrameLayout {
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
} else if (child instanceof ChartSweepView) {
- // sweep is always placed along specific dimension
- final ChartSweepView sweep = (ChartSweepView) child;
- final Rect sweepMargins = sweep.getMargins();
-
- if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
- parentRect.top += sweepMargins.top + (int) sweep.getPoint();
- parentRect.bottom = parentRect.top;
- parentRect.left += sweepMargins.left;
- parentRect.right += sweepMargins.right;
- Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(),
- parentRect, childRect);
-
- } else {
- parentRect.left += sweepMargins.left + (int) sweep.getPoint();
- parentRect.right = parentRect.left;
- parentRect.top += sweepMargins.top;
- parentRect.bottom += sweepMargins.bottom;
- Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(),
- parentRect, childRect);
- }
+ layoutSweep((ChartSweepView) child, parentRect, childRect);
+ child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
}
+ }
+ }
+
+ protected void layoutSweep(ChartSweepView sweep) {
+ final Rect parentRect = new Rect(mContent);
+ final Rect childRect = new Rect();
+
+ layoutSweep(sweep, parentRect, childRect);
+ sweep.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+ }
- child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+ protected void layoutSweep(ChartSweepView sweep, Rect parentRect, Rect childRect) {
+ final Rect sweepMargins = sweep.getMargins();
+
+ // sweep is always placed along specific dimension
+ if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
+ parentRect.top += sweepMargins.top + (int) sweep.getPoint();
+ parentRect.bottom = parentRect.top;
+ parentRect.left += sweepMargins.left;
+ parentRect.right += sweepMargins.right;
+ Gravity.apply(SWEEP_GRAVITY, parentRect.width(), sweep.getMeasuredHeight(),
+ parentRect, childRect);
+
+ } else {
+ parentRect.left += sweepMargins.left + (int) sweep.getPoint();
+ parentRect.right = parentRect.left;
+ parentRect.top += sweepMargins.top;
+ parentRect.bottom += sweepMargins.bottom;
+ Gravity.apply(SWEEP_GRAVITY, sweep.getMeasuredWidth(), parentRect.height(),
+ parentRect, childRect);
}
}