Merge "Data usage performance, bugfixes."

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

View File

@@ -17,8 +17,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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();