Merge "Data usage performance, bugfixes."

This commit is contained in:
Jeff Sharkey
2011-09-12 16:59:30 -07:00
committed by Android (Google) Code Review
16 changed files with 663 additions and 262 deletions

View File

@@ -17,8 +17,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/cycles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="40dip"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingLeft="@*android:dimen/preference_item_padding_side"
android:paddingRight="@*android:dimen/preference_item_padding_side">

View File

@@ -17,8 +17,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@*android:dimen/preference_fragment_padding_side"
android:paddingRight="@*android:dimen/preference_fragment_padding_side"
android:orientation="vertical"
android:clipChildren="false"
android:clipToPadding="false">

View File

@@ -16,9 +16,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@*android:dimen/preference_fragment_padding_side"
android:paddingRight="@*android:dimen/preference_fragment_padding_side">
android:layout_height="wrap_content">
<include layout="@layout/app_percentage_item" />

View File

@@ -3485,13 +3485,13 @@ found in the list of installed applications.</string>
<!-- Body of dialog shown to request confirmation that mobile data will be disabled. [CHAR LIMIT=NONE] -->
<string name="data_usage_disable_mobile">Disable mobile data?</string>
<!-- Checkbox label that will disable mobile network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
<string name="data_usage_disable_mobile_limit">Disable mobile data at limit</string>
<string name="data_usage_disable_mobile_limit">Set mobile data limit</string>
<!-- Checkbox label that will disable 4G network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
<string name="data_usage_disable_4g_limit">Disable 4G data at limit</string>
<string name="data_usage_disable_4g_limit">Set 4G data limit</string>
<!-- Checkbox label that will disable 2G-3G network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
<string name="data_usage_disable_3g_limit">Disable 2G-3G data at limit</string>
<string name="data_usage_disable_3g_limit">Set 2G-3G data limit</string>
<!-- Checkbox label that will disable Wi-Fi network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
<string name="data_usage_disable_wifi_limit">Disable Wi-Fi data at limit</string>
<string name="data_usage_disable_wifi_limit">Set Wi-Fi data limit</string>
<!-- Tab title for showing Wi-Fi data usage. [CHAR LIMIT=10] -->
<string name="data_usage_tab_wifi">Wi-Fi</string>

View File

@@ -25,11 +25,6 @@ import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
@@ -43,6 +38,7 @@ import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.Time.TIMEZONE_UTC;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.settings.Utils.prepareCustomPreferencesList;
import android.animation.LayoutTransition;
@@ -58,15 +54,11 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.net.ConnectivityManager;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
@@ -123,9 +115,12 @@ import android.widget.TextView;
import com.android.internal.telephony.Phone;
import com.android.settings.drawable.InsetBoundsDrawable;
import com.android.settings.drawable.DrawableWrapper;
import com.android.settings.net.ChartData;
import com.android.settings.net.ChartDataLoader;
import com.android.settings.net.NetworkPolicyEditor;
import com.android.settings.net.SummaryForAllUidLoader;
import com.android.settings.net.UidDetail;
import com.android.settings.net.UidDetailProvider;
import com.android.settings.widget.ChartDataUsageView;
import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
import com.android.settings.widget.PieChartView;
@@ -165,7 +160,8 @@ public class DataUsageSummary extends Fragment {
private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
private static final String TAG_APP_DETAILS = "appDetails";
private static final int LOADER_SUMMARY = 2;
private static final int LOADER_CHART_DATA = 2;
private static final int LOADER_SUMMARY = 3;
private static final long KB_IN_BYTES = 1024;
private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
@@ -188,6 +184,9 @@ public class DataUsageSummary extends Fragment {
private ListView mListView;
private DataUsageAdapter mAdapter;
/** Distance to inset content from sides, when needed. */
private int mInsetSide = 0;
private ViewGroup mHeader;
private ViewGroup mNetworkSwitchesContainer;
@@ -220,7 +219,8 @@ public class DataUsageSummary extends Fragment {
private boolean mShowWifi = false;
private boolean mShowEthernet = false;
private NetworkTemplate mTemplate = null;
private NetworkTemplate mTemplate;
private ChartData mChartData;
private int[] mAppDetailUids = null;
@@ -228,11 +228,6 @@ public class DataUsageSummary extends Fragment {
private NetworkPolicyEditor mPolicyEditor;
private NetworkStatsHistory mHistory;
private NetworkStatsHistory mDetailHistory;
private NetworkStatsHistory mDetailHistoryDefault;
private NetworkStatsHistory mDetailHistoryForeground;
private String mCurrentTab = null;
private String mIntentTab = null;
@@ -242,6 +237,8 @@ public class DataUsageSummary extends Fragment {
/** Flag used to ignore listeners during binding. */
private boolean mBinding;
private UidDetailProvider mUidDetailProvider;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -273,25 +270,39 @@ public class DataUsageSummary extends Fragment {
final Context context = inflater.getContext();
final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
mUidDetailProvider = new UidDetailProvider(context);
mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container);
mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
mListView = (ListView) view.findViewById(android.R.id.list);
// decide if we need to manually inset our content, or if we should rely
// on parent container for inset.
final boolean shouldInset = mListView.getScrollBarStyle()
== View.SCROLLBARS_OUTSIDE_OVERLAY;
if (shouldInset) {
mInsetSide = view.getResources().getDimensionPixelOffset(
com.android.internal.R.dimen.preference_fragment_padding_side);
} else {
mInsetSide = 0;
}
// adjust padding around tabwidget as needed
prepareCustomPreferencesList(container, view, mListView, true);
// inset selector and divider drawables
final int insetSide = view.getResources().getDimensionPixelOffset(
com.android.internal.R.dimen.preference_fragment_padding_side);
insetListViewDrawables(mListView, insetSide);
mTabHost.setup();
mTabHost.setOnTabChangedListener(mTabListener);
mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
mListView.addHeaderView(mHeader, null, false);
if (mInsetSide > 0) {
// inset selector and divider drawables
insetListViewDrawables(mListView, mInsetSide);
mHeader.setPadding(mInsetSide, 0, mInsetSide, 0);
}
{
// bind network switches
mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById(
@@ -346,7 +357,7 @@ public class DataUsageSummary extends Fragment {
// only assign layout transitions once first layout is finished
mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
mAdapter = new DataUsageAdapter();
mAdapter = new DataUsageAdapter(mUidDetailProvider, mInsetSide);
mListView.setOnItemClickListener(mListListener);
mListView.setAdapter(mAdapter);
@@ -370,7 +381,10 @@ public class DataUsageSummary extends Fragment {
@Override
protected Void doInBackground(Void... params) {
try {
// wait a few seconds before kicking off
Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS);
mStatsService.forceUpdate();
} catch (InterruptedException e) {
} catch (RemoteException e) {
}
return null;
@@ -382,7 +396,7 @@ public class DataUsageSummary extends Fragment {
updateBody();
}
}
}.execute();
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
@@ -393,21 +407,23 @@ public class DataUsageSummary extends Fragment {
@Override
public void onPrepareOptionsMenu(Menu menu) {
final Context context = getActivity();
final boolean appDetailMode = isAppDetailMode();
mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
mMenuDataRoaming.setVisible(hasMobileRadio(context));
mMenuDataRoaming.setVisible(hasMobileRadio(context) && !appDetailMode);
mMenuDataRoaming.setChecked(getDataRoaming());
mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
mMenuRestrictBackground.setVisible(!appDetailMode);
mMenuRestrictBackground.setChecked(getRestrictBackground());
final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
split4g.setVisible(hasMobile4gRadio(context));
split4g.setVisible(hasMobile4gRadio(context) && !appDetailMode);
split4g.setChecked(isMobilePolicySplit());
final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
if (hasWifiRadio(context) && hasMobileRadio(context)) {
showWifi.setVisible(true);
showWifi.setVisible(!appDetailMode);
showWifi.setChecked(mShowWifi);
} else {
showWifi.setVisible(false);
@@ -416,7 +432,7 @@ public class DataUsageSummary extends Fragment {
final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
if (hasEthernet(context) && hasMobileRadio(context)) {
showEthernet.setVisible(true);
showEthernet.setVisible(!appDetailMode);
showEthernet.setChecked(mShowEthernet);
} else {
showEthernet.setVisible(false);
@@ -478,6 +494,9 @@ public class DataUsageSummary extends Fragment {
mDataEnabledView = null;
mDisableAtLimitView = null;
mUidDetailProvider.clearCache();
mUidDetailProvider = null;
}
/**
@@ -495,6 +514,7 @@ public class DataUsageSummary extends Fragment {
final LayoutTransition chartTransition = buildLayoutTransition();
chartTransition.setStartDelay(LayoutTransition.APPEARING, 0);
chartTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
chartTransition.setAnimator(LayoutTransition.APPEARING, null);
chartTransition.setAnimator(LayoutTransition.DISAPPEARING, null);
mChart.setLayoutTransition(chartTransition);
}
@@ -536,17 +556,14 @@ public class DataUsageSummary extends Fragment {
mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE);
if (mIntentTab != null) {
if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) {
// already hit updateBody() when added; ignore
updateBody();
} else {
mTabHost.setCurrentTabByTag(mIntentTab);
}
mIntentTab = null;
} else {
if (mTabHost.getCurrentTab() == 0) {
updateBody();
} else {
mTabHost.setCurrentTab(0);
}
// already hit updateBody() when added; ignore
}
}
@@ -583,6 +600,7 @@ public class DataUsageSummary extends Fragment {
*/
private void updateBody() {
mBinding = true;
if (!isAdded()) return;
final Context context = getActivity();
final String currentTab = mTabHost.getCurrentTabTag();
@@ -636,25 +654,14 @@ public class DataUsageSummary extends Fragment {
throw new IllegalStateException("unknown tab: " + currentTab);
}
try {
// load stats for current template
mHistory = mStatsService.getHistoryForNetwork(
mTemplate, FIELD_RX_BYTES | FIELD_TX_BYTES);
} catch (RemoteException e) {
// since we can't do much without policy or history, and we don't
// want to leave with half-baked UI, we bail hard.
throw new RuntimeException("problem reading network policy or stats", e);
}
// kick off loader for network history
// TODO: consider chaining two loaders together instead of reloading
// network history when showing app detail.
getLoaderManager().restartLoader(LOADER_CHART_DATA,
ChartDataLoader.buildArgs(mTemplate, mAppDetailUids), mChartDataCallbacks);
// bind chart to historical stats
mChart.bindNetworkStats(mHistory);
// only update policy when switching tabs
updatePolicy(tabChanged);
updateAppDetail();
// force scroll to top of body
mListView.smoothScrollToPosition(0);
// detail mode can change visible menus, invalidate
getActivity().invalidateOptionsMenu();
mBinding = false;
}
@@ -683,10 +690,6 @@ public class DataUsageSummary extends Fragment {
mAppDetail.setVisibility(View.GONE);
mCycleAdapter.setChangeVisible(true);
mDetailHistory = null;
mDetailHistoryDefault = null;
mDetailHistoryForeground = null;
// hide detail stats when not in detail mode
mChart.bindDetailNetworkStats(null);
return;
@@ -697,7 +700,7 @@ public class DataUsageSummary extends Fragment {
// show icon and all labels appearing under this app
final int primaryUid = getAppDetailPrimaryUid();
final UidDetail detail = resolveDetailForUid(context, primaryUid);
final UidDetail detail = mUidDetailProvider.getUidDetail(primaryUid, true);
mAppIcon.setImageDrawable(detail.icon);
mAppTitles.removeAllViews();
@@ -725,7 +728,6 @@ public class DataUsageSummary extends Fragment {
mAppSettings.setEnabled(false);
}
updateDetailHistory();
updateDetailData();
if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid)
@@ -743,54 +745,6 @@ public class DataUsageSummary extends Fragment {
}
}
/**
* 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
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.
throw new RuntimeException("problem reading network stats", e);
}
mDetailHistory = new NetworkStatsHistory(mDetailHistoryForeground.getBucketDuration());
mDetailHistory.recordEntireHistory(mDetailHistoryDefault);
mDetailHistory.recordEntireHistory(mDetailHistoryForeground);
// bind chart to historical stats
mChart.bindDetailNetworkStats(mDetailHistory);
}
/**
* 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) {
if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
@@ -956,21 +910,22 @@ public class DataUsageSummary extends Fragment {
mCycleAdapter.clear();
final Context context = mCycleSpinner.getContext();
long historyStart = mHistory.getStart();
long historyEnd = mHistory.getEnd();
if (historyStart == Long.MAX_VALUE || historyEnd == Long.MIN_VALUE) {
historyStart = System.currentTimeMillis();
historyEnd = System.currentTimeMillis();
long historyStart = Long.MAX_VALUE;
long historyEnd = Long.MIN_VALUE;
if (mChartData != null) {
historyStart = mChartData.network.getStart();
historyEnd = mChartData.network.getEnd();
}
if (historyStart == Long.MAX_VALUE) historyStart = System.currentTimeMillis();
if (historyEnd == Long.MIN_VALUE) historyEnd = System.currentTimeMillis();
boolean hasCycles = false;
if (policy != null) {
// find the next cycle boundary
long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
int guardCount = 0;
// walk backwards, generating all valid cycle ranges
while (cycleEnd > historyStart) {
final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
@@ -979,12 +934,6 @@ public class DataUsageSummary extends Fragment {
mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
cycleEnd = cycleStart;
hasCycles = true;
// TODO: remove this guard once we have better testing
if (guardCount++ > 50) {
Log.wtf(TAG, "stuck generating ranges for historyStart=" + historyStart
+ ", historyEnd=" + historyEnd + " and policy=" + policy);
}
}
// one last cycle entry to modify policy cycle day
@@ -1075,7 +1024,7 @@ public class DataUsageSummary extends Fragment {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Context context = view.getContext();
final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
final UidDetail detail = resolveDetailForUid(context, app.uids[0]);
final UidDetail detail = mUidDetailProvider.getUidDetail(app.uids[0], true);
AppDetailsFragment.show(DataUsageSummary.this, app.uids, detail.label);
}
};
@@ -1128,11 +1077,11 @@ public class DataUsageSummary extends Fragment {
final Context context = getActivity();
NetworkStatsHistory.Entry entry = null;
if (isAppDetailMode() && mDetailHistory != null) {
if (isAppDetailMode() && mChartData != null && mChartData.detail != null) {
// bind foreground/background to piechart and labels
entry = mDetailHistoryDefault.getValues(start, end, now, entry);
entry = mChartData.detailDefault.getValues(start, end, now, entry);
final long defaultBytes = entry.rxBytes + entry.txBytes;
entry = mDetailHistoryForeground.getValues(start, end, now, entry);
entry = mChartData.detailForeground.getValues(start, end, now, entry);
final long foregroundBytes = entry.rxBytes + entry.txBytes;
mAppPieChart.setOriginAngle(175);
@@ -1147,17 +1096,18 @@ public class DataUsageSummary extends Fragment {
mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes));
// and finally leave with summary data for label below
entry = mDetailHistory.getValues(start, end, now, null);
entry = mChartData.detail.getValues(start, end, now, null);
getLoaderManager().destroyLoader(LOADER_SUMMARY);
} else {
entry = mHistory.getValues(start, end, now, null);
if (mChartData != null) {
entry = mChartData.network.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);
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
}
final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
@@ -1168,7 +1118,38 @@ public class DataUsageSummary extends Fragment {
getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
}
private final LoaderCallbacks<NetworkStats> mSummaryForAllUid = new LoaderCallbacks<
private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks<
ChartData>() {
/** {@inheritDoc} */
public Loader<ChartData> onCreateLoader(int id, Bundle args) {
return new ChartDataLoader(getActivity(), mStatsService, args);
}
/** {@inheritDoc} */
public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
mChartData = data;
mChart.bindNetworkStats(mChartData.network);
mChart.bindDetailNetworkStats(mChartData.detail);
// calcuate policy cycles based on available data
updatePolicy(true);
updateAppDetail();
// force scroll to top of body when showing detail
if (mChartData.detail != null) {
mListView.smoothScrollToPosition(0);
}
}
/** {@inheritDoc} */
public void onLoaderReset(Loader<ChartData> loader) {
mChartData = null;
mChart.bindNetworkStats(null);
mChart.bindDetailNetworkStats(null);
}
};
private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks<
NetworkStats>() {
/** {@inheritDoc} */
public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
@@ -1337,9 +1318,17 @@ public class DataUsageSummary extends Fragment {
* Adapter of applications, sorted by total usage descending.
*/
public static class DataUsageAdapter extends BaseAdapter {
private final UidDetailProvider mProvider;
private final int mInsetSide;
private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
private long mLargest;
public DataUsageAdapter(UidDetailProvider provider, int insetSide) {
mProvider = checkNotNull(provider);
mInsetSide = insetSide;
}
/**
* Bind the given {@link NetworkStats}, or {@code null} to clear list.
*/
@@ -1401,21 +1390,22 @@ public class DataUsageSummary extends Fragment {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.data_usage_item, parent, false);
if (mInsetSide > 0) {
convertView.setPadding(mInsetSide, 0, mInsetSide, 0);
}
}
final Context context = parent.getContext();
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
final ProgressBar progress = (ProgressBar) convertView.findViewById(
android.R.id.progress);
// kick off async load of app details
final AppUsageItem item = mItems.get(position);
final UidDetail detail = resolveDetailForUid(context, item.uids[0]);
UidDetailTask.bindView(mProvider, item, convertView);
icon.setImageDrawable(detail.icon);
title.setText(detail.label);
text1.setText(Formatter.formatFileSize(context, item.total));
final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
@@ -1745,66 +1735,64 @@ public class DataUsageSummary extends Fragment {
}
}
public static class UidDetail {
public CharSequence label;
public CharSequence[] detailLabels;
public Drawable icon;
}
/**
* Resolve best descriptive label for the given UID.
* Background task that loads {@link UidDetail}, binding to
* {@link DataUsageAdapter} row item when finished.
*/
public static UidDetail resolveDetailForUid(Context context, int uid) {
final Resources res = context.getResources();
final PackageManager pm = context.getPackageManager();
private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> {
private final UidDetailProvider mProvider;
private final AppUsageItem mItem;
private final View mTarget;
final UidDetail detail = new UidDetail();
detail.label = pm.getNameForUid(uid);
detail.icon = pm.getDefaultActivityIcon();
// handle special case labels
switch (uid) {
case android.os.Process.SYSTEM_UID:
detail.label = res.getString(R.string.process_kernel_label);
detail.icon = pm.getDefaultActivityIcon();
return detail;
case TrafficStats.UID_REMOVED:
detail.label = res.getString(R.string.data_usage_uninstalled_apps);
detail.icon = pm.getDefaultActivityIcon();
return detail;
private UidDetailTask(UidDetailProvider provider, AppUsageItem item, View target) {
mProvider = checkNotNull(provider);
mItem = checkNotNull(item);
mTarget = checkNotNull(target);
}
// otherwise fall back to using packagemanager labels
final String[] packageNames = pm.getPackagesForUid(uid);
final int length = packageNames != null ? packageNames.length : 0;
try {
if (length == 1) {
final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
detail.label = info.loadLabel(pm).toString();
detail.icon = info.loadIcon(pm);
} else if (length > 1) {
detail.detailLabels = new CharSequence[length];
for (int i = 0; i < length; i++) {
final String packageName = packageNames[i];
final PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
detail.detailLabels[i] = appInfo.loadLabel(pm).toString();
if (packageInfo.sharedUserLabel != 0) {
detail.label = pm.getText(packageName, packageInfo.sharedUserLabel,
packageInfo.applicationInfo).toString();
detail.icon = appInfo.loadIcon(pm);
}
}
public static void bindView(
UidDetailProvider provider, AppUsageItem item, View target) {
final UidDetailTask existing = (UidDetailTask) target.getTag();
if (existing != null) {
existing.cancel(false);
}
final UidDetail cachedDetail = provider.getUidDetail(item.uids[0], false);
if (cachedDetail != null) {
bindView(cachedDetail, target);
} else {
target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor(
AsyncTask.THREAD_POOL_EXECUTOR));
}
} catch (NameNotFoundException e) {
}
if (TextUtils.isEmpty(detail.label)) {
detail.label = Integer.toString(uid);
private static void bindView(UidDetail detail, View target) {
final ImageView icon = (ImageView) target.findViewById(android.R.id.icon);
final TextView title = (TextView) target.findViewById(android.R.id.title);
if (detail != null) {
icon.setImageDrawable(detail.icon);
title.setText(detail.label);
} else {
icon.setImageDrawable(null);
title.setText(null);
}
}
@Override
protected void onPreExecute() {
bindView(null, mTarget);
}
@Override
protected UidDetail doInBackground(Void... params) {
return mProvider.getUidDetail(mItem.uids[0], true);
}
@Override
protected void onPostExecute(UidDetail result) {
bindView(result, mTarget);
}
return detail;
}
/**
@@ -1827,6 +1815,9 @@ public class DataUsageSummary extends Fragment {
* Test if device has a mobile 4G data radio.
*/
private static boolean hasMobile4gRadio(Context context) {
if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) {
return false;
}
if (TEST_RADIOS) {
return SystemProperties.get(TEST_RADIOS_PROP).contains("4g");
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.net;
import android.net.NetworkStatsHistory;
public class ChartData {
public NetworkStatsHistory network;
public NetworkStatsHistory detail;
public NetworkStatsHistory detailDefault;
public NetworkStatsHistory detailForeground;
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.net;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.net.INetworkStatsService;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.Bundle;
import android.os.RemoteException;
/**
* Loader for historical chart data for both network and UID details.
*/
public class ChartDataLoader extends AsyncTaskLoader<ChartData> {
private static final String KEY_TEMPLATE = "template";
private static final String KEY_UIDS = "uids";
private static final String KEY_FIELDS = "fields";
private final INetworkStatsService mStatsService;
private final Bundle mArgs;
public static Bundle buildArgs(NetworkTemplate template, int[] uids) {
return buildArgs(template, uids, FIELD_RX_BYTES | FIELD_TX_BYTES);
}
public static Bundle buildArgs(NetworkTemplate template, int[] uids, int fields) {
final Bundle args = new Bundle();
args.putParcelable(KEY_TEMPLATE, template);
args.putIntArray(KEY_UIDS, uids);
args.putInt(KEY_FIELDS, fields);
return args;
}
public ChartDataLoader(Context context, INetworkStatsService statsService, Bundle args) {
super(context);
mStatsService = statsService;
mArgs = args;
}
@Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
@Override
public ChartData loadInBackground() {
final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
final int[] uids = mArgs.getIntArray(KEY_UIDS);
final int fields = mArgs.getInt(KEY_FIELDS);
try {
return loadInBackground(template, uids, fields);
} 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.
throw new RuntimeException("problem reading network stats", e);
}
}
private ChartData loadInBackground(NetworkTemplate template, int[] uids, int fields)
throws RemoteException {
final ChartData data = new ChartData();
data.network = mStatsService.getHistoryForNetwork(template, fields);
if (uids != null) {
data.detailDefault = null;
data.detailForeground = null;
// load stats for current uid and template
for (int uid : uids) {
data.detailDefault = collectHistoryForUid(
template, uid, SET_DEFAULT, data.detailDefault);
data.detailForeground = collectHistoryForUid(
template, uid, SET_FOREGROUND, data.detailForeground);
}
data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration());
data.detail.recordEntireHistory(data.detailDefault);
data.detail.recordEntireHistory(data.detailForeground);
}
return data;
}
@Override
protected void onStopLoading() {
super.onStopLoading();
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
cancelLoad();
}
/**
* Collect {@link NetworkStatsHistory} for the requested UID, combining with
* an existing {@link NetworkStatsHistory} if provided.
*/
private NetworkStatsHistory collectHistoryForUid(
NetworkTemplate template, int uid, int set, NetworkStatsHistory existing)
throws RemoteException {
final NetworkStatsHistory history = mStatsService.getHistoryForUid(
template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
if (existing != null) {
existing.recordEntireHistory(history);
return existing;
} else {
return history;
}
}
}

View File

@@ -36,8 +36,10 @@ import android.text.format.Time;
import com.android.internal.util.Objects;
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Utility class to modify list of {@link NetworkPolicy}. Specifically knows
@@ -46,6 +48,8 @@ import java.util.ArrayList;
public class NetworkPolicyEditor {
// TODO: be more robust when missing policies from service
public static final boolean ENABLE_SPLIT_POLICIES = false;
private INetworkPolicyManager mPolicyService;
private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList();
@@ -83,6 +87,11 @@ public class NetworkPolicyEditor {
mPolicies.add(policy);
}
// force combine any split policies when disabled
if (!ENABLE_SPLIT_POLICIES) {
modified |= forceMobilePolicyCombined();
}
// when we cleaned policies above, write back changes
if (modified) writeAsync();
}
@@ -161,6 +170,22 @@ public class NetworkPolicyEditor {
writeAsync();
}
/**
* Remove any split {@link NetworkPolicy}.
*/
private boolean forceMobilePolicyCombined() {
final HashSet<String> subscriberIds = Sets.newHashSet();
for (NetworkPolicy policy : mPolicies) {
subscriberIds.add(policy.template.getSubscriberId());
}
boolean modified = false;
for (String subscriberId : subscriberIds) {
modified |= setMobilePolicySplitInternal(subscriberId, false);
}
return modified;
}
public boolean isMobilePolicySplit(String subscriberId) {
boolean has3g = false;
boolean has4g = false;
@@ -181,6 +206,18 @@ public class NetworkPolicyEditor {
}
public void setMobilePolicySplit(String subscriberId, boolean split) {
if (setMobilePolicySplitInternal(subscriberId, split)) {
writeAsync();
}
}
/**
* Mutate {@link NetworkPolicy} for given subscriber, combining or splitting
* the policy as requested.
*
* @return {@code true} when any {@link NetworkPolicy} was mutated.
*/
private boolean setMobilePolicySplitInternal(String subscriberId, boolean split) {
final boolean beforeSplit = isMobilePolicySplit(subscriberId);
final NetworkTemplate template3g = buildTemplateMobile3gLower(subscriberId);
@@ -189,7 +226,7 @@ public class NetworkPolicyEditor {
if (split == beforeSplit) {
// already in requested state; skip
return;
return false;
} else if (beforeSplit && !split) {
// combine, picking most restrictive policy
@@ -203,7 +240,7 @@ public class NetworkPolicyEditor {
mPolicies.add(
new NetworkPolicy(templateAll, restrictive.cycleDay, restrictive.warningBytes,
restrictive.limitBytes, SNOOZE_NEVER));
writeAsync();
return true;
} else if (!beforeSplit && split) {
// duplicate existing policy into two rules
@@ -215,8 +252,9 @@ public class NetworkPolicyEditor {
mPolicies.add(
new NetworkPolicy(template4g, policyAll.cycleDay, policyAll.warningBytes,
policyAll.limitBytes, SNOOZE_NEVER));
writeAsync();
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.net;
import android.graphics.drawable.Drawable;
public class UidDetail {
public CharSequence label;
public CharSequence[] detailLabels;
public Drawable icon;
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.net;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.net.TrafficStats;
import android.text.TextUtils;
import android.util.SparseArray;
import com.android.settings.R;
public class UidDetailProvider {
private final Context mContext;
private final SparseArray<UidDetail> mUidDetailCache;
public UidDetailProvider(Context context) {
mContext = context.getApplicationContext();
mUidDetailCache = new SparseArray<UidDetail>();
}
public void clearCache() {
mUidDetailCache.clear();
}
/**
* Resolve best descriptive label for the given UID.
*/
public UidDetail getUidDetail(int uid, boolean blocking) {
final UidDetail cached = mUidDetailCache.get(uid);
if (cached != null) {
return cached;
} else if (!blocking) {
return null;
}
final Resources res = mContext.getResources();
final PackageManager pm = mContext.getPackageManager();
final UidDetail detail = new UidDetail();
detail.label = pm.getNameForUid(uid);
detail.icon = pm.getDefaultActivityIcon();
// handle special case labels
switch (uid) {
case android.os.Process.SYSTEM_UID:
detail.label = res.getString(R.string.process_kernel_label);
detail.icon = pm.getDefaultActivityIcon();
mUidDetailCache.put(uid, detail);
return detail;
case TrafficStats.UID_REMOVED:
detail.label = res.getString(R.string.data_usage_uninstalled_apps);
detail.icon = pm.getDefaultActivityIcon();
mUidDetailCache.put(uid, detail);
return detail;
}
// otherwise fall back to using packagemanager labels
final String[] packageNames = pm.getPackagesForUid(uid);
final int length = packageNames != null ? packageNames.length : 0;
try {
if (length == 1) {
final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
detail.label = info.loadLabel(pm).toString();
detail.icon = info.loadIcon(pm);
} else if (length > 1) {
detail.detailLabels = new CharSequence[length];
for (int i = 0; i < length; i++) {
final String packageName = packageNames[i];
final PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
detail.detailLabels[i] = appInfo.loadLabel(pm).toString();
if (packageInfo.sharedUserLabel != 0) {
detail.label = pm.getText(packageName, packageInfo.sharedUserLabel,
packageInfo.applicationInfo).toString();
detail.icon = appInfo.loadIcon(pm);
}
}
}
} catch (NameNotFoundException e) {
}
if (TextUtils.isEmpty(detail.label)) {
detail.label = Integer.toString(uid);
}
mUidDetailCache.put(uid, detail);
return detail;
}
}

View File

@@ -26,9 +26,9 @@ import android.text.SpannableStringBuilder;
public interface ChartAxis {
/** Set range of raw values this axis should cover. */
public void setBounds(long min, long max);
public boolean setBounds(long min, long max);
/** Set range of screen points this axis should cover. */
public void setSize(float size);
public boolean setSize(float size);
/** Convert raw value into screen point. */
public float convertToPoint(long value);

View File

@@ -30,6 +30,7 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.android.internal.util.Objects;
import com.android.settings.R;
import com.android.settings.widget.ChartSweepView.OnSweepListener;
@@ -214,7 +215,8 @@ public class ChartDataUsageView extends ChartView {
// 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 maxSeries = Math.max(mSeries.getMaxVisible(), mDetailSeries.getMaxVisible());
final long maxVisible = Math.max(maxSeries, maxSweep) * 12 / 10;
final long maxDefault = Math.max(maxVisible, 2 * GB_IN_BYTES);
newMax = Math.max(maxDefault, newMax);
@@ -222,12 +224,14 @@ public class ChartDataUsageView extends ChartView {
if (newMax != mVertMax) {
mVertMax = newMax;
mVert.setBounds(0L, newMax);
final boolean changed = mVert.setBounds(0L, newMax);
mSweepWarning.setValidRange(0L, newMax);
mSweepLimit.setValidRange(0L, newMax);
mSeries.generatePath();
mDetailSeries.generatePath();
if (changed) {
mSeries.invalidatePath();
mDetailSeries.invalidatePath();
}
mGrid.invalidate();
@@ -263,6 +267,10 @@ public class ChartDataUsageView extends ChartView {
interestLine = mSweepLimit.getValue();
}
if (interestLine < 0) {
interestLine = Long.MAX_VALUE;
}
final boolean estimateVisible = (maxEstimate >= interestLine * 7 / 10);
mSeries.setEstimateVisible(estimateVisible);
}
@@ -354,8 +362,10 @@ public class ChartDataUsageView extends ChartView {
* last "week" of available data, without triggering listener events.
*/
public void setVisibleRange(long visibleStart, long visibleEnd) {
mHoriz.setBounds(visibleStart, visibleEnd);
final boolean changed = mHoriz.setBounds(visibleStart, visibleEnd);
mGrid.setBounds(visibleStart, visibleEnd);
mSeries.setBounds(visibleStart, visibleEnd);
mDetailSeries.setBounds(visibleStart, visibleEnd);
final long validStart = Math.max(visibleStart, getStatsStart());
final long validEnd = Math.min(visibleEnd, getStatsEnd());
@@ -378,7 +388,10 @@ public class ChartDataUsageView extends ChartView {
mSweepRight.setValue(sweepMax);
requestLayout();
mSeries.generatePath();
if (changed) {
mSeries.invalidatePath();
mDetailSeries.invalidatePath();
}
updateVertAxisBounds(null);
updateEstimateVisible();
@@ -410,15 +423,30 @@ public class ChartDataUsageView extends ChartView {
setBounds(currentTime - DateUtils.DAY_IN_MILLIS * 30, currentTime);
}
/** {@inheritDoc} */
public void setBounds(long min, long max) {
mMin = min;
mMax = max;
@Override
public int hashCode() {
return Objects.hashCode(mMin, mMax, mSize);
}
/** {@inheritDoc} */
public void setSize(float size) {
this.mSize = size;
public boolean setBounds(long min, long max) {
if (mMin != min || mMax != max) {
mMin = min;
mMax = max;
return true;
} else {
return false;
}
}
/** {@inheritDoc} */
public boolean setSize(float size) {
if (mSize != size) {
mSize = size;
return true;
} else {
return false;
}
}
/** {@inheritDoc} */
@@ -461,15 +489,30 @@ public class ChartDataUsageView extends ChartView {
private long mMax;
private float mSize;
/** {@inheritDoc} */
public void setBounds(long min, long max) {
mMin = min;
mMax = max;
@Override
public int hashCode() {
return Objects.hashCode(mMin, mMax, mSize);
}
/** {@inheritDoc} */
public void setSize(float size) {
mSize = size;
public boolean setBounds(long min, long max) {
if (mMin != min || mMax != max) {
mMin = min;
mMax = max;
return true;
} else {
return false;
}
}
/** {@inheritDoc} */
public boolean setSize(float size) {
if (mSize != size) {
mSize = size;
return true;
} else {
return false;
}
}
/** {@inheritDoc} */

View File

@@ -58,12 +58,16 @@ public class ChartNetworkSeriesView extends View {
private Path mPathFill;
private Path mPathEstimate;
private long mStart;
private long mEnd;
private long mPrimaryLeft;
private long mPrimaryRight;
/** Series will be extended to reach this end time. */
private long mEndTime = Long.MIN_VALUE;
private boolean mPathValid = false;
private boolean mEstimateVisible = false;
private long mMax;
@@ -130,13 +134,15 @@ public class ChartNetworkSeriesView extends View {
public void bindNetworkStats(NetworkStatsHistory stats) {
mStats = stats;
mPathStroke.reset();
mPathFill.reset();
mPathEstimate.reset();
invalidatePath();
invalidate();
}
public void setBounds(long start, long end) {
mStart = start;
mEnd = end;
}
/**
* Set the range to paint with {@link #mPaintFill}, leaving the remaining
* area to be painted with {@link #mPaintFillSecondary}.
@@ -147,26 +153,27 @@ public class ChartNetworkSeriesView extends View {
invalidate();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
generatePath();
public void invalidatePath() {
mPathValid = false;
mMax = 0;
invalidate();
}
/**
* Erase any existing {@link Path} and generate series outline based on
* currently bound {@link NetworkStatsHistory} data.
*/
public void generatePath() {
private void generatePath() {
if (LOGD) Log.d(TAG, "generatePath()");
mMax = 0;
mPathStroke.reset();
mPathFill.reset();
mPathEstimate.reset();
mPathValid = true;
// bail when not enough stats to render
if (mStats == null || mStats.size() < 2) {
invalidate();
return;
}
@@ -185,7 +192,10 @@ public class ChartNetworkSeriesView extends View {
long totalData = 0;
NetworkStatsHistory.Entry entry = null;
for (int i = 0; i < mStats.size(); i++) {
final int start = mStats.getIndexBefore(mStart);
final int end = mStats.getIndexAfter(mEnd);
for (int i = start; i <= end; i++) {
entry = mStats.getValues(i, entry);
lastTime = entry.bucketStart + entry.bucketDuration;
@@ -206,9 +216,6 @@ public class ChartNetworkSeriesView extends View {
totalData += entry.rxBytes + entry.txBytes;
}
// skip if beyond view
if (x > width) break;
lastX = x;
lastY = y;
}
@@ -284,13 +291,24 @@ public class ChartNetworkSeriesView extends View {
}
public long getMaxVisible() {
return mEstimateVisible ? mMaxEstimate : mMax;
final long maxVisible = mEstimateVisible ? mMaxEstimate : mMax;
if (maxVisible <= 0 && mStats != null) {
// haven't generated path yet; fall back to raw data
final NetworkStatsHistory.Entry entry = mStats.getValues(mStart, mEnd, null);
return entry.rxBytes + entry.txBytes;
} else {
return maxVisible;
}
}
@Override
protected void onDraw(Canvas canvas) {
int save;
if (!mPathValid) {
generatePath();
}
final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);

View File

@@ -76,6 +76,8 @@ public class ChartSweepView extends View {
private ChartSweepView mValidAfterDynamic;
private ChartSweepView mValidBeforeDynamic;
private float mLabelOffset;
private Paint mOutlinePaint = new Paint();
public static final int HORIZONTAL = 0;
@@ -230,12 +232,44 @@ public class ChartSweepView extends View {
private void invalidateLabel() {
if (mLabelTemplate != null && mAxis != null) {
mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
invalidateLabelOffset();
invalidate();
} else {
mLabelValue = mValue;
}
}
/**
* When overlapping with neighbor, split difference and push label.
*/
public void invalidateLabelOffset() {
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 != mLabelOffset) {
mLabelOffset = labelOffset;
invalidate();
if (mValidAfterDynamic != null) mValidAfterDynamic.invalidateLabelOffset();
if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidateLabelOffset();
}
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
@@ -566,6 +600,12 @@ public class ChartSweepView extends View {
mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
invalidateLabelOffset();
}
@Override
protected void onDraw(Canvas canvas) {
final int width = getWidth();
@@ -575,36 +615,11 @@ public class ChartSweepView extends View {
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) {
final int count = canvas.save();
{
canvas.translate(mContentOffset.left, mContentOffset.top + labelOffset);
canvas.translate(mContentOffset.left, mContentOffset.top + mLabelOffset);
mLabelLayout.draw(canvas);
}
canvas.restoreToCount(count);

View File

@@ -31,14 +31,14 @@ public class InvertedChartAxis implements ChartAxis {
}
/** {@inheritDoc} */
public void setBounds(long min, long max) {
mWrapped.setBounds(min, max);
public boolean setBounds(long min, long max) {
return mWrapped.setBounds(min, max);
}
/** {@inheritDoc} */
public void setSize(float size) {
public boolean setSize(float size) {
mSize = size;
mWrapped.setSize(size);
return mWrapped.setSize(size);
}
/** {@inheritDoc} */

View File

@@ -40,7 +40,7 @@ import java.util.ArrayList;
*/
public class PieChartView extends View {
public static final String TAG = "PieChartView";
public static final boolean LOGD = true;
public static final boolean LOGD = false;
private ArrayList<Slice> mSlices = Lists.newArrayList();