Data usage performance, bugfixes.

Optimize launch times by removing unneeded extra work, including
reloading data and tightening chart invalidation.  Fix invalidation
storm when sweeps overlap.  Move chart history into loader instead of
blocking main thread.

Disable "Split 4G" mode until telephony support is ready, and combine
any existing split policies.

Async loading of application details.  Remove alpha transitions to
speed up on some hardware.  Hide menus in detail mode.  Delay kicking
off force-poll.  Fix inset padding on large devices.

Bug: 5284321, 5273918, 5263056
Change-Id: I746d79c05e2a6ea97bbdbdc5d807e208328d1373
This commit is contained in:
Jeff Sharkey
2011-09-11 17:29:49 -07:00
parent a3fb4572dd
commit b98c55bd09
16 changed files with 663 additions and 262 deletions

View File

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

View File

@@ -17,8 +17,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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:orientation="vertical"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false"> android:clipToPadding="false">

View File

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

View File

@@ -3485,13 +3485,13 @@ found in the list of installed applications.</string>
<!-- Body of dialog shown to request confirmation that mobile data will be disabled. [CHAR LIMIT=NONE] --> <!-- 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> <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] --> <!-- 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] --> <!-- 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] --> <!-- 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] --> <!-- 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] --> <!-- Tab title for showing Wi-Fi data usage. [CHAR LIMIT=10] -->
<string name="data_usage_tab_wifi">Wi-Fi</string> <string name="data_usage_tab_wifi">Wi-Fi</string>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,9 +26,9 @@ import android.text.SpannableStringBuilder;
public interface ChartAxis { public interface ChartAxis {
/** Set range of raw values this axis should cover. */ /** 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. */ /** 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. */ /** Convert raw value into screen point. */
public float convertToPoint(long value); public float convertToPoint(long value);

View File

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

View File

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

View File

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

View File

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

View File

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