Merge "Data usage UI fixes; sweeps, combined history."

This commit is contained in:
Jeff Sharkey
2011-08-29 00:40:51 -07:00
committed by Android (Google) Code Review
6 changed files with 381 additions and 160 deletions

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -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];

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}
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);
}
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);
}
}