summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/layout/data_usage_cycles.xml3
-rw-r--r--res/layout/data_usage_header.xml2
-rw-r--r--res/layout/data_usage_item.xml4
-rw-r--r--res/values/strings.xml8
-rw-r--r--src/com/android/settings/DataUsageSummary.java355
-rw-r--r--src/com/android/settings/net/ChartData.java27
-rw-r--r--src/com/android/settings/net/ChartDataLoader.java137
-rw-r--r--src/com/android/settings/net/NetworkPolicyEditor.java46
-rw-r--r--src/com/android/settings/net/UidDetail.java25
-rw-r--r--src/com/android/settings/net/UidDetailProvider.java110
-rw-r--r--src/com/android/settings/widget/ChartAxis.java4
-rw-r--r--src/com/android/settings/widget/ChartDataUsageView.java75
-rw-r--r--src/com/android/settings/widget/ChartNetworkSeriesView.java46
-rw-r--r--src/com/android/settings/widget/ChartSweepView.java67
-rw-r--r--src/com/android/settings/widget/InvertedChartAxis.java8
-rw-r--r--src/com/android/settings/widget/PieChartView.java2
16 files changed, 660 insertions, 259 deletions
diff --git a/res/layout/data_usage_cycles.xml b/res/layout/data_usage_cycles.xml
index 459ef6ed3..136fec634 100644
--- a/res/layout/data_usage_cycles.xml
+++ b/res/layout/data_usage_cycles.xml
@@ -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">
diff --git a/res/layout/data_usage_header.xml b/res/layout/data_usage_header.xml
index 9dcb45637..b27d88ffa 100644
--- a/res/layout/data_usage_header.xml
+++ b/res/layout/data_usage_header.xml
@@ -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">
diff --git a/res/layout/data_usage_item.xml b/res/layout/data_usage_item.xml
index 7808173af..fac3c6bdb 100644
--- a/res/layout/data_usage_item.xml
+++ b/res/layout/data_usage_item.xml
@@ -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" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1874107dc..c59ba49d5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -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>
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 417c52543..0e080750a 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -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);
- }
-
- // bind chart to historical stats
- mChart.bindNetworkStats(mHistory);
+ // 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);
- // 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();
-
- final UidDetail detail = new UidDetail();
- detail.label = pm.getNameForUid(uid);
- detail.icon = pm.getDefaultActivityIcon();
+ private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> {
+ private final UidDetailProvider mProvider;
+ private final AppUsageItem mItem;
+ private final View mTarget;
+
+ private UidDetailTask(UidDetailProvider provider, AppUsageItem item, View target) {
+ mProvider = checkNotNull(provider);
+ mItem = checkNotNull(item);
+ mTarget = checkNotNull(target);
+ }
+
+ public static void bindView(
+ UidDetailProvider provider, AppUsageItem item, View target) {
+ final UidDetailTask existing = (UidDetailTask) target.getTag();
+ if (existing != null) {
+ existing.cancel(false);
+ }
- // 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;
+ 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));
+ }
}
- // otherwise fall back to using packagemanager labels
- final String[] packageNames = pm.getPackagesForUid(uid);
- final int length = packageNames != null ? packageNames.length : 0;
+ 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);
- 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);
- }
- }
+ if (detail != null) {
+ icon.setImageDrawable(detail.icon);
+ title.setText(detail.label);
+ } else {
+ icon.setImageDrawable(null);
+ title.setText(null);
}
- } catch (NameNotFoundException e) {
}
- if (TextUtils.isEmpty(detail.label)) {
- detail.label = Integer.toString(uid);
+ @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");
}
diff --git a/src/com/android/settings/net/ChartData.java b/src/com/android/settings/net/ChartData.java
new file mode 100644
index 000000000..0b8969e84
--- /dev/null
+++ b/src/com/android/settings/net/ChartData.java
@@ -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;
+}
diff --git a/src/com/android/settings/net/ChartDataLoader.java b/src/com/android/settings/net/ChartDataLoader.java
new file mode 100644
index 000000000..09e6e3b6e
--- /dev/null
+++ b/src/com/android/settings/net/ChartDataLoader.java
@@ -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;
+ }
+ }
+}
diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java
index 161e6ee57..bb5a2c37d 100644
--- a/src/com/android/settings/net/NetworkPolicyEditor.java
+++ b/src/com/android/settings/net/NetworkPolicyEditor.java
@@ -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;
}
}
}
diff --git a/src/com/android/settings/net/UidDetail.java b/src/com/android/settings/net/UidDetail.java
new file mode 100644
index 000000000..fd44d47b8
--- /dev/null
+++ b/src/com/android/settings/net/UidDetail.java
@@ -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;
+}
diff --git a/src/com/android/settings/net/UidDetailProvider.java b/src/com/android/settings/net/UidDetailProvider.java
new file mode 100644
index 000000000..9eac801fe
--- /dev/null
+++ b/src/com/android/settings/net/UidDetailProvider.java
@@ -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;
+ }
+}
diff --git a/src/com/android/settings/widget/ChartAxis.java b/src/com/android/settings/widget/ChartAxis.java
index d3d499c7b..515fe6634 100644
--- a/src/com/android/settings/widget/ChartAxis.java
+++ b/src/com/android/settings/widget/ChartAxis.java
@@ -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);
diff --git a/src/com/android/settings/widget/ChartDataUsageView.java b/src/com/android/settings/widget/ChartDataUsageView.java
index cb9c8d7fe..e831cc151 100644
--- a/src/com/android/settings/widget/ChartDataUsageView.java
+++ b/src/com/android/settings/widget/ChartDataUsageView.java
@@ -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);
}
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mMin, mMax, mSize);
+ }
+
/** {@inheritDoc} */
- public void setBounds(long min, long max) {
- mMin = min;
- mMax = max;
+ public boolean setBounds(long min, long max) {
+ if (mMin != min || mMax != max) {
+ mMin = min;
+ mMax = max;
+ return true;
+ } else {
+ return false;
+ }
}
/** {@inheritDoc} */
- public void setSize(float size) {
- this.mSize = size;
+ 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;
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mMin, mMax, mSize);
+ }
+
/** {@inheritDoc} */
- public void setBounds(long min, long max) {
- mMin = min;
- mMax = max;
+ public boolean setBounds(long min, long max) {
+ if (mMin != min || mMax != max) {
+ mMin = min;
+ mMax = max;
+ return true;
+ } else {
+ return false;
+ }
}
/** {@inheritDoc} */
- public void setSize(float size) {
- mSize = size;
+ public boolean setSize(float size) {
+ if (mSize != size) {
+ mSize = size;
+ return true;
+ } else {
+ return false;
+ }
}
/** {@inheritDoc} */
diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java
index f0ccc1bd9..c4f45b0ac 100644
--- a/src/com/android/settings/widget/ChartNetworkSeriesView.java
+++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java
@@ -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);
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 7b6d88754..2190588b2 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -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();
@@ -567,6 +601,12 @@ public class ChartSweepView extends View {
}
@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();
final int height = getHeight();
@@ -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);
diff --git a/src/com/android/settings/widget/InvertedChartAxis.java b/src/com/android/settings/widget/InvertedChartAxis.java
index 7dcc78a02..2d820d972 100644
--- a/src/com/android/settings/widget/InvertedChartAxis.java
+++ b/src/com/android/settings/widget/InvertedChartAxis.java
@@ -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} */
diff --git a/src/com/android/settings/widget/PieChartView.java b/src/com/android/settings/widget/PieChartView.java
index 85d45a259..6765733eb 100644
--- a/src/com/android/settings/widget/PieChartView.java
+++ b/src/com/android/settings/widget/PieChartView.java
@@ -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();