diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/settings/DataUsageSummary.java | 185 | ||||
-rw-r--r-- | src/com/android/settings/net/NetworkPolicyEditor.java | 85 | ||||
-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.java | 112 | ||||
-rw-r--r-- | src/com/android/settings/widget/ChartView.java | 54 |
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); } } |