diff options
-rw-r--r-- | res/layout/data_usage_cycles.xml | 3 | ||||
-rw-r--r-- | res/layout/data_usage_header.xml | 2 | ||||
-rw-r--r-- | res/layout/data_usage_item.xml | 4 | ||||
-rw-r--r-- | res/values/strings.xml | 8 | ||||
-rw-r--r-- | src/com/android/settings/DataUsageSummary.java | 355 | ||||
-rw-r--r-- | src/com/android/settings/net/ChartData.java | 27 | ||||
-rw-r--r-- | src/com/android/settings/net/ChartDataLoader.java | 137 | ||||
-rw-r--r-- | src/com/android/settings/net/NetworkPolicyEditor.java | 46 | ||||
-rw-r--r-- | src/com/android/settings/net/UidDetail.java | 25 | ||||
-rw-r--r-- | src/com/android/settings/net/UidDetailProvider.java | 110 | ||||
-rw-r--r-- | src/com/android/settings/widget/ChartAxis.java | 4 | ||||
-rw-r--r-- | src/com/android/settings/widget/ChartDataUsageView.java | 75 | ||||
-rw-r--r-- | src/com/android/settings/widget/ChartNetworkSeriesView.java | 46 | ||||
-rw-r--r-- | src/com/android/settings/widget/ChartSweepView.java | 67 | ||||
-rw-r--r-- | src/com/android/settings/widget/InvertedChartAxis.java | 8 | ||||
-rw-r--r-- | src/com/android/settings/widget/PieChartView.java | 2 |
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(); |