Merge "Data usage performance, bugfixes."
This commit is contained in:
@@ -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">
|
||||||
|
|
||||||
|
@@ -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">
|
||||||
|
@@ -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" />
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
|
@@ -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();
|
final UidDetail cachedDetail = provider.getUidDetail(item.uids[0], false);
|
||||||
detail.icon = info.loadIcon(pm);
|
if (cachedDetail != null) {
|
||||||
} else if (length > 1) {
|
bindView(cachedDetail, target);
|
||||||
detail.detailLabels = new CharSequence[length];
|
} else {
|
||||||
for (int i = 0; i < length; i++) {
|
target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor(
|
||||||
final String packageName = packageNames[i];
|
AsyncTask.THREAD_POOL_EXECUTOR));
|
||||||
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)) {
|
private static void bindView(UidDetail detail, View target) {
|
||||||
detail.label = Integer.toString(uid);
|
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");
|
||||||
}
|
}
|
||||||
|
27
src/com/android/settings/net/ChartData.java
Normal file
27
src/com/android/settings/net/ChartData.java
Normal 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;
|
||||||
|
}
|
137
src/com/android/settings/net/ChartDataLoader.java
Normal file
137
src/com/android/settings/net/ChartDataLoader.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
src/com/android/settings/net/UidDetail.java
Normal file
25
src/com/android/settings/net/UidDetail.java
Normal 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;
|
||||||
|
}
|
110
src/com/android/settings/net/UidDetailProvider.java
Normal file
110
src/com/android/settings/net/UidDetailProvider.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
@@ -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) {
|
||||||
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} */
|
||||||
|
@@ -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);
|
||||||
|
|
||||||
|
@@ -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);
|
||||||
|
@@ -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} */
|
||||||
|
@@ -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();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user