Data usage UI fixes; sweeps, combined history.

Fix sweep z-order so that limit are always above inspection range,
and draw shadows behind sweep labels.  Narrower margins for sweeps
with labels; push labels to keep from overlapping.

Generous touch targets on sweeps, and delegate touches to neighboring
sweep if nearer.  Refresh sweep layout during axis zoom, and don't
allow zoom below default minimum.  Let inspection sweeps move beyond
valid data ranges.  Draw less-frequent tick marks when working with
large axis ranges.

Remove Wi-Fi policies but continue showing historical data.  Write
NetworkPolicy if modified during read, and snapshot when async write
requested.

Handle combined UID histories for "Android OS."

Bug: 5191421, 5092579, 5225988, 5221101, 5221065, 5221005, 5150906, 5058025
Change-Id: Id51652e8a10bb90e1345f7a8af01bd70cb8ac677
This commit is contained in:
Jeff Sharkey
2011-08-27 17:09:43 -07:00
parent d66b61908d
commit 55d18a57e4
6 changed files with 381 additions and 160 deletions

View File

@@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<com.android.settings.widget.DataUsageChartView <com.android.settings.widget.ChartDataUsageView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings" xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:id="@+id/chart" android:id="@+id/chart"
@@ -55,28 +55,6 @@
settings:fillColor="#c0ba7f3e" settings:fillColor="#c0ba7f3e"
settings:fillColorSecondary="#60ba7f3e" /> settings:fillColorSecondary="#60ba7f3e" />
<com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
settings:sweepDrawable="@drawable/data_sweep_warning"
settings:followAxis="vertical"
settings:neighborMargin="40dip"
settings:labelSize="60dip"
settings:labelTemplate="@string/data_usage_sweep_warning"
settings:labelColor="#f7931d" />
<com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_limit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
settings:sweepDrawable="@drawable/data_sweep_limit"
settings:followAxis="vertical"
settings:neighborMargin="40dip"
settings:labelSize="60dip"
settings:labelTemplate="@string/data_usage_sweep_limit"
settings:labelColor="#c01a2c" />
<com.android.settings.widget.ChartSweepView <com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_left" android:id="@+id/sweep_left"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -93,4 +71,26 @@
settings:followAxis="horizontal" settings:followAxis="horizontal"
settings:neighborMargin="5dip" /> settings:neighborMargin="5dip" />
</com.android.settings.widget.DataUsageChartView> <com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
settings:sweepDrawable="@drawable/data_sweep_warning"
settings:followAxis="vertical"
settings:neighborMargin="5dip"
settings:labelSize="60dip"
settings:labelTemplate="@string/data_usage_sweep_warning"
settings:labelColor="#f7931d" />
<com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_limit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
settings:sweepDrawable="@drawable/data_sweep_limit"
settings:followAxis="vertical"
settings:neighborMargin="5dip"
settings:labelSize="60dip"
settings:labelTemplate="@string/data_usage_sweep_limit"
settings:labelColor="#c01a2c" />
</com.android.settings.widget.ChartDataUsageView>

View File

@@ -122,12 +122,13 @@ import android.widget.TextView;
import com.android.internal.telephony.Phone; import com.android.internal.telephony.Phone;
import com.android.settings.net.NetworkPolicyEditor; import com.android.settings.net.NetworkPolicyEditor;
import com.android.settings.net.SummaryForAllUidLoader; import com.android.settings.net.SummaryForAllUidLoader;
import com.android.settings.widget.DataUsageChartView; import com.android.settings.widget.ChartDataUsageView;
import com.android.settings.widget.DataUsageChartView.DataUsageChartListener; import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
import com.android.settings.widget.PieChartView; import com.android.settings.widget.PieChartView;
import com.google.android.collect.Lists; import com.google.android.collect.Lists;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Locale; import java.util.Locale;
@@ -195,7 +196,7 @@ public class DataUsageSummary extends Fragment {
private Spinner mCycleSpinner; private Spinner mCycleSpinner;
private CycleAdapter mCycleAdapter; private CycleAdapter mCycleAdapter;
private DataUsageChartView mChart; private ChartDataUsageView mChart;
private TextView mUsageSummary; private TextView mUsageSummary;
private TextView mEmpty; private TextView mEmpty;
@@ -216,8 +217,7 @@ public class DataUsageSummary extends Fragment {
private NetworkTemplate mTemplate = null; private NetworkTemplate mTemplate = null;
private static final int UID_NONE = -1; private int[] mAppDetailUids = null;
private int mUid = UID_NONE;
private Intent mAppSettingsIntent; private Intent mAppSettingsIntent;
@@ -307,7 +307,7 @@ public class DataUsageSummary extends Fragment {
mCycleSpinner.setAdapter(mCycleAdapter); mCycleSpinner.setAdapter(mCycleAdapter);
mCycleSpinner.setOnItemSelectedListener(mCycleListener); mCycleSpinner.setOnItemSelectedListener(mCycleListener);
mChart = (DataUsageChartView) mHeader.findViewById(R.id.chart); mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
mChart.setListener(mChartListener); mChart.setListener(mChartListener);
{ {
@@ -611,8 +611,9 @@ public class DataUsageSummary extends Fragment {
mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context)); mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
} else if (TAB_WIFI.equals(currentTab)) { } else if (TAB_WIFI.equals(currentTab)) {
// wifi doesn't have any controls
mDataEnabledView.setVisibility(View.GONE); mDataEnabledView.setVisibility(View.GONE);
setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_wifi_limit); mDisableAtLimitView.setVisibility(View.GONE);
mTemplate = buildTemplateWifi(); mTemplate = buildTemplateWifi();
} else if (TAB_ETHERNET.equals(currentTab)) { } else if (TAB_ETHERNET.equals(currentTab)) {
@@ -649,12 +650,16 @@ public class DataUsageSummary extends Fragment {
} }
private boolean isAppDetailMode() { 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 * Update UID details panels to match {@link #mAppDetailUids}, showing or
* depending on {@link #isAppDetailMode()}. * hiding them depending on {@link #isAppDetailMode()}.
*/ */
private void updateAppDetail() { private void updateAppDetail() {
final Context context = getActivity(); final Context context = getActivity();
@@ -681,7 +686,8 @@ public class DataUsageSummary extends Fragment {
mChart.bindNetworkPolicy(null); mChart.bindNetworkPolicy(null);
// show icon and all labels appearing under this app // 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); mAppIcon.setImageDrawable(detail.icon);
mAppTitles.removeAllViews(); mAppTitles.removeAllViews();
@@ -695,7 +701,7 @@ public class DataUsageSummary extends Fragment {
// enable settings button when package provides it // enable settings button when package provides it
// TODO: target torwards entire UID instead of just first package // 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) { if (packageNames != null && packageNames.length > 0) {
mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
mAppSettingsIntent.setPackage(packageNames[0]); mAppSettingsIntent.setPackage(packageNames[0]);
@@ -709,12 +715,40 @@ public class DataUsageSummary extends Fragment {
mAppSettings.setEnabled(false); 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 { try {
mDetailHistoryDefault = null;
mDetailHistoryForeground = null;
// load stats for current uid and template // load stats for current uid and template
mDetailHistoryDefault = mStatsService.getHistoryForUid( for (int uid : mAppDetailUids) {
mTemplate, mUid, SET_DEFAULT, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES); mDetailHistoryDefault = collectHistoryForUid(
mDetailHistoryForeground = mStatsService.getHistoryForUid( uid, SET_DEFAULT, mDetailHistoryDefault);
mTemplate, mUid, SET_FOREGROUND, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES); mDetailHistoryForeground = collectHistoryForUid(
uid, SET_FOREGROUND, mDetailHistoryForeground);
}
} catch (RemoteException e) { } catch (RemoteException e) {
// since we can't do much without history, and we don't want to // since we can't do much without history, and we don't want to
// leave with half-baked UI, we bail hard. // leave with half-baked UI, we bail hard.
@@ -727,23 +761,24 @@ public class DataUsageSummary extends Fragment {
// bind chart to historical stats // bind chart to historical stats
mChart.bindDetailNetworkStats(mDetailHistory); 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());
} else {
mAppRestrictView.setVisibility(View.GONE);
} }
/**
* 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 {
return history;
}
} }
private void setPolicyCycleDay(int cycleDay) { private void setPolicyCycleDay(int cycleDay) {
@@ -764,8 +799,8 @@ public class DataUsageSummary extends Fragment {
updatePolicy(false); updatePolicy(false);
} }
private boolean isNetworkPolicyModifiable() { private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
return isBandwidthControlEnabled() && mDataEnabled.isChecked(); return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked();
} }
private boolean isBandwidthControlEnabled() { private boolean isBandwidthControlEnabled() {
@@ -810,9 +845,10 @@ public class DataUsageSummary extends Fragment {
} }
private boolean getAppRestrictBackground() { private boolean getAppRestrictBackground() {
final int primaryUid = getAppDetailPrimaryUid();
final int uidPolicy; final int uidPolicy;
try { try {
uidPolicy = mPolicyService.getUidPolicy(mUid); uidPolicy = mPolicyService.getUidPolicy(primaryUid);
} catch (RemoteException e) { } catch (RemoteException e) {
// since we can't do much without policy, we bail hard. // since we can't do much without policy, we bail hard.
throw new RuntimeException("problem reading network policy", e); throw new RuntimeException("problem reading network policy", e);
@@ -823,9 +859,10 @@ public class DataUsageSummary extends Fragment {
private void setAppRestrictBackground(boolean restrictBackground) { private void setAppRestrictBackground(boolean restrictBackground) {
if (LOGD) Log.d(TAG, "setAppRestrictBackground()"); if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
final int primaryUid = getAppDetailPrimaryUid();
try { try {
mPolicyService.setUidPolicy( mPolicyService.setUidPolicy(primaryUid,
mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
} catch (RemoteException e) { } catch (RemoteException e) {
throw new RuntimeException("unable to save policy", 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); final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
if (isNetworkPolicyModifiable()) { if (isNetworkPolicyModifiable(policy)) {
mDisableAtLimitView.setVisibility(View.VISIBLE); mDisableAtLimitView.setVisibility(View.VISIBLE);
mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED); mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
if (!isAppDetailMode()) { if (!isAppDetailMode()) {
@@ -912,19 +949,28 @@ public class DataUsageSummary extends Fragment {
} }
// one last cycle entry to modify policy cycle day // one last cycle entry to modify policy cycle day
mCycleAdapter.setChangePossible(isNetworkPolicyModifiable()); mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy));
} }
if (!hasCycles) { if (!hasCycles) {
// no valid cycles; show all data // no policy defined cycles; show entry for each four-week period
// TODO: offer simple ranges like "last week" etc long cycleEnd = historyEnd;
mCycleAdapter.add(new CycleItem(context, historyStart, 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); mCycleAdapter.setChangePossible(false);
} }
// force pick the current cycle (first item) // force pick the current cycle (first item)
if (mCycleAdapter.getCount() > 0) {
mCycleSpinner.setSelection(0); mCycleSpinner.setSelection(0);
mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0); mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
} else {
updateDetailData();
}
} }
private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() { private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
@@ -985,8 +1031,10 @@ public class DataUsageSummary extends Fragment {
private OnItemClickListener mListListener = new OnItemClickListener() { private OnItemClickListener mListListener = new OnItemClickListener() {
/** {@inheritDoc} */ /** {@inheritDoc} */
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Context context = view.getContext();
final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position); 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); entry = mHistory.getValues(start, end, now, null);
// kick off loader for detailed stats // kick off loader for detailed stats
// TODO: delay loader until animation is finished
getLoaderManager().restartLoader(LOADER_SUMMARY, getLoaderManager().restartLoader(LOADER_SUMMARY,
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid); SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
} }
@@ -1222,9 +1271,20 @@ public class DataUsageSummary extends Fragment {
} }
private static class AppUsageItem implements Comparable<AppUsageItem> { private static class AppUsageItem implements Comparable<AppUsageItem> {
public int uid; public int[] uids;
public long total; 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} */ /** {@inheritDoc} */
public int compareTo(AppUsageItem another) { public int compareTo(AppUsageItem another) {
return Long.compare(another.total, total); return Long.compare(another.total, total);
@@ -1244,9 +1304,7 @@ public class DataUsageSummary extends Fragment {
public void bindStats(NetworkStats stats) { public void bindStats(NetworkStats stats) {
mItems.clear(); mItems.clear();
final AppUsageItem systemItem = new AppUsageItem(); final AppUsageItem systemItem = new AppUsageItem(android.os.Process.SYSTEM_UID);
systemItem.uid = android.os.Process.SYSTEM_UID;
final SparseArray<AppUsageItem> knownUids = new SparseArray<AppUsageItem>(); final SparseArray<AppUsageItem> knownUids = new SparseArray<AppUsageItem>();
NetworkStats.Entry entry = null; NetworkStats.Entry entry = null;
@@ -1260,8 +1318,7 @@ public class DataUsageSummary extends Fragment {
if (isApp || uid == TrafficStats.UID_REMOVED) { if (isApp || uid == TrafficStats.UID_REMOVED) {
AppUsageItem item = knownUids.get(uid); AppUsageItem item = knownUids.get(uid);
if (item == null) { if (item == null) {
item = new AppUsageItem(); item = new AppUsageItem(uid);
item.uid = uid;
knownUids.put(uid, item); knownUids.put(uid, item);
mItems.add(item); mItems.add(item);
} }
@@ -1269,6 +1326,7 @@ public class DataUsageSummary extends Fragment {
item.total += entry.rxBytes + entry.txBytes; item.total += entry.rxBytes + entry.txBytes;
} else { } else {
systemItem.total += entry.rxBytes + entry.txBytes; systemItem.total += entry.rxBytes + entry.txBytes;
systemItem.addUid(uid);
} }
} }
@@ -1293,7 +1351,7 @@ public class DataUsageSummary extends Fragment {
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
return mItems.get(position).uid; return mItems.get(position).uids[0];
} }
@Override @Override
@@ -1312,7 +1370,7 @@ public class DataUsageSummary extends Fragment {
android.R.id.progress); android.R.id.progress);
final AppUsageItem item = mItems.get(position); 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); icon.setImageDrawable(detail.icon);
title.setText(detail.label); title.setText(detail.label);
@@ -1323,7 +1381,6 @@ public class DataUsageSummary extends Fragment {
return convertView; return convertView;
} }
} }
/** /**
@@ -1331,11 +1388,11 @@ public class DataUsageSummary extends Fragment {
* {@link DataUsageSummary}. * {@link DataUsageSummary}.
*/ */
public static class AppDetailsFragment extends Fragment { 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(); final Bundle args = new Bundle();
args.putInt(EXTRA_UID, uid); args.putIntArray(EXTRA_UIDS, uids);
final AppDetailsFragment fragment = new AppDetailsFragment(); final AppDetailsFragment fragment = new AppDetailsFragment();
fragment.setArguments(args); fragment.setArguments(args);
@@ -1344,6 +1401,7 @@ public class DataUsageSummary extends Fragment {
final FragmentTransaction ft = parent.getFragmentManager().beginTransaction(); final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
ft.add(fragment, TAG_APP_DETAILS); ft.add(fragment, TAG_APP_DETAILS);
ft.addToBackStack(TAG_APP_DETAILS); ft.addToBackStack(TAG_APP_DETAILS);
ft.setBreadCrumbTitle(label);
ft.commit(); ft.commit();
} }
@@ -1351,7 +1409,7 @@ public class DataUsageSummary extends Fragment {
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
target.mUid = getArguments().getInt(EXTRA_UID); target.mAppDetailUids = getArguments().getIntArray(EXTRA_UIDS);
target.updateBody(); target.updateBody();
} }
@@ -1359,7 +1417,7 @@ public class DataUsageSummary extends Fragment {
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
target.mUid = UID_NONE; target.mAppDetailUids = null;
target.updateBody(); target.updateBody();
} }
} }
@@ -1441,7 +1499,7 @@ public class DataUsageSummary extends Fragment {
private static final String EXTRA_CYCLE_DAY = "cycleDay"; private static final String EXTRA_CYCLE_DAY = "cycleDay";
public static void show(DataUsageSummary parent) { 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(); final Bundle args = new Bundle();
args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay); args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
@@ -1807,4 +1865,13 @@ public class DataUsageSummary extends Fragment {
summary.setVisibility(View.VISIBLE); summary.setVisibility(View.VISIBLE);
summary.setText(string); 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.NetworkPolicy.WARNING_DISABLED;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
import static android.net.NetworkTemplate.MATCH_MOBILE_4G; 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.buildTemplateMobile3gLower;
import static android.net.NetworkTemplate.buildTemplateMobile4g; import static android.net.NetworkTemplate.buildTemplateMobile4g;
import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateMobileAll;
@@ -40,7 +41,7 @@ import java.util.ArrayList;
/** /**
* Utility class to modify list of {@link NetworkPolicy}. Specifically knows * 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 { public class NetworkPolicyEditor {
// TODO: be more robust when missing policies from service // TODO: be more robust when missing policies from service
@@ -53,65 +54,81 @@ public class NetworkPolicyEditor {
} }
public void read() { public void read() {
final NetworkPolicy[] policies;
try { try {
final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies(); policies = mPolicyService.getNetworkPolicies();
} catch (RemoteException e) {
throw new RuntimeException("problem reading policies", e);
}
boolean modified = false;
mPolicies.clear(); mPolicies.clear();
for (NetworkPolicy policy : policies) { for (NetworkPolicy policy : policies) {
// TODO: find better place to clamp these // TODO: find better place to clamp these
if (policy.limitBytes < -1) { if (policy.limitBytes < -1) {
policy.limitBytes = LIMIT_DISABLED; policy.limitBytes = LIMIT_DISABLED;
modified = true;
} }
if (policy.warningBytes < -1) { if (policy.warningBytes < -1) {
policy.warningBytes = WARNING_DISABLED; 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); mPolicies.add(policy);
} }
} catch (RemoteException e) {
throw new RuntimeException("problem reading policies", e); // when we cleaned policies above, write back changes
} if (modified) writeAsync();
} }
public void writeAsync() { public void writeAsync() {
// TODO: consider making more robust by passing through service // TODO: consider making more robust by passing through service
final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
write(); write(policies);
return null; return null;
} }
}.execute(); }.execute();
} }
public void write() { public void write(NetworkPolicy[] policies) {
try { try {
final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
mPolicyService.setNetworkPolicies(policies); mPolicyService.setNetworkPolicies(policies);
} catch (RemoteException e) { } catch (RemoteException e) {
throw new RuntimeException("problem reading policies", e); throw new RuntimeException("problem writing policies", e);
} }
} }
public boolean hasLimitedPolicy(NetworkTemplate template) { public boolean hasLimitedPolicy(NetworkTemplate template) {
final NetworkPolicy policy = getPolicy(template, false); final NetworkPolicy policy = getPolicy(template);
return policy != null && policy.limitBytes != LIMIT_DISABLED; 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) { for (NetworkPolicy policy : mPolicies) {
if (policy.template.equals(template)) { if (policy.template.equals(template)) {
return policy; 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) { private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) {
// TODO: move this into framework to share with NetworkPolicyManagerService // TODO: move this into framework to share with NetworkPolicyManagerService
@@ -124,21 +141,21 @@ public class NetworkPolicyEditor {
} }
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) { public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
final NetworkPolicy policy = getPolicy(template, true); final NetworkPolicy policy = getOrCreatePolicy(template);
policy.cycleDay = cycleDay; policy.cycleDay = cycleDay;
policy.lastSnooze = SNOOZE_NEVER; policy.lastSnooze = SNOOZE_NEVER;
writeAsync(); writeAsync();
} }
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) { public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
final NetworkPolicy policy = getPolicy(template, true); final NetworkPolicy policy = getOrCreatePolicy(template);
policy.warningBytes = warningBytes; policy.warningBytes = warningBytes;
policy.lastSnooze = SNOOZE_NEVER; policy.lastSnooze = SNOOZE_NEVER;
writeAsync(); writeAsync();
} }
public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) { public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
final NetworkPolicy policy = getPolicy(template, true); final NetworkPolicy policy = getOrCreatePolicy(template);
policy.limitBytes = limitBytes; policy.limitBytes = limitBytes;
policy.lastSnooze = SNOOZE_NEVER; policy.lastSnooze = SNOOZE_NEVER;
writeAsync(); writeAsync();
@@ -176,8 +193,8 @@ public class NetworkPolicyEditor {
} else if (beforeSplit && !split) { } else if (beforeSplit && !split) {
// combine, picking most restrictive policy // combine, picking most restrictive policy
final NetworkPolicy policy3g = getPolicy(template3g, false); final NetworkPolicy policy3g = getPolicy(template3g);
final NetworkPolicy policy4g = getPolicy(template4g, false); final NetworkPolicy policy4g = getPolicy(template4g);
final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g
: policy4g; : policy4g;
@@ -190,7 +207,7 @@ public class NetworkPolicyEditor {
} else if (!beforeSplit && split) { } else if (!beforeSplit && split) {
// duplicate existing policy into two rules // duplicate existing policy into two rules
final NetworkPolicy policyAll = getPolicy(templateAll, false); final NetworkPolicy policyAll = getPolicy(templateAll);
mPolicies.remove(policyAll); mPolicies.remove(policyAll);
mPolicies.add( mPolicies.add(
new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.warningBytes, new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.warningBytes,

View File

@@ -27,6 +27,7 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
@@ -37,7 +38,7 @@ import com.android.settings.widget.ChartSweepView.OnSweepListener;
* Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along * Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along
* with {@link ChartSweepView} for inspection ranges and warning/limits. * 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 KB_IN_BYTES = 1024;
private static final long MB_IN_BYTES = 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 int MSG_UPDATE_AXIS = 100;
private static final long DELAY_MILLIS = 250; private static final long DELAY_MILLIS = 250;
private static final boolean LIMIT_SWEEPS_TO_VALID_DATA = false;
private ChartGridView mGrid; private ChartGridView mGrid;
private ChartNetworkSeriesView mSeries; private ChartNetworkSeriesView mSeries;
private ChartNetworkSeriesView mDetailSeries; private ChartNetworkSeriesView mDetailSeries;
@@ -70,15 +73,15 @@ public class DataUsageChartView extends ChartView {
private DataUsageChartListener mListener; private DataUsageChartListener mListener;
public DataUsageChartView(Context context) { public ChartDataUsageView(Context context) {
this(context, null, 0); this(context, null, 0);
} }
public DataUsageChartView(Context context, AttributeSet attrs) { public ChartDataUsageView(Context context, AttributeSet attrs) {
this(context, attrs, 0); this(context, attrs, 0);
} }
public DataUsageChartView(Context context, AttributeSet attrs, int defStyle) { public ChartDataUsageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
init(new TimeAxis(), new InvertedChartAxis(new DataAxis())); init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
@@ -186,6 +189,7 @@ public class DataUsageChartView extends ChartView {
updateVertAxisBounds(null); updateVertAxisBounds(null);
requestLayout(); requestLayout();
invalidate();
} }
/** /**
@@ -194,7 +198,8 @@ public class DataUsageChartView extends ChartView {
*/ */
private void updateVertAxisBounds(ChartSweepView activeSweep) { private void updateVertAxisBounds(ChartSweepView activeSweep) {
final long max = mVertMax; final long max = mVertMax;
final long newMax;
long newMax = 0;
if (activeSweep != null) { if (activeSweep != null) {
final int adjustAxis = activeSweep.shouldAdjustAxis(); final int adjustAxis = activeSweep.shouldAdjustAxis();
if (adjustAxis > 0) { if (adjustAxis > 0) {
@@ -206,13 +211,13 @@ public class DataUsageChartView extends ChartView {
} else { } else {
newMax = max; newMax = max;
} }
}
} else { // always show known data and policy lines
// try showing all known data and policy
final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue()); final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10; final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
newMax = Math.max(maxVisible, 2 * GB_IN_BYTES); final long maxDefault = Math.max(maxVisible, 2 * GB_IN_BYTES);
} newMax = Math.max(maxDefault, newMax);
// only invalidate when vertMax actually changed // only invalidate when vertMax actually changed
if (newMax != mVertMax) { if (newMax != mVertMax) {
@@ -231,6 +236,16 @@ public class DataUsageChartView extends ChartView {
if (activeSweep != null) { if (activeSweep != null) {
activeSweep.updateValueFromPosition(); 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 validStart = Math.max(visibleStart, getStatsStart());
final long validEnd = Math.min(visibleEnd, getStatsEnd()); final long validEnd = Math.min(visibleEnd, getStatsEnd());
if (LIMIT_SWEEPS_TO_VALID_DATA) {
// prevent time sweeps from leaving valid data // prevent time sweeps from leaving valid data
mSweepLeft.setValidRange(validStart, validEnd); mSweepLeft.setValidRange(validStart, validEnd);
mSweepRight.setValidRange(validStart, validEnd); mSweepRight.setValidRange(validStart, validEnd);
} else {
mSweepLeft.setValidRange(visibleStart, visibleEnd);
mSweepRight.setValidRange(visibleStart, visibleEnd);
}
// default sweeps to last week of data // default sweeps to last week of data
final long halfRange = (visibleEnd + visibleStart) / 2; final long halfRange = (visibleEnd + visibleStart) / 2;
@@ -424,7 +444,7 @@ public class DataUsageChartView extends ChartView {
final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL); final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL);
final float[] tickPoints = new float[tickCount]; final float[] tickPoints = new float[tickCount];
for (int i = 0; i < tickCount; i++) { for (int i = 0; i < tickCount; i++) {
tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * i)); tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * (i + 1)));
} }
return tickPoints; return tickPoints;
} }
@@ -501,7 +521,14 @@ public class DataUsageChartView extends ChartView {
/** {@inheritDoc} */ /** {@inheritDoc} */
public float[] getTickPoints() { public float[] getTickPoints() {
final long range = mMax - mMin; 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 int tickCount = (int) (range / tickJump);
final float[] tickPoints = new float[tickCount]; final float[] tickPoints = new float[tickCount];

View File

@@ -21,6 +21,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@@ -42,8 +43,14 @@ import com.google.common.base.Preconditions;
*/ */
public class ChartSweepView extends View { public class ChartSweepView extends View {
private static final boolean DRAW_OUTLINE = false;
private Drawable mSweep; private Drawable mSweep;
private Rect mSweepPadding = new Rect(); 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 Point mSweepOffset = new Point();
private Rect mMargins = new Rect(); private Rect mMargins = new Rect();
@@ -66,6 +73,8 @@ public class ChartSweepView extends View {
private ChartSweepView mValidAfterDynamic; private ChartSweepView mValidAfterDynamic;
private ChartSweepView mValidBeforeDynamic; private ChartSweepView mValidBeforeDynamic;
private Paint mOutlinePaint = new Paint();
public static final int HORIZONTAL = 0; public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1; public static final int VERTICAL = 1;
@@ -98,6 +107,10 @@ public class ChartSweepView extends View {
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0)); setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE)); setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
mOutlinePaint.setColor(Color.RED);
mOutlinePaint.setStrokeWidth(1f);
mOutlinePaint.setStyle(Style.STROKE);
a.recycle(); a.recycle();
setWillNotDraw(false); setWillNotDraw(false);
@@ -123,11 +136,11 @@ public class ChartSweepView extends View {
if (mFollowAxis == VERTICAL) { if (mFollowAxis == VERTICAL) {
final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
- mSweepPadding.bottom; - mSweepPadding.bottom;
return mSweepPadding.top + (targetHeight / 2); return mSweepPadding.top + (targetHeight / 2) + mSweepOffset.y;
} else { } else {
final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
- mSweepPadding.right; - 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.density = getResources().getDisplayMetrics().density;
paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale); paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
paint.setColor(mLabelColor); paint.setColor(mLabelColor);
paint.setShadowLayer(4 * paint.density, 0, 0, Color.BLACK);
mLabelTemplate = new SpannableStringBuilder(template); mLabelTemplate = new SpannableStringBuilder(template);
mLabelLayout = new DynamicLayout( mLabelLayout = new DynamicLayout(
@@ -283,6 +297,26 @@ public class ChartSweepView extends View {
mValidBeforeDynamic = validBefore; 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 @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) return false; if (!isEnabled()) return false;
@@ -294,9 +328,18 @@ public class ChartSweepView extends View {
// only start tracking when in sweet spot // only start tracking when in sweet spot
final boolean accept; final boolean accept;
if (mFollowAxis == VERTICAL) { if (mFollowAxis == VERTICAL) {
accept = event.getX() > getWidth() - (mSweepPadding.right * 2); accept = event.getX() > getWidth() - (mSweepPadding.right * 3);
} else { } 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) { if (accept) {
@@ -460,6 +503,7 @@ public class ChartSweepView extends View {
final int templateHeight = mLabelLayout.getHeight(); final int templateHeight = mLabelLayout.getHeight();
mSweepOffset.x = 0; mSweepOffset.x = 0;
mSweepOffset.y = 0;
mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset()); mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight)); setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
@@ -485,6 +529,23 @@ public class ChartSweepView extends View {
mMargins.bottom = mSweepPadding.bottom; 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); mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
} }
@@ -493,9 +554,43 @@ public class ChartSweepView extends View {
final int width = getWidth(); final int width = getWidth();
final int height = getHeight(); 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; final int labelSize;
if (isEnabled() && mLabelLayout != null) { if (isEnabled() && mLabelLayout != null) {
final int count = canvas.save();
{
canvas.translate(mContentOffset.x, mContentOffset.y + labelOffset);
mLabelLayout.draw(canvas); mLabelLayout.draw(canvas);
}
canvas.restoreToCount(count);
labelSize = mLabelSize; labelSize = mLabelSize;
} else { } else {
labelSize = 0; labelSize = 0;
@@ -512,4 +607,11 @@ public class ChartSweepView extends View {
mSweep.draw(canvas); 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. * and screen coordinates.
*/ */
public class ChartView extends FrameLayout { public class ChartView extends FrameLayout {
private static final String TAG = "ChartView";
// TODO: extend something that supports two-dimensional scrolling // TODO: extend something that supports two-dimensional scrolling
private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT; private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT;
@@ -122,16 +120,30 @@ public class ChartView extends FrameLayout {
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
} else if (child instanceof ChartSweepView) { } else if (child instanceof ChartSweepView) {
// sweep is always placed along specific dimension layoutSweep((ChartSweepView) child, parentRect, childRect);
final ChartSweepView sweep = (ChartSweepView) child; 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(); final Rect sweepMargins = sweep.getMargins();
// sweep is always placed along specific dimension
if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) { if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
parentRect.top += sweepMargins.top + (int) sweep.getPoint(); parentRect.top += sweepMargins.top + (int) sweep.getPoint();
parentRect.bottom = parentRect.top; parentRect.bottom = parentRect.top;
parentRect.left += sweepMargins.left; parentRect.left += sweepMargins.left;
parentRect.right += sweepMargins.right; parentRect.right += sweepMargins.right;
Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(), Gravity.apply(SWEEP_GRAVITY, parentRect.width(), sweep.getMeasuredHeight(),
parentRect, childRect); parentRect, childRect);
} else { } else {
@@ -139,13 +151,9 @@ public class ChartView extends FrameLayout {
parentRect.right = parentRect.left; parentRect.right = parentRect.left;
parentRect.top += sweepMargins.top; parentRect.top += sweepMargins.top;
parentRect.bottom += sweepMargins.bottom; parentRect.bottom += sweepMargins.bottom;
Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(), Gravity.apply(SWEEP_GRAVITY, sweep.getMeasuredWidth(), parentRect.height(),
parentRect, childRect); parentRect, childRect);
} }
} }
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
}
}
} }