Merge "Data usage performance, bugfixes."
This commit is contained in:
@@ -17,8 +17,9 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/cycles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="40dip"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="@*android:dimen/preference_item_padding_side"
|
||||
android:paddingRight="@*android:dimen/preference_item_padding_side">
|
||||
|
||||
|
@@ -17,8 +17,6 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@*android:dimen/preference_fragment_padding_side"
|
||||
android:paddingRight="@*android:dimen/preference_fragment_padding_side"
|
||||
android:orientation="vertical"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
@@ -16,9 +16,7 @@
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@*android:dimen/preference_fragment_padding_side"
|
||||
android:paddingRight="@*android:dimen/preference_fragment_padding_side">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include layout="@layout/app_percentage_item" />
|
||||
|
||||
|
@@ -3485,13 +3485,13 @@ found in the list of installed applications.</string>
|
||||
<!-- Body of dialog shown to request confirmation that mobile data will be disabled. [CHAR LIMIT=NONE] -->
|
||||
<string name="data_usage_disable_mobile">Disable mobile data?</string>
|
||||
<!-- Checkbox label that will disable mobile network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
|
||||
<string name="data_usage_disable_mobile_limit">Disable mobile data at limit</string>
|
||||
<string name="data_usage_disable_mobile_limit">Set mobile data limit</string>
|
||||
<!-- Checkbox label that will disable 4G network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
|
||||
<string name="data_usage_disable_4g_limit">Disable 4G data at limit</string>
|
||||
<string name="data_usage_disable_4g_limit">Set 4G data limit</string>
|
||||
<!-- Checkbox label that will disable 2G-3G network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
|
||||
<string name="data_usage_disable_3g_limit">Disable 2G-3G data at limit</string>
|
||||
<string name="data_usage_disable_3g_limit">Set 2G-3G data limit</string>
|
||||
<!-- Checkbox label that will disable Wi-Fi network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
|
||||
<string name="data_usage_disable_wifi_limit">Disable Wi-Fi data at limit</string>
|
||||
<string name="data_usage_disable_wifi_limit">Set Wi-Fi data limit</string>
|
||||
|
||||
<!-- Tab title for showing Wi-Fi data usage. [CHAR LIMIT=10] -->
|
||||
<string name="data_usage_tab_wifi">Wi-Fi</string>
|
||||
|
@@ -25,11 +25,6 @@ import static android.net.NetworkPolicyManager.POLICY_NONE;
|
||||
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
|
||||
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
|
||||
import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
|
||||
import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.SET_FOREGROUND;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
|
||||
import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
|
||||
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
|
||||
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
|
||||
import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
|
||||
@@ -43,6 +38,7 @@ import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
|
||||
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
|
||||
import static android.text.format.Time.TIMEZONE_UTC;
|
||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
import static com.android.internal.util.Preconditions.checkNotNull;
|
||||
import static com.android.settings.Utils.prepareCustomPreferencesList;
|
||||
|
||||
import android.animation.LayoutTransition;
|
||||
@@ -58,15 +54,11 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.INetworkPolicyManager;
|
||||
import android.net.INetworkStatsService;
|
||||
@@ -123,9 +115,12 @@ import android.widget.TextView;
|
||||
|
||||
import com.android.internal.telephony.Phone;
|
||||
import com.android.settings.drawable.InsetBoundsDrawable;
|
||||
import com.android.settings.drawable.DrawableWrapper;
|
||||
import com.android.settings.net.ChartData;
|
||||
import com.android.settings.net.ChartDataLoader;
|
||||
import com.android.settings.net.NetworkPolicyEditor;
|
||||
import com.android.settings.net.SummaryForAllUidLoader;
|
||||
import com.android.settings.net.UidDetail;
|
||||
import com.android.settings.net.UidDetailProvider;
|
||||
import com.android.settings.widget.ChartDataUsageView;
|
||||
import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
|
||||
import com.android.settings.widget.PieChartView;
|
||||
@@ -165,7 +160,8 @@ public class DataUsageSummary extends Fragment {
|
||||
private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
|
||||
private static final String TAG_APP_DETAILS = "appDetails";
|
||||
|
||||
private static final int LOADER_SUMMARY = 2;
|
||||
private static final int LOADER_CHART_DATA = 2;
|
||||
private static final int LOADER_SUMMARY = 3;
|
||||
|
||||
private static final long KB_IN_BYTES = 1024;
|
||||
private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
|
||||
@@ -188,6 +184,9 @@ public class DataUsageSummary extends Fragment {
|
||||
private ListView mListView;
|
||||
private DataUsageAdapter mAdapter;
|
||||
|
||||
/** Distance to inset content from sides, when needed. */
|
||||
private int mInsetSide = 0;
|
||||
|
||||
private ViewGroup mHeader;
|
||||
|
||||
private ViewGroup mNetworkSwitchesContainer;
|
||||
@@ -220,7 +219,8 @@ public class DataUsageSummary extends Fragment {
|
||||
private boolean mShowWifi = false;
|
||||
private boolean mShowEthernet = false;
|
||||
|
||||
private NetworkTemplate mTemplate = null;
|
||||
private NetworkTemplate mTemplate;
|
||||
private ChartData mChartData;
|
||||
|
||||
private int[] mAppDetailUids = null;
|
||||
|
||||
@@ -228,11 +228,6 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
private NetworkPolicyEditor mPolicyEditor;
|
||||
|
||||
private NetworkStatsHistory mHistory;
|
||||
private NetworkStatsHistory mDetailHistory;
|
||||
private NetworkStatsHistory mDetailHistoryDefault;
|
||||
private NetworkStatsHistory mDetailHistoryForeground;
|
||||
|
||||
private String mCurrentTab = null;
|
||||
private String mIntentTab = null;
|
||||
|
||||
@@ -242,6 +237,8 @@ public class DataUsageSummary extends Fragment {
|
||||
/** Flag used to ignore listeners during binding. */
|
||||
private boolean mBinding;
|
||||
|
||||
private UidDetailProvider mUidDetailProvider;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -273,25 +270,39 @@ public class DataUsageSummary extends Fragment {
|
||||
final Context context = inflater.getContext();
|
||||
final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
|
||||
|
||||
mUidDetailProvider = new UidDetailProvider(context);
|
||||
|
||||
mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
|
||||
mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container);
|
||||
mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
|
||||
mListView = (ListView) view.findViewById(android.R.id.list);
|
||||
|
||||
// decide if we need to manually inset our content, or if we should rely
|
||||
// on parent container for inset.
|
||||
final boolean shouldInset = mListView.getScrollBarStyle()
|
||||
== View.SCROLLBARS_OUTSIDE_OVERLAY;
|
||||
if (shouldInset) {
|
||||
mInsetSide = view.getResources().getDimensionPixelOffset(
|
||||
com.android.internal.R.dimen.preference_fragment_padding_side);
|
||||
} else {
|
||||
mInsetSide = 0;
|
||||
}
|
||||
|
||||
// adjust padding around tabwidget as needed
|
||||
prepareCustomPreferencesList(container, view, mListView, true);
|
||||
|
||||
// inset selector and divider drawables
|
||||
final int insetSide = view.getResources().getDimensionPixelOffset(
|
||||
com.android.internal.R.dimen.preference_fragment_padding_side);
|
||||
insetListViewDrawables(mListView, insetSide);
|
||||
|
||||
mTabHost.setup();
|
||||
mTabHost.setOnTabChangedListener(mTabListener);
|
||||
|
||||
mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
|
||||
mListView.addHeaderView(mHeader, null, false);
|
||||
|
||||
if (mInsetSide > 0) {
|
||||
// inset selector and divider drawables
|
||||
insetListViewDrawables(mListView, mInsetSide);
|
||||
mHeader.setPadding(mInsetSide, 0, mInsetSide, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// bind network switches
|
||||
mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById(
|
||||
@@ -346,7 +357,7 @@ public class DataUsageSummary extends Fragment {
|
||||
// only assign layout transitions once first layout is finished
|
||||
mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
|
||||
|
||||
mAdapter = new DataUsageAdapter();
|
||||
mAdapter = new DataUsageAdapter(mUidDetailProvider, mInsetSide);
|
||||
mListView.setOnItemClickListener(mListListener);
|
||||
mListView.setAdapter(mAdapter);
|
||||
|
||||
@@ -370,7 +381,10 @@ public class DataUsageSummary extends Fragment {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
// wait a few seconds before kicking off
|
||||
Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS);
|
||||
mStatsService.forceUpdate();
|
||||
} catch (InterruptedException e) {
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
return null;
|
||||
@@ -382,7 +396,7 @@ public class DataUsageSummary extends Fragment {
|
||||
updateBody();
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -393,21 +407,23 @@ public class DataUsageSummary extends Fragment {
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
final Context context = getActivity();
|
||||
final boolean appDetailMode = isAppDetailMode();
|
||||
|
||||
mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
|
||||
mMenuDataRoaming.setVisible(hasMobileRadio(context));
|
||||
mMenuDataRoaming.setVisible(hasMobileRadio(context) && !appDetailMode);
|
||||
mMenuDataRoaming.setChecked(getDataRoaming());
|
||||
|
||||
mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
|
||||
mMenuRestrictBackground.setVisible(!appDetailMode);
|
||||
mMenuRestrictBackground.setChecked(getRestrictBackground());
|
||||
|
||||
final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
|
||||
split4g.setVisible(hasMobile4gRadio(context));
|
||||
split4g.setVisible(hasMobile4gRadio(context) && !appDetailMode);
|
||||
split4g.setChecked(isMobilePolicySplit());
|
||||
|
||||
final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
|
||||
if (hasWifiRadio(context) && hasMobileRadio(context)) {
|
||||
showWifi.setVisible(true);
|
||||
showWifi.setVisible(!appDetailMode);
|
||||
showWifi.setChecked(mShowWifi);
|
||||
} else {
|
||||
showWifi.setVisible(false);
|
||||
@@ -416,7 +432,7 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
|
||||
if (hasEthernet(context) && hasMobileRadio(context)) {
|
||||
showEthernet.setVisible(true);
|
||||
showEthernet.setVisible(!appDetailMode);
|
||||
showEthernet.setChecked(mShowEthernet);
|
||||
} else {
|
||||
showEthernet.setVisible(false);
|
||||
@@ -478,6 +494,9 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
mDataEnabledView = null;
|
||||
mDisableAtLimitView = null;
|
||||
|
||||
mUidDetailProvider.clearCache();
|
||||
mUidDetailProvider = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -495,6 +514,7 @@ public class DataUsageSummary extends Fragment {
|
||||
final LayoutTransition chartTransition = buildLayoutTransition();
|
||||
chartTransition.setStartDelay(LayoutTransition.APPEARING, 0);
|
||||
chartTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
|
||||
chartTransition.setAnimator(LayoutTransition.APPEARING, null);
|
||||
chartTransition.setAnimator(LayoutTransition.DISAPPEARING, null);
|
||||
mChart.setLayoutTransition(chartTransition);
|
||||
}
|
||||
@@ -536,17 +556,14 @@ public class DataUsageSummary extends Fragment {
|
||||
mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE);
|
||||
if (mIntentTab != null) {
|
||||
if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) {
|
||||
// already hit updateBody() when added; ignore
|
||||
updateBody();
|
||||
} else {
|
||||
mTabHost.setCurrentTabByTag(mIntentTab);
|
||||
}
|
||||
mIntentTab = null;
|
||||
} else {
|
||||
if (mTabHost.getCurrentTab() == 0) {
|
||||
updateBody();
|
||||
} else {
|
||||
mTabHost.setCurrentTab(0);
|
||||
}
|
||||
// already hit updateBody() when added; ignore
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,6 +600,7 @@ public class DataUsageSummary extends Fragment {
|
||||
*/
|
||||
private void updateBody() {
|
||||
mBinding = true;
|
||||
if (!isAdded()) return;
|
||||
|
||||
final Context context = getActivity();
|
||||
final String currentTab = mTabHost.getCurrentTabTag();
|
||||
@@ -636,25 +654,14 @@ public class DataUsageSummary extends Fragment {
|
||||
throw new IllegalStateException("unknown tab: " + currentTab);
|
||||
}
|
||||
|
||||
try {
|
||||
// load stats for current template
|
||||
mHistory = mStatsService.getHistoryForNetwork(
|
||||
mTemplate, FIELD_RX_BYTES | FIELD_TX_BYTES);
|
||||
} catch (RemoteException e) {
|
||||
// since we can't do much without policy or history, and we don't
|
||||
// want to leave with half-baked UI, we bail hard.
|
||||
throw new RuntimeException("problem reading network policy or stats", e);
|
||||
}
|
||||
// kick off loader for network history
|
||||
// TODO: consider chaining two loaders together instead of reloading
|
||||
// network history when showing app detail.
|
||||
getLoaderManager().restartLoader(LOADER_CHART_DATA,
|
||||
ChartDataLoader.buildArgs(mTemplate, mAppDetailUids), mChartDataCallbacks);
|
||||
|
||||
// bind chart to historical stats
|
||||
mChart.bindNetworkStats(mHistory);
|
||||
|
||||
// only update policy when switching tabs
|
||||
updatePolicy(tabChanged);
|
||||
updateAppDetail();
|
||||
|
||||
// force scroll to top of body
|
||||
mListView.smoothScrollToPosition(0);
|
||||
// detail mode can change visible menus, invalidate
|
||||
getActivity().invalidateOptionsMenu();
|
||||
|
||||
mBinding = false;
|
||||
}
|
||||
@@ -683,10 +690,6 @@ public class DataUsageSummary extends Fragment {
|
||||
mAppDetail.setVisibility(View.GONE);
|
||||
mCycleAdapter.setChangeVisible(true);
|
||||
|
||||
mDetailHistory = null;
|
||||
mDetailHistoryDefault = null;
|
||||
mDetailHistoryForeground = null;
|
||||
|
||||
// hide detail stats when not in detail mode
|
||||
mChart.bindDetailNetworkStats(null);
|
||||
return;
|
||||
@@ -697,7 +700,7 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
// show icon and all labels appearing under this app
|
||||
final int primaryUid = getAppDetailPrimaryUid();
|
||||
final UidDetail detail = resolveDetailForUid(context, primaryUid);
|
||||
final UidDetail detail = mUidDetailProvider.getUidDetail(primaryUid, true);
|
||||
mAppIcon.setImageDrawable(detail.icon);
|
||||
|
||||
mAppTitles.removeAllViews();
|
||||
@@ -725,7 +728,6 @@ public class DataUsageSummary extends Fragment {
|
||||
mAppSettings.setEnabled(false);
|
||||
}
|
||||
|
||||
updateDetailHistory();
|
||||
updateDetailData();
|
||||
|
||||
if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid)
|
||||
@@ -743,54 +745,6 @@ public class DataUsageSummary extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update {@link #mDetailHistory} and related values based on
|
||||
* {@link #mAppDetailUids}.
|
||||
*/
|
||||
private void updateDetailHistory() {
|
||||
try {
|
||||
mDetailHistoryDefault = null;
|
||||
mDetailHistoryForeground = null;
|
||||
|
||||
// load stats for current uid and template
|
||||
for (int uid : mAppDetailUids) {
|
||||
mDetailHistoryDefault = collectHistoryForUid(
|
||||
uid, SET_DEFAULT, mDetailHistoryDefault);
|
||||
mDetailHistoryForeground = collectHistoryForUid(
|
||||
uid, SET_FOREGROUND, mDetailHistoryForeground);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// since we can't do much without history, and we don't want to
|
||||
// leave with half-baked UI, we bail hard.
|
||||
throw new RuntimeException("problem reading network stats", e);
|
||||
}
|
||||
|
||||
mDetailHistory = new NetworkStatsHistory(mDetailHistoryForeground.getBucketDuration());
|
||||
mDetailHistory.recordEntireHistory(mDetailHistoryDefault);
|
||||
mDetailHistory.recordEntireHistory(mDetailHistoryForeground);
|
||||
|
||||
// bind chart to historical stats
|
||||
mChart.bindDetailNetworkStats(mDetailHistory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect {@link NetworkStatsHistory} for the requested UID, combining with
|
||||
* an existing {@link NetworkStatsHistory} if provided.
|
||||
*/
|
||||
private NetworkStatsHistory collectHistoryForUid(
|
||||
int uid, int set, NetworkStatsHistory existing)
|
||||
throws RemoteException {
|
||||
final NetworkStatsHistory history = mStatsService.getHistoryForUid(
|
||||
mTemplate, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
|
||||
|
||||
if (existing != null) {
|
||||
existing.recordEntireHistory(history);
|
||||
return existing;
|
||||
} else {
|
||||
return history;
|
||||
}
|
||||
}
|
||||
|
||||
private void setPolicyCycleDay(int cycleDay) {
|
||||
if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
|
||||
mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
|
||||
@@ -956,21 +910,22 @@ public class DataUsageSummary extends Fragment {
|
||||
mCycleAdapter.clear();
|
||||
|
||||
final Context context = mCycleSpinner.getContext();
|
||||
long historyStart = mHistory.getStart();
|
||||
long historyEnd = mHistory.getEnd();
|
||||
|
||||
if (historyStart == Long.MAX_VALUE || historyEnd == Long.MIN_VALUE) {
|
||||
historyStart = System.currentTimeMillis();
|
||||
historyEnd = System.currentTimeMillis();
|
||||
long historyStart = Long.MAX_VALUE;
|
||||
long historyEnd = Long.MIN_VALUE;
|
||||
if (mChartData != null) {
|
||||
historyStart = mChartData.network.getStart();
|
||||
historyEnd = mChartData.network.getEnd();
|
||||
}
|
||||
|
||||
if (historyStart == Long.MAX_VALUE) historyStart = System.currentTimeMillis();
|
||||
if (historyEnd == Long.MIN_VALUE) historyEnd = System.currentTimeMillis();
|
||||
|
||||
boolean hasCycles = false;
|
||||
if (policy != null) {
|
||||
// find the next cycle boundary
|
||||
long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
|
||||
|
||||
int guardCount = 0;
|
||||
|
||||
// walk backwards, generating all valid cycle ranges
|
||||
while (cycleEnd > historyStart) {
|
||||
final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
|
||||
@@ -979,12 +934,6 @@ public class DataUsageSummary extends Fragment {
|
||||
mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
|
||||
cycleEnd = cycleStart;
|
||||
hasCycles = true;
|
||||
|
||||
// TODO: remove this guard once we have better testing
|
||||
if (guardCount++ > 50) {
|
||||
Log.wtf(TAG, "stuck generating ranges for historyStart=" + historyStart
|
||||
+ ", historyEnd=" + historyEnd + " and policy=" + policy);
|
||||
}
|
||||
}
|
||||
|
||||
// one last cycle entry to modify policy cycle day
|
||||
@@ -1075,7 +1024,7 @@ public class DataUsageSummary extends Fragment {
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final Context context = view.getContext();
|
||||
final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
|
||||
final UidDetail detail = resolveDetailForUid(context, app.uids[0]);
|
||||
final UidDetail detail = mUidDetailProvider.getUidDetail(app.uids[0], true);
|
||||
AppDetailsFragment.show(DataUsageSummary.this, app.uids, detail.label);
|
||||
}
|
||||
};
|
||||
@@ -1128,11 +1077,11 @@ public class DataUsageSummary extends Fragment {
|
||||
final Context context = getActivity();
|
||||
|
||||
NetworkStatsHistory.Entry entry = null;
|
||||
if (isAppDetailMode() && mDetailHistory != null) {
|
||||
if (isAppDetailMode() && mChartData != null && mChartData.detail != null) {
|
||||
// bind foreground/background to piechart and labels
|
||||
entry = mDetailHistoryDefault.getValues(start, end, now, entry);
|
||||
entry = mChartData.detailDefault.getValues(start, end, now, entry);
|
||||
final long defaultBytes = entry.rxBytes + entry.txBytes;
|
||||
entry = mDetailHistoryForeground.getValues(start, end, now, entry);
|
||||
entry = mChartData.detailForeground.getValues(start, end, now, entry);
|
||||
final long foregroundBytes = entry.rxBytes + entry.txBytes;
|
||||
|
||||
mAppPieChart.setOriginAngle(175);
|
||||
@@ -1147,17 +1096,18 @@ public class DataUsageSummary extends Fragment {
|
||||
mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes));
|
||||
|
||||
// and finally leave with summary data for label below
|
||||
entry = mDetailHistory.getValues(start, end, now, null);
|
||||
entry = mChartData.detail.getValues(start, end, now, null);
|
||||
|
||||
getLoaderManager().destroyLoader(LOADER_SUMMARY);
|
||||
|
||||
} else {
|
||||
entry = mHistory.getValues(start, end, now, null);
|
||||
if (mChartData != null) {
|
||||
entry = mChartData.network.getValues(start, end, now, null);
|
||||
}
|
||||
|
||||
// kick off loader for detailed stats
|
||||
// TODO: delay loader until animation is finished
|
||||
getLoaderManager().restartLoader(LOADER_SUMMARY,
|
||||
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
|
||||
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
|
||||
}
|
||||
|
||||
final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
|
||||
@@ -1168,7 +1118,38 @@ public class DataUsageSummary extends Fragment {
|
||||
getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
|
||||
}
|
||||
|
||||
private final LoaderCallbacks<NetworkStats> mSummaryForAllUid = new LoaderCallbacks<
|
||||
private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks<
|
||||
ChartData>() {
|
||||
/** {@inheritDoc} */
|
||||
public Loader<ChartData> onCreateLoader(int id, Bundle args) {
|
||||
return new ChartDataLoader(getActivity(), mStatsService, args);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
|
||||
mChartData = data;
|
||||
mChart.bindNetworkStats(mChartData.network);
|
||||
mChart.bindDetailNetworkStats(mChartData.detail);
|
||||
|
||||
// calcuate policy cycles based on available data
|
||||
updatePolicy(true);
|
||||
updateAppDetail();
|
||||
|
||||
// force scroll to top of body when showing detail
|
||||
if (mChartData.detail != null) {
|
||||
mListView.smoothScrollToPosition(0);
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void onLoaderReset(Loader<ChartData> loader) {
|
||||
mChartData = null;
|
||||
mChart.bindNetworkStats(null);
|
||||
mChart.bindDetailNetworkStats(null);
|
||||
}
|
||||
};
|
||||
|
||||
private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks<
|
||||
NetworkStats>() {
|
||||
/** {@inheritDoc} */
|
||||
public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
|
||||
@@ -1337,9 +1318,17 @@ public class DataUsageSummary extends Fragment {
|
||||
* Adapter of applications, sorted by total usage descending.
|
||||
*/
|
||||
public static class DataUsageAdapter extends BaseAdapter {
|
||||
private final UidDetailProvider mProvider;
|
||||
private final int mInsetSide;
|
||||
|
||||
private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
|
||||
private long mLargest;
|
||||
|
||||
public DataUsageAdapter(UidDetailProvider provider, int insetSide) {
|
||||
mProvider = checkNotNull(provider);
|
||||
mInsetSide = insetSide;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the given {@link NetworkStats}, or {@code null} to clear list.
|
||||
*/
|
||||
@@ -1401,21 +1390,22 @@ public class DataUsageSummary extends Fragment {
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(parent.getContext()).inflate(
|
||||
R.layout.data_usage_item, parent, false);
|
||||
|
||||
if (mInsetSide > 0) {
|
||||
convertView.setPadding(mInsetSide, 0, mInsetSide, 0);
|
||||
}
|
||||
}
|
||||
|
||||
final Context context = parent.getContext();
|
||||
|
||||
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
|
||||
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
|
||||
final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
|
||||
final ProgressBar progress = (ProgressBar) convertView.findViewById(
|
||||
android.R.id.progress);
|
||||
|
||||
// kick off async load of app details
|
||||
final AppUsageItem item = mItems.get(position);
|
||||
final UidDetail detail = resolveDetailForUid(context, item.uids[0]);
|
||||
UidDetailTask.bindView(mProvider, item, convertView);
|
||||
|
||||
icon.setImageDrawable(detail.icon);
|
||||
title.setText(detail.label);
|
||||
text1.setText(Formatter.formatFileSize(context, item.total));
|
||||
|
||||
final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
|
||||
@@ -1745,66 +1735,64 @@ public class DataUsageSummary extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
public static class UidDetail {
|
||||
public CharSequence label;
|
||||
public CharSequence[] detailLabels;
|
||||
public Drawable icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve best descriptive label for the given UID.
|
||||
* Background task that loads {@link UidDetail}, binding to
|
||||
* {@link DataUsageAdapter} row item when finished.
|
||||
*/
|
||||
public static UidDetail resolveDetailForUid(Context context, int uid) {
|
||||
final Resources res = context.getResources();
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> {
|
||||
private final UidDetailProvider mProvider;
|
||||
private final AppUsageItem mItem;
|
||||
private final View mTarget;
|
||||
|
||||
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();
|
||||
return detail;
|
||||
case TrafficStats.UID_REMOVED:
|
||||
detail.label = res.getString(R.string.data_usage_uninstalled_apps);
|
||||
detail.icon = pm.getDefaultActivityIcon();
|
||||
return detail;
|
||||
private UidDetailTask(UidDetailProvider provider, AppUsageItem item, View target) {
|
||||
mProvider = checkNotNull(provider);
|
||||
mItem = checkNotNull(item);
|
||||
mTarget = checkNotNull(target);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
public static void bindView(
|
||||
UidDetailProvider provider, AppUsageItem item, View target) {
|
||||
final UidDetailTask existing = (UidDetailTask) target.getTag();
|
||||
if (existing != null) {
|
||||
existing.cancel(false);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(detail.label)) {
|
||||
detail.label = Integer.toString(uid);
|
||||
final UidDetail cachedDetail = provider.getUidDetail(item.uids[0], false);
|
||||
if (cachedDetail != null) {
|
||||
bindView(cachedDetail, target);
|
||||
} else {
|
||||
target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor(
|
||||
AsyncTask.THREAD_POOL_EXECUTOR));
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
private static boolean hasMobile4gRadio(Context context) {
|
||||
if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) {
|
||||
return false;
|
||||
}
|
||||
if (TEST_RADIOS) {
|
||||
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.google.android.collect.Lists;
|
||||
import com.google.android.collect.Sets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Utility class to modify list of {@link NetworkPolicy}. Specifically knows
|
||||
@@ -46,6 +48,8 @@ import java.util.ArrayList;
|
||||
public class NetworkPolicyEditor {
|
||||
// TODO: be more robust when missing policies from service
|
||||
|
||||
public static final boolean ENABLE_SPLIT_POLICIES = false;
|
||||
|
||||
private INetworkPolicyManager mPolicyService;
|
||||
private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList();
|
||||
|
||||
@@ -83,6 +87,11 @@ public class NetworkPolicyEditor {
|
||||
mPolicies.add(policy);
|
||||
}
|
||||
|
||||
// force combine any split policies when disabled
|
||||
if (!ENABLE_SPLIT_POLICIES) {
|
||||
modified |= forceMobilePolicyCombined();
|
||||
}
|
||||
|
||||
// when we cleaned policies above, write back changes
|
||||
if (modified) writeAsync();
|
||||
}
|
||||
@@ -161,6 +170,22 @@ public class NetworkPolicyEditor {
|
||||
writeAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any split {@link NetworkPolicy}.
|
||||
*/
|
||||
private boolean forceMobilePolicyCombined() {
|
||||
final HashSet<String> subscriberIds = Sets.newHashSet();
|
||||
for (NetworkPolicy policy : mPolicies) {
|
||||
subscriberIds.add(policy.template.getSubscriberId());
|
||||
}
|
||||
|
||||
boolean modified = false;
|
||||
for (String subscriberId : subscriberIds) {
|
||||
modified |= setMobilePolicySplitInternal(subscriberId, false);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
public boolean isMobilePolicySplit(String subscriberId) {
|
||||
boolean has3g = false;
|
||||
boolean has4g = false;
|
||||
@@ -181,6 +206,18 @@ public class NetworkPolicyEditor {
|
||||
}
|
||||
|
||||
public void setMobilePolicySplit(String subscriberId, boolean split) {
|
||||
if (setMobilePolicySplitInternal(subscriberId, split)) {
|
||||
writeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutate {@link NetworkPolicy} for given subscriber, combining or splitting
|
||||
* the policy as requested.
|
||||
*
|
||||
* @return {@code true} when any {@link NetworkPolicy} was mutated.
|
||||
*/
|
||||
private boolean setMobilePolicySplitInternal(String subscriberId, boolean split) {
|
||||
final boolean beforeSplit = isMobilePolicySplit(subscriberId);
|
||||
|
||||
final NetworkTemplate template3g = buildTemplateMobile3gLower(subscriberId);
|
||||
@@ -189,7 +226,7 @@ public class NetworkPolicyEditor {
|
||||
|
||||
if (split == beforeSplit) {
|
||||
// already in requested state; skip
|
||||
return;
|
||||
return false;
|
||||
|
||||
} else if (beforeSplit && !split) {
|
||||
// combine, picking most restrictive policy
|
||||
@@ -203,7 +240,7 @@ public class NetworkPolicyEditor {
|
||||
mPolicies.add(
|
||||
new NetworkPolicy(templateAll, restrictive.cycleDay, restrictive.warningBytes,
|
||||
restrictive.limitBytes, SNOOZE_NEVER));
|
||||
writeAsync();
|
||||
return true;
|
||||
|
||||
} else if (!beforeSplit && split) {
|
||||
// duplicate existing policy into two rules
|
||||
@@ -215,8 +252,9 @@ public class NetworkPolicyEditor {
|
||||
mPolicies.add(
|
||||
new NetworkPolicy(template4g, policyAll.cycleDay, policyAll.warningBytes,
|
||||
policyAll.limitBytes, SNOOZE_NEVER));
|
||||
writeAsync();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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 {
|
||||
|
||||
/** Set range of raw values this axis should cover. */
|
||||
public void setBounds(long min, long max);
|
||||
public boolean setBounds(long min, long max);
|
||||
/** Set range of screen points this axis should cover. */
|
||||
public void setSize(float size);
|
||||
public boolean setSize(float size);
|
||||
|
||||
/** Convert raw value into screen point. */
|
||||
public float convertToPoint(long value);
|
||||
|
@@ -30,6 +30,7 @@ import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.internal.util.Objects;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.widget.ChartSweepView.OnSweepListener;
|
||||
|
||||
@@ -214,7 +215,8 @@ public class ChartDataUsageView extends ChartView {
|
||||
|
||||
// always show known data and policy lines
|
||||
final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
|
||||
final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
|
||||
final long maxSeries = Math.max(mSeries.getMaxVisible(), mDetailSeries.getMaxVisible());
|
||||
final long maxVisible = Math.max(maxSeries, maxSweep) * 12 / 10;
|
||||
final long maxDefault = Math.max(maxVisible, 2 * GB_IN_BYTES);
|
||||
newMax = Math.max(maxDefault, newMax);
|
||||
|
||||
@@ -222,12 +224,14 @@ public class ChartDataUsageView extends ChartView {
|
||||
if (newMax != mVertMax) {
|
||||
mVertMax = newMax;
|
||||
|
||||
mVert.setBounds(0L, newMax);
|
||||
final boolean changed = mVert.setBounds(0L, newMax);
|
||||
mSweepWarning.setValidRange(0L, newMax);
|
||||
mSweepLimit.setValidRange(0L, newMax);
|
||||
|
||||
mSeries.generatePath();
|
||||
mDetailSeries.generatePath();
|
||||
if (changed) {
|
||||
mSeries.invalidatePath();
|
||||
mDetailSeries.invalidatePath();
|
||||
}
|
||||
|
||||
mGrid.invalidate();
|
||||
|
||||
@@ -263,6 +267,10 @@ public class ChartDataUsageView extends ChartView {
|
||||
interestLine = mSweepLimit.getValue();
|
||||
}
|
||||
|
||||
if (interestLine < 0) {
|
||||
interestLine = Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
final boolean estimateVisible = (maxEstimate >= interestLine * 7 / 10);
|
||||
mSeries.setEstimateVisible(estimateVisible);
|
||||
}
|
||||
@@ -354,8 +362,10 @@ public class ChartDataUsageView extends ChartView {
|
||||
* last "week" of available data, without triggering listener events.
|
||||
*/
|
||||
public void setVisibleRange(long visibleStart, long visibleEnd) {
|
||||
mHoriz.setBounds(visibleStart, visibleEnd);
|
||||
final boolean changed = mHoriz.setBounds(visibleStart, visibleEnd);
|
||||
mGrid.setBounds(visibleStart, visibleEnd);
|
||||
mSeries.setBounds(visibleStart, visibleEnd);
|
||||
mDetailSeries.setBounds(visibleStart, visibleEnd);
|
||||
|
||||
final long validStart = Math.max(visibleStart, getStatsStart());
|
||||
final long validEnd = Math.min(visibleEnd, getStatsEnd());
|
||||
@@ -378,7 +388,10 @@ public class ChartDataUsageView extends ChartView {
|
||||
mSweepRight.setValue(sweepMax);
|
||||
|
||||
requestLayout();
|
||||
mSeries.generatePath();
|
||||
if (changed) {
|
||||
mSeries.invalidatePath();
|
||||
mDetailSeries.invalidatePath();
|
||||
}
|
||||
|
||||
updateVertAxisBounds(null);
|
||||
updateEstimateVisible();
|
||||
@@ -410,15 +423,30 @@ public class ChartDataUsageView extends ChartView {
|
||||
setBounds(currentTime - DateUtils.DAY_IN_MILLIS * 30, currentTime);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void setBounds(long min, long max) {
|
||||
mMin = min;
|
||||
mMax = max;
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(mMin, mMax, mSize);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void setSize(float size) {
|
||||
this.mSize = 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;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@@ -461,15 +489,30 @@ public class ChartDataUsageView extends ChartView {
|
||||
private long mMax;
|
||||
private float mSize;
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void setBounds(long min, long max) {
|
||||
mMin = min;
|
||||
mMax = max;
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(mMin, mMax, mSize);
|
||||
}
|
||||
|
||||
/** {@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;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
|
@@ -58,12 +58,16 @@ public class ChartNetworkSeriesView extends View {
|
||||
private Path mPathFill;
|
||||
private Path mPathEstimate;
|
||||
|
||||
private long mStart;
|
||||
private long mEnd;
|
||||
|
||||
private long mPrimaryLeft;
|
||||
private long mPrimaryRight;
|
||||
|
||||
/** Series will be extended to reach this end time. */
|
||||
private long mEndTime = Long.MIN_VALUE;
|
||||
|
||||
private boolean mPathValid = false;
|
||||
private boolean mEstimateVisible = false;
|
||||
|
||||
private long mMax;
|
||||
@@ -130,13 +134,15 @@ public class ChartNetworkSeriesView extends View {
|
||||
|
||||
public void bindNetworkStats(NetworkStatsHistory stats) {
|
||||
mStats = stats;
|
||||
|
||||
mPathStroke.reset();
|
||||
mPathFill.reset();
|
||||
mPathEstimate.reset();
|
||||
invalidatePath();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setBounds(long start, long end) {
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the range to paint with {@link #mPaintFill}, leaving the remaining
|
||||
* area to be painted with {@link #mPaintFillSecondary}.
|
||||
@@ -147,26 +153,27 @@ public class ChartNetworkSeriesView extends View {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
generatePath();
|
||||
public void invalidatePath() {
|
||||
mPathValid = false;
|
||||
mMax = 0;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase any existing {@link Path} and generate series outline based on
|
||||
* currently bound {@link NetworkStatsHistory} data.
|
||||
*/
|
||||
public void generatePath() {
|
||||
private void generatePath() {
|
||||
if (LOGD) Log.d(TAG, "generatePath()");
|
||||
|
||||
mMax = 0;
|
||||
mPathStroke.reset();
|
||||
mPathFill.reset();
|
||||
mPathEstimate.reset();
|
||||
mPathValid = true;
|
||||
|
||||
// bail when not enough stats to render
|
||||
if (mStats == null || mStats.size() < 2) {
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,7 +192,10 @@ public class ChartNetworkSeriesView extends View {
|
||||
long totalData = 0;
|
||||
|
||||
NetworkStatsHistory.Entry entry = null;
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
|
||||
final int start = mStats.getIndexBefore(mStart);
|
||||
final int end = mStats.getIndexAfter(mEnd);
|
||||
for (int i = start; i <= end; i++) {
|
||||
entry = mStats.getValues(i, entry);
|
||||
|
||||
lastTime = entry.bucketStart + entry.bucketDuration;
|
||||
@@ -206,9 +216,6 @@ public class ChartNetworkSeriesView extends View {
|
||||
totalData += entry.rxBytes + entry.txBytes;
|
||||
}
|
||||
|
||||
// skip if beyond view
|
||||
if (x > width) break;
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
@@ -284,13 +291,24 @@ public class ChartNetworkSeriesView extends View {
|
||||
}
|
||||
|
||||
public long getMaxVisible() {
|
||||
return mEstimateVisible ? mMaxEstimate : mMax;
|
||||
final long maxVisible = mEstimateVisible ? mMaxEstimate : mMax;
|
||||
if (maxVisible <= 0 && mStats != null) {
|
||||
// haven't generated path yet; fall back to raw data
|
||||
final NetworkStatsHistory.Entry entry = mStats.getValues(mStart, mEnd, null);
|
||||
return entry.rxBytes + entry.txBytes;
|
||||
} else {
|
||||
return maxVisible;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
int save;
|
||||
|
||||
if (!mPathValid) {
|
||||
generatePath();
|
||||
}
|
||||
|
||||
final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
|
||||
final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
|
||||
|
||||
|
@@ -76,6 +76,8 @@ public class ChartSweepView extends View {
|
||||
private ChartSweepView mValidAfterDynamic;
|
||||
private ChartSweepView mValidBeforeDynamic;
|
||||
|
||||
private float mLabelOffset;
|
||||
|
||||
private Paint mOutlinePaint = new Paint();
|
||||
|
||||
public static final int HORIZONTAL = 0;
|
||||
@@ -230,12 +232,44 @@ public class ChartSweepView extends View {
|
||||
private void invalidateLabel() {
|
||||
if (mLabelTemplate != null && mAxis != null) {
|
||||
mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
|
||||
invalidateLabelOffset();
|
||||
invalidate();
|
||||
} else {
|
||||
mLabelValue = mValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When overlapping with neighbor, split difference and push label.
|
||||
*/
|
||||
public void invalidateLabelOffset() {
|
||||
float margin;
|
||||
float labelOffset = 0;
|
||||
if (mFollowAxis == VERTICAL) {
|
||||
if (mValidAfterDynamic != null) {
|
||||
margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
|
||||
if (margin < 0) {
|
||||
labelOffset = margin / 2;
|
||||
}
|
||||
} else if (mValidBeforeDynamic != null) {
|
||||
margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
|
||||
if (margin < 0) {
|
||||
labelOffset = -margin / 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: implement horizontal labels
|
||||
}
|
||||
|
||||
// when offsetting label, neighbor probably needs to offset too
|
||||
if (labelOffset != mLabelOffset) {
|
||||
mLabelOffset = labelOffset;
|
||||
invalidate();
|
||||
if (mValidAfterDynamic != null) mValidAfterDynamic.invalidateLabelOffset();
|
||||
if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidateLabelOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jumpDrawablesToCurrentState() {
|
||||
super.jumpDrawablesToCurrentState();
|
||||
@@ -566,6 +600,12 @@ public class ChartSweepView extends View {
|
||||
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
|
||||
protected void onDraw(Canvas canvas) {
|
||||
final int width = getWidth();
|
||||
@@ -575,36 +615,11 @@ public class ChartSweepView extends View {
|
||||
canvas.drawRect(0, 0, width, height, mOutlinePaint);
|
||||
}
|
||||
|
||||
// when overlapping with neighbor, split difference and push label
|
||||
float margin;
|
||||
float labelOffset = 0;
|
||||
if (mFollowAxis == VERTICAL) {
|
||||
if (mValidAfterDynamic != null) {
|
||||
margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
|
||||
if (margin < 0) {
|
||||
labelOffset = margin / 2;
|
||||
}
|
||||
} else if (mValidBeforeDynamic != null) {
|
||||
margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
|
||||
if (margin < 0) {
|
||||
labelOffset = -margin / 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: implement horizontal labels
|
||||
}
|
||||
|
||||
// when offsetting label, neighbor probably needs to offset too
|
||||
if (labelOffset != 0) {
|
||||
if (mValidAfterDynamic != null) mValidAfterDynamic.invalidate();
|
||||
if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidate();
|
||||
}
|
||||
|
||||
final int labelSize;
|
||||
if (isEnabled() && mLabelLayout != null) {
|
||||
final int count = canvas.save();
|
||||
{
|
||||
canvas.translate(mContentOffset.left, mContentOffset.top + labelOffset);
|
||||
canvas.translate(mContentOffset.left, mContentOffset.top + mLabelOffset);
|
||||
mLabelLayout.draw(canvas);
|
||||
}
|
||||
canvas.restoreToCount(count);
|
||||
|
@@ -31,14 +31,14 @@ public class InvertedChartAxis implements ChartAxis {
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void setBounds(long min, long max) {
|
||||
mWrapped.setBounds(min, max);
|
||||
public boolean setBounds(long min, long max) {
|
||||
return mWrapped.setBounds(min, max);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void setSize(float size) {
|
||||
public boolean setSize(float size) {
|
||||
mSize = size;
|
||||
mWrapped.setSize(size);
|
||||
return mWrapped.setSize(size);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
|
@@ -40,7 +40,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public class PieChartView extends View {
|
||||
public static final String TAG = "PieChartView";
|
||||
public static final boolean LOGD = true;
|
||||
public static final boolean LOGD = false;
|
||||
|
||||
private ArrayList<Slice> mSlices = Lists.newArrayList();
|
||||
|
||||
|
Reference in New Issue
Block a user