Merge "Data Usage, materialized!" into lmp-dev

This commit is contained in:
Jeff Sharkey
2014-07-22 23:40:05 +00:00
committed by Android (Google) Code Review
64 changed files with 590 additions and 795 deletions

View File

@@ -55,6 +55,7 @@ import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
@@ -63,6 +64,7 @@ import android.content.Loader;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -107,9 +109,6 @@ import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
@@ -138,7 +137,6 @@ import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
import com.android.settings.widget.ChartDataUsageView;
import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
import com.android.settings.widget.PieChartView;
import com.google.android.collect.Lists;
import libcore.util.Objects;
@@ -170,7 +168,6 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
private static final String TAB_ETHERNET = "ethernet";
private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable";
private static final String TAG_CONFIRM_DATA_ROAMING = "confirmDataRoaming";
private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
private static final String TAG_CYCLE_EDITOR = "cycleEditor";
private static final String TAG_WARNING_EDITOR = "warningEditor";
@@ -189,15 +186,11 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
private static final int LOADER_CHART_DATA = 2;
private static final int LOADER_SUMMARY = 3;
private static final int FOREGROUND_BYTES_COLOR = 0xff009688;
private static final int DEFAULT_BYTES_COLOR = 0xffced7db;
private INetworkManagementService mNetworkService;
private INetworkStatsService mStatsService;
private NetworkPolicyManager mPolicyManager;
private TelephonyManager mTelephonyManager;
private INetworkStatsSession mStatsSession;
private static final String PREF_FILE = "data_usage";
@@ -219,29 +212,33 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
private ViewGroup mNetworkSwitchesContainer;
private LinearLayout mNetworkSwitches;
private boolean mDataEnabledSupported;
private Switch mDataEnabled;
private View mDataEnabledView;
private CheckBox mDisableAtLimit;
private boolean mDisableAtLimitSupported;
private Switch mDisableAtLimit;
private View mDisableAtLimitView;
private View mCycleView;
private Spinner mCycleSpinner;
private CycleAdapter mCycleAdapter;
private TextView mCycleSummary;
private ChartDataUsageView mChart;
private TextView mUsageSummary;
private View mDisclaimer;
private TextView mEmpty;
private View mStupidPadding;
private View mAppDetail;
private ImageView mAppIcon;
private ViewGroup mAppTitles;
private PieChartView mAppPieChart;
private TextView mAppTotal;
private TextView mAppForeground;
private TextView mAppBackground;
private Button mAppSettings;
private LinearLayout mAppSwitches;
private CheckBox mAppRestrict;
private Switch mAppRestrict;
private View mAppRestrictView;
private boolean mShowWifi = false;
@@ -259,9 +256,11 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
private String mCurrentTab = null;
private String mIntentTab = null;
private MenuItem mMenuDataRoaming;
private MenuItem mMenuRestrictBackground;
private MenuItem mMenuAutoSync;
private MenuItem mMenuShowWifi;
private MenuItem mMenuShowEthernet;
private MenuItem mMenuSimCards;
private MenuItem mMenuCellularNetworks;
/** Flag used to ignore listeners during binding. */
private boolean mBinding;
@@ -359,13 +358,17 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches);
mDataEnabled = new Switch(inflater.getContext());
mDataEnabled.setClickable(false);
mDataEnabled.setFocusable(false);
mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled);
mDataEnabledView.setTag(R.id.preference_highlight_key,
DATA_USAGE_ENABLE_MOBILE_KEY);
mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
mDataEnabledView.setClickable(true);
mDataEnabledView.setFocusable(true);
mDataEnabledView.setOnClickListener(mDataEnabledListener);
mNetworkSwitches.addView(mDataEnabledView);
mDisableAtLimit = new CheckBox(inflater.getContext());
mDisableAtLimit = new Switch(inflater.getContext());
mDisableAtLimit.setClickable(false);
mDisableAtLimit.setFocusable(false);
mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
@@ -375,15 +378,16 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
mDisableAtLimitView.setFocusable(true);
mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
mNetworkSwitches.addView(mDisableAtLimitView);
}
// bind cycle dropdown
mCycleView = mHeader.findViewById(R.id.cycles);
mCycleView.setTag(R.id.preference_highlight_key, DATA_USAGE_CYCLE_KEY);
mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
mCycleAdapter = new CycleAdapter(context);
mCycleSpinner.setAdapter(mCycleAdapter);
mCycleSpinner.setOnItemSelectedListener(mCycleListener);
mCycleView = inflater.inflate(R.layout.data_usage_cycles, mNetworkSwitches, false);
mCycleView.setTag(R.id.preference_highlight_key, DATA_USAGE_CYCLE_KEY);
mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
mCycleAdapter = new CycleAdapter(context);
mCycleSpinner.setAdapter(mCycleAdapter);
mCycleSpinner.setOnItemSelectedListener(mCycleListener);
mCycleSummary = (TextView) mCycleView.findViewById(R.id.cycle_summary);
mNetworkSwitches.addView(mCycleView);
}
mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
mChart.setListener(mChartListener);
@@ -394,7 +398,6 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
mAppDetail = mHeader.findViewById(R.id.app_detail);
mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon);
mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles);
mAppPieChart = (PieChartView) mAppDetail.findViewById(R.id.app_pie_chart);
mAppForeground = (TextView) mAppDetail.findViewById(R.id.app_foreground);
mAppBackground = (TextView) mAppDetail.findViewById(R.id.app_background);
mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches);
@@ -402,7 +405,7 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings);
mAppSettings.setOnClickListener(mAppSettingsListener);
mAppRestrict = new CheckBox(inflater.getContext());
mAppRestrict = new Switch(inflater.getContext());
mAppRestrict.setClickable(false);
mAppRestrict.setFocusable(false);
mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
@@ -412,8 +415,9 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
mAppSwitches.addView(mAppRestrictView);
}
mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary);
mDisclaimer = mHeader.findViewById(R.id.disclaimer);
mEmpty = (TextView) mHeader.findViewById(android.R.id.empty);
mStupidPadding = mHeader.findViewById(R.id.stupid_padding);
mAdapter = new DataUsageAdapter(mUidDetailProvider, mInsetSide);
mListView.setOnItemClickListener(mListListener);
@@ -480,39 +484,24 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
final boolean appDetailMode = isAppDetailMode();
final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
mMenuDataRoaming.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
mMenuDataRoaming.setChecked(getDataRoaming());
mMenuShowWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
// TODO: Define behavior of this sync button. See: http://b/16076571
if (hasWifiRadio(context) && hasReadyMobileRadio(context)) {
mMenuShowWifi.setVisible(!appDetailMode);
} else {
mMenuShowWifi.setVisible(false);
}
mMenuShowEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
if (hasEthernet(context) && hasReadyMobileRadio(context)) {
mMenuShowEthernet.setVisible(!appDetailMode);
} else {
mMenuShowEthernet.setVisible(false);
}
mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
mMenuRestrictBackground.setVisible(
hasReadyMobileRadio(context) && isOwner && !appDetailMode);
mMenuRestrictBackground.setChecked(mPolicyManager.getRestrictBackground());
// TODO: Define behavior of this sync button. See: http://b/16076571
mMenuAutoSync = menu.findItem(R.id.data_usage_menu_auto_sync);
mMenuAutoSync.setChecked(ContentResolver.getMasterSyncAutomatically());
mMenuAutoSync.setVisible(!appDetailMode);
final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
split4g.setVisible(hasReadyMobile4gRadio(context) && isOwner && !appDetailMode);
split4g.setChecked(isMobilePolicySplit());
final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
if (hasWifiRadio(context) && hasReadyMobileRadio(context)) {
showWifi.setVisible(!appDetailMode);
showWifi.setChecked(mShowWifi);
} else {
showWifi.setVisible(false);
}
final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
if (hasEthernet(context) && hasReadyMobileRadio(context)) {
showEthernet.setVisible(!appDetailMode);
showEthernet.setChecked(mShowEthernet);
} else {
showEthernet.setVisible(false);
}
final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered);
if (hasReadyMobileRadio(context) || hasWifiRadio(context)) {
@@ -521,6 +510,17 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
metered.setVisible(false);
}
// TODO: show when multiple sims available
mMenuSimCards = menu.findItem(R.id.data_usage_menu_sim_cards);
mMenuSimCards.setVisible(false);
mMenuCellularNetworks = menu.findItem(R.id.data_usage_menu_cellular_networks);
if (hasReadyMobileRadio(context)) {
mMenuCellularNetworks.setVisible(!appDetailMode);
} else {
mMenuCellularNetworks.setVisible(false);
}
final MenuItem help = menu.findItem(R.id.data_usage_menu_help);
String helpUrl;
if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_data_usage))) {
@@ -528,23 +528,35 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
} else {
help.setVisible(false);
}
updateMenuTitles();
}
private void updateMenuTitles() {
if (mPolicyManager.getRestrictBackground()) {
mMenuRestrictBackground.setTitle(R.string.data_usage_menu_allow_background);
} else {
mMenuRestrictBackground.setTitle(R.string.data_usage_menu_restrict_background);
}
if (mShowWifi) {
mMenuShowWifi.setTitle(R.string.data_usage_menu_hide_wifi);
} else {
mMenuShowWifi.setTitle(R.string.data_usage_menu_show_wifi);
}
if (mShowEthernet) {
mMenuShowEthernet.setTitle(R.string.data_usage_menu_hide_ethernet);
} else {
mMenuShowEthernet.setTitle(R.string.data_usage_menu_show_ethernet);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.data_usage_menu_roaming: {
final boolean dataRoaming = !item.isChecked();
if (dataRoaming) {
ConfirmDataRoamingFragment.show(this);
} else {
// no confirmation to disable roaming
setDataRoaming(false);
}
return true;
}
case R.id.data_usage_menu_restrict_background: {
final boolean restrictBackground = !item.isChecked();
final boolean restrictBackground = !mPolicyManager.getRestrictBackground();
if (restrictBackground) {
ConfirmRestrictFragment.show(this);
} else {
@@ -553,41 +565,37 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
}
return true;
}
case R.id.data_usage_menu_split_4g: {
final boolean mobileSplit = !item.isChecked();
setMobilePolicySplit(mobileSplit);
item.setChecked(isMobilePolicySplit());
updateTabs();
return true;
}
case R.id.data_usage_menu_show_wifi: {
mShowWifi = !item.isChecked();
mShowWifi = !mShowWifi;
mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply();
item.setChecked(mShowWifi);
updateMenuTitles();
updateTabs();
return true;
}
case R.id.data_usage_menu_show_ethernet: {
mShowEthernet = !item.isChecked();
mShowEthernet = !mShowEthernet;
mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply();
item.setChecked(mShowEthernet);
updateMenuTitles();
updateTabs();
return true;
}
case R.id.data_usage_menu_sim_cards: {
// TODO: hook up to sim cards
return true;
}
case R.id.data_usage_menu_cellular_networks: {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setComponent(new ComponentName("com.android.phone",
"com.android.phone.MobileNetworkSettings"));
startActivity(intent);
return true;
}
case R.id.data_usage_menu_metered: {
final SettingsActivity sa = (SettingsActivity) getActivity();
sa.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null,
R.string.data_usage_metered_title, null, this, 0);
return true;
}
case R.id.data_usage_menu_auto_sync: {
if (ActivityManager.isUserAMonkey()) {
Log.d("SyncState", "ignoring monkey's attempt to flip global sync state");
} else {
ConfirmAutoSyncChangeFragment.show(this, !item.isChecked());
}
return true;
}
}
return false;
}
@@ -725,7 +733,8 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
mDataEnabledView.setVisibility(isOwner ? View.VISIBLE : View.GONE);
mDataEnabledSupported = isOwner;
mDisableAtLimitSupported = true;
// TODO: remove mobile tabs when SIM isn't ready
@@ -748,14 +757,14 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
} else if (TAB_WIFI.equals(currentTab)) {
// wifi doesn't have any controls
mDataEnabledView.setVisibility(View.GONE);
mDisableAtLimitView.setVisibility(View.GONE);
mDataEnabledSupported = false;
mDisableAtLimitSupported = false;
mTemplate = buildTemplateWifiWildcard();
} else if (TAB_ETHERNET.equals(currentTab)) {
// ethernet doesn't have any controls
mDataEnabledView.setVisibility(View.GONE);
mDisableAtLimitView.setVisibility(View.GONE);
mDataEnabledSupported = false;
mDisableAtLimitSupported = false;
mTemplate = buildTemplateEthernet();
} else {
@@ -808,12 +817,25 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
mAppIcon.setImageDrawable(detail.icon);
mAppTitles.removeAllViews();
View title = null;
if (detail.detailLabels != null) {
for (CharSequence label : detail.detailLabels) {
mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, label));
title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false);
((TextView) title.findViewById(R.id.app_title)).setText(label);
mAppTitles.addView(title);
}
} else {
mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, detail.label));
title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false);
((TextView) title.findViewById(R.id.app_title)).setText(detail.label);
mAppTitles.addView(title);
}
// Remember last slot for summary
if (title != null) {
mAppTotal = (TextView) title.findViewById(R.id.app_summary);
} else {
mAppTotal = null;
}
// enable settings button when package provides it
@@ -904,24 +926,9 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
}
}
private boolean getDataRoaming() {
final ContentResolver resolver = getActivity().getContentResolver();
return android.provider.Settings.Global.getInt(resolver,
android.provider.Settings.Global.DATA_ROAMING, 0) != 0;
}
private void setDataRoaming(boolean enabled) {
// TODO: teach telephony DataConnectionTracker to watch and apply
// updates when changed.
final ContentResolver resolver = getActivity().getContentResolver();
android.provider.Settings.Global.putInt(resolver,
android.provider.Settings.Global.DATA_ROAMING, enabled ? 1 : 0);
mMenuDataRoaming.setChecked(enabled);
}
public void setRestrictBackground(boolean restrictBackground) {
mPolicyManager.setRestrictBackground(restrictBackground);
mMenuRestrictBackground.setChecked(restrictBackground);
updateMenuTitles();
}
private boolean getAppRestrictBackground() {
@@ -943,10 +950,12 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
* current {@link #mTemplate}.
*/
private void updatePolicy(boolean refreshCycle) {
boolean dataEnabledVisible = mDataEnabledSupported;
boolean disableAtLimitVisible = mDisableAtLimitSupported;
if (isAppDetailMode()) {
mNetworkSwitches.setVisibility(View.GONE);
} else {
mNetworkSwitches.setVisibility(View.VISIBLE);
dataEnabledVisible = false;
disableAtLimitVisible = false;
}
// TODO: move enabled state directly into policy
@@ -958,7 +967,6 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
if (isNetworkPolicyModifiable(policy)) {
mDisableAtLimitView.setVisibility(View.VISIBLE);
mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
if (!isAppDetailMode()) {
mChart.bindNetworkPolicy(policy);
@@ -966,10 +974,13 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
} else {
// controls are disabled; don't bind warning/limit sweeps
mDisableAtLimitView.setVisibility(View.GONE);
disableAtLimitVisible = false;
mChart.bindNetworkPolicy(null);
}
mDataEnabledView.setVisibility(dataEnabledVisible ? View.VISIBLE : View.GONE);
mDisableAtLimitView.setVisibility(disableAtLimitVisible ? View.VISIBLE : View.GONE);
if (refreshCycle) {
// generate cycle list based on policy and available history
updateCycleList(policy);
@@ -1049,12 +1060,12 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
}
}
private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
private View.OnClickListener mDataEnabledListener = new View.OnClickListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
public void onClick(View v) {
if (mBinding) return;
final boolean dataEnabled = isChecked;
final boolean dataEnabled = !mDataEnabled.isChecked();
final String currentTab = mCurrentTab;
if (TAB_MOBILE.equals(currentTab)) {
if (dataEnabled) {
@@ -1178,15 +1189,11 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
final long defaultBytes = entry.rxBytes + entry.txBytes;
entry = mChartData.detailForeground.getValues(start, end, now, entry);
final long foregroundBytes = entry.rxBytes + entry.txBytes;
final long totalBytes = defaultBytes + foregroundBytes;
mAppPieChart.setOriginAngle(175);
mAppPieChart.removeAllSlices();
mAppPieChart.addSlice(foregroundBytes, FOREGROUND_BYTES_COLOR);
mAppPieChart.addSlice(defaultBytes, DEFAULT_BYTES_COLOR);
mAppPieChart.generatePath();
if (mAppTotal != null) {
mAppTotal.setText(Formatter.formatFileSize(context, totalBytes));
}
mAppBackground.setText(Formatter.formatFileSize(context, defaultBytes));
mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes));
@@ -1195,11 +1202,15 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
getLoaderManager().destroyLoader(LOADER_SUMMARY);
mCycleSummary.setVisibility(View.GONE);
} else {
if (mChartData != null) {
entry = mChartData.network.getValues(start, end, now, null);
}
mCycleSummary.setVisibility(View.VISIBLE);
// kick off loader for detailed stats
getLoaderManager().restartLoader(LOADER_SUMMARY,
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
@@ -1207,18 +1218,19 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
final String rangePhrase = formatDateRange(context, start, end);
mCycleSummary.setText(totalPhrase);
final int summaryRes;
if (TAB_MOBILE.equals(mCurrentTab) || TAB_3G.equals(mCurrentTab)
|| TAB_4G.equals(mCurrentTab)) {
summaryRes = R.string.data_usage_total_during_range_mobile;
if (isAppDetailMode()) {
mDisclaimer.setVisibility(View.GONE);
} else {
mDisclaimer.setVisibility(View.VISIBLE);
}
} else {
summaryRes = R.string.data_usage_total_during_range;
mDisclaimer.setVisibility(View.GONE);
}
mUsageSummary.setText(getString(summaryRes, totalPhrase, rangePhrase));
// initial layout is finished above, ensure we have transitions
ensureLayoutTransitions();
}
@@ -1278,6 +1290,7 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
private void updateEmptyVisible() {
final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode();
mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
mStupidPadding.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
}
};
@@ -1308,12 +1321,6 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
}
private DataUsageChartListener mChartListener = new DataUsageChartListener() {
@Override
public void onInspectRangeChanged() {
if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
updateDetailData();
}
@Override
public void onWarningChanged() {
setPolicyWarningBytes(mChart.getWarningBytes());
@@ -1404,8 +1411,8 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
private final CycleChangeItem mChangeItem;
public CycleAdapter(Context context) {
super(context, android.R.layout.simple_spinner_item);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
super(context, R.layout.data_usage_cycle_item);
setDropDownViewResource(R.layout.data_usage_cycle_item_dropdown);
mChangeItem = new CycleChangeItem(context);
}
@@ -1447,11 +1454,21 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
}
public static class AppItem implements Comparable<AppItem>, Parcelable {
public static final int CATEGORY_USER = 0;
public static final int CATEGORY_APP_TITLE = 1;
public static final int CATEGORY_APP = 2;
public final int key;
public boolean restricted;
public int category;
public SparseBooleanArray uids = new SparseBooleanArray();
public long total;
public AppItem() {
this.key = 0;
}
public AppItem(int key) {
this.key = key;
}
@@ -1480,7 +1497,11 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
@Override
public int compareTo(AppItem another) {
return Long.compare(another.total, total);
int comparison = Integer.compare(another.category, category);
if (comparison == 0) {
comparison = Long.compare(another.total, total);
}
return comparison;
}
public static final Creator<AppItem> CREATOR = new Creator<AppItem>() {
@@ -1516,9 +1537,11 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
*/
public void bindStats(NetworkStats stats, int[] restrictedUids) {
mItems.clear();
mLargest = 0;
final int currentUserId = ActivityManager.getCurrentUser();
final SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
boolean hasApps = false;
NetworkStats.Entry entry = null;
final int size = stats != null ? stats.size() : 0;
@@ -1548,6 +1571,9 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
}
item.addUid(uid);
item.total += entry.rxBytes + entry.txBytes;
if (item.total > mLargest) {
mLargest = item.total;
}
}
for (int uid : restrictedUids) {
@@ -1564,8 +1590,14 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
item.restricted = true;
}
hasApps = !mItems.isEmpty();
if (hasApps) {
final AppItem title = new AppItem();
title.category = AppItem.CATEGORY_APP_TITLE;
mItems.add(title);
}
Collections.sort(mItems);
mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0;
notifyDataSetChanged();
}
@@ -1585,36 +1617,72 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.data_usage_item, parent, false);
public int getViewTypeCount() {
return 2;
}
if (mInsetSide > 0) {
convertView.setPaddingRelative(mInsetSide, 0, mInsetSide, 0);
}
}
final Context context = parent.getContext();
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
@Override
public int getItemViewType(int position) {
final AppItem item = mItems.get(position);
UidDetailTask.bindView(mProvider, item, convertView);
if (item.restricted && item.total <= 0) {
text1.setText(R.string.data_usage_app_restricted);
progress.setVisibility(View.GONE);
if (item.category == AppItem.CATEGORY_APP_TITLE) {
return 1;
} else {
text1.setText(Formatter.formatFileSize(context, item.total));
progress.setVisibility(View.VISIBLE);
return 0;
}
}
final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
progress.setProgress(percentTotal);
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
return getItemViewType(position) == 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final AppItem item = mItems.get(position);
if (getItemViewType(position) == 1) {
if (convertView == null) {
convertView = inflateCategoryHeader(LayoutInflater.from(parent.getContext()),
parent);
}
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
title.setText(R.string.data_usage_app);
} else {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.data_usage_item, parent, false);
if (mInsetSide > 0) {
convertView.setPaddingRelative(mInsetSide, 0, mInsetSide, 0);
}
}
final Context context = parent.getContext();
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
UidDetailTask.bindView(mProvider, item, convertView);
if (item.restricted && item.total <= 0) {
text1.setText(R.string.data_usage_app_restricted);
progress.setVisibility(View.GONE);
} else {
text1.setText(Formatter.formatFileSize(context, item.total));
progress.setVisibility(View.VISIBLE);
}
final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
progress.setProgress(percentTotal);
}
return convertView;
}
@@ -1639,7 +1707,8 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
ft.add(fragment, TAG_APP_DETAILS);
ft.addToBackStack(TAG_APP_DETAILS);
ft.setBreadCrumbTitle(label);
ft.setBreadCrumbTitle(
parent.getResources().getString(R.string.data_usage_app_summary_title));
ft.commitAllowingStateLoss();
}
@@ -1946,46 +2015,6 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
}
}
/**
* Dialog to request user confirmation before setting
* {@link android.provider.Settings.Global#DATA_ROAMING}.
*/
public static class ConfirmDataRoamingFragment extends DialogFragment {
public static void show(DataUsageSummary parent) {
if (!parent.isAdded()) return;
final ConfirmDataRoamingFragment dialog = new ConfirmDataRoamingFragment();
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_ROAMING);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.roaming_reenable_title);
if (Utils.hasMultipleUsers(context)) {
builder.setMessage(R.string.roaming_warning_multiuser);
} else {
builder.setMessage(R.string.roaming_warning);
}
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
if (target != null) {
target.setDataRoaming(true);
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
}
/**
* Dialog to request user confirmation before setting
* {@link INetworkPolicyManager#setRestrictBackground(boolean)}.
@@ -2308,12 +2337,12 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable
return view;
}
private static View inflateAppTitle(
LayoutInflater inflater, ViewGroup root, CharSequence label) {
final TextView view = (TextView) inflater.inflate(
R.layout.data_usage_app_title, root, false);
view.setText(label);
return view;
private static View inflateCategoryHeader(LayoutInflater inflater, ViewGroup root) {
final TypedArray a = inflater.getContext().obtainStyledAttributes(null,
com.android.internal.R.styleable.Preference,
com.android.internal.R.attr.preferenceCategoryStyle, 0);
final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout, 0);
return inflater.inflate(resId, root, false);
}
/**

View File

@@ -29,10 +29,9 @@ import android.net.NetworkTemplate;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.provider.SearchIndexableResource;
import android.preference.SwitchPreference;
import android.telephony.TelephonyManager;
import com.android.settings.R;
@@ -42,7 +41,6 @@ import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -117,7 +115,7 @@ public class DataUsageMeteredSettings extends SettingsPreferenceFragment impleme
return pref;
}
private class MeteredPreference extends CheckBoxPreference {
private class MeteredPreference extends SwitchPreference {
private final NetworkTemplate mTemplate;
private boolean mBinding;

View File

@@ -50,26 +50,24 @@ public class ChartDataUsageView extends ChartView {
private static final int MSG_UPDATE_AXIS = 100;
private static final long DELAY_MILLIS = 250;
private static final boolean LIMIT_SWEEPS_TO_VALID_DATA = false;
private ChartGridView mGrid;
private ChartNetworkSeriesView mSeries;
private ChartNetworkSeriesView mDetailSeries;
private NetworkStatsHistory mHistory;
private ChartSweepView mSweepLeft;
private ChartSweepView mSweepRight;
private ChartSweepView mSweepWarning;
private ChartSweepView mSweepLimit;
private long mInspectStart;
private long mInspectEnd;
private Handler mHandler;
/** Current maximum value of {@link #mVert}. */
private long mVertMax;
public interface DataUsageChartListener {
public void onInspectRangeChanged();
public void onWarningChanged();
public void onLimitChanged();
public void requestWarningEdit();
@@ -112,43 +110,27 @@ public class ChartDataUsageView extends ChartView {
mDetailSeries = (ChartNetworkSeriesView) findViewById(R.id.detail_series);
mDetailSeries.setVisibility(View.GONE);
mSweepLeft = (ChartSweepView) findViewById(R.id.sweep_left);
mSweepRight = (ChartSweepView) findViewById(R.id.sweep_right);
mSweepLimit = (ChartSweepView) findViewById(R.id.sweep_limit);
mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
// prevent sweeps from crossing each other
mSweepLeft.setValidRangeDynamic(null, mSweepRight);
mSweepRight.setValidRangeDynamic(mSweepLeft, null);
mSweepWarning.setValidRangeDynamic(null, mSweepLimit);
mSweepLimit.setValidRangeDynamic(mSweepWarning, null);
// mark neighbors for checking touch events against
mSweepLeft.setNeighbors(mSweepRight);
mSweepRight.setNeighbors(mSweepLeft);
mSweepLimit.setNeighbors(mSweepWarning, mSweepLeft, mSweepRight);
mSweepWarning.setNeighbors(mSweepLimit, mSweepLeft, mSweepRight);
mSweepLimit.setNeighbors(mSweepWarning);
mSweepWarning.setNeighbors(mSweepLimit);
mSweepLeft.addOnSweepListener(mHorizListener);
mSweepRight.addOnSweepListener(mHorizListener);
mSweepWarning.addOnSweepListener(mVertListener);
mSweepLimit.addOnSweepListener(mVertListener);
mSweepWarning.setDragInterval(5 * MB_IN_BYTES);
mSweepLimit.setDragInterval(5 * MB_IN_BYTES);
// TODO: make time sweeps adjustable through dpad
mSweepLeft.setClickable(false);
mSweepLeft.setFocusable(false);
mSweepRight.setClickable(false);
mSweepRight.setFocusable(false);
// tell everyone about our axis
mGrid.init(mHoriz, mVert);
mSeries.init(mHoriz, mVert);
mDetailSeries.init(mHoriz, mVert);
mSweepLeft.init(mHoriz);
mSweepRight.init(mHoriz);
mSweepWarning.init(mVert);
mSweepLimit.init(mVert);
@@ -194,7 +176,7 @@ public class ChartDataUsageView extends ChartView {
mSweepLimit.setEnabled(true);
mSweepLimit.setValue(policy.limitBytes);
} else {
mSweepLimit.setVisibility(View.VISIBLE);
mSweepLimit.setVisibility(View.INVISIBLE);
mSweepLimit.setEnabled(false);
mSweepLimit.setValue(-1);
}
@@ -295,23 +277,6 @@ public class ChartDataUsageView extends ChartView {
mSeries.setEstimateVisible(estimateVisible);
}
private OnSweepListener mHorizListener = new OnSweepListener() {
@Override
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
updatePrimaryRange();
// update detail list only when done sweeping
if (sweepDone && mListener != null) {
mListener.onInspectRangeChanged();
}
}
@Override
public void requestEdit(ChartSweepView sweep) {
// ignored
}
};
private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
if (force || !mHandler.hasMessages(MSG_UPDATE_AXIS, sweep)) {
mHandler.sendMessageDelayed(
@@ -369,11 +334,11 @@ public class ChartDataUsageView extends ChartView {
}
public long getInspectStart() {
return mSweepLeft.getValue();
return mInspectStart;
}
public long getInspectEnd() {
return mSweepRight.getValue();
return mInspectEnd;
}
public long getWarningBytes() {
@@ -384,14 +349,6 @@ public class ChartDataUsageView extends ChartView {
return mSweepLimit.getLabelValue();
}
private long getHistoryStart() {
return mHistory != null ? mHistory.getStart() : Long.MAX_VALUE;
}
private long getHistoryEnd() {
return mHistory != null ? mHistory.getEnd() : Long.MIN_VALUE;
}
/**
* Set the exact time range that should be displayed, updating how
* {@link ChartNetworkSeriesView} paints. Moves inspection ranges to be the
@@ -403,30 +360,8 @@ public class ChartDataUsageView extends ChartView {
mSeries.setBounds(visibleStart, visibleEnd);
mDetailSeries.setBounds(visibleStart, visibleEnd);
final long historyStart = getHistoryStart();
final long historyEnd = getHistoryEnd();
final long validStart = historyStart == Long.MAX_VALUE ? visibleStart
: Math.max(visibleStart, historyStart);
final long validEnd = historyEnd == Long.MIN_VALUE ? visibleEnd
: Math.min(visibleEnd, historyEnd);
if (LIMIT_SWEEPS_TO_VALID_DATA) {
// prevent time sweeps from leaving valid data
mSweepLeft.setValidRange(validStart, validEnd);
mSweepRight.setValidRange(validStart, validEnd);
} else {
mSweepLeft.setValidRange(visibleStart, visibleEnd);
mSweepRight.setValidRange(visibleStart, visibleEnd);
}
// default sweeps to last week of data
final long halfRange = (visibleEnd + visibleStart) / 2;
final long sweepMax = validEnd;
final long sweepMin = Math.max(visibleStart, (sweepMax - DateUtils.WEEK_IN_MILLIS));
mSweepLeft.setValue(sweepMin);
mSweepRight.setValue(sweepMax);
mInspectStart = visibleStart;
mInspectEnd = visibleEnd;
requestLayout();
if (changed) {
@@ -440,15 +375,11 @@ public class ChartDataUsageView extends ChartView {
}
private void updatePrimaryRange() {
final long left = mSweepLeft.getValue();
final long right = mSweepRight.getValue();
// prefer showing primary range on detail series, when available
if (mDetailSeries.getVisibility() == View.VISIBLE) {
mDetailSeries.setPrimaryRange(left, right);
mSeries.setPrimaryRange(0, 0);
mSeries.setSecondary(true);
} else {
mSeries.setPrimaryRange(left, right);
mSeries.setSecondary(false);
}
}

View File

@@ -19,22 +19,25 @@ package com.android.settings.widget;
import static com.android.settings.DataUsageSummary.formatDateRange;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import com.android.internal.util.Preconditions;
import com.android.settings.R;
import java.util.Locale;
/**
* Background of {@link ChartView} that renders grid lines as requested by
* {@link ChartAxis#getTickPoints()}.
@@ -47,10 +50,13 @@ public class ChartGridView extends View {
private Drawable mPrimary;
private Drawable mSecondary;
private Drawable mBorder;
private int mLabelSize;
private int mLabelColor;
private Layout mLayoutStart;
private Layout mLayoutEnd;
private Layout mLabelStart;
private Layout mLabelMid;
private Layout mLabelEnd;
public ChartGridView(Context context) {
this(context, null, 0);
@@ -71,7 +77,17 @@ public class ChartGridView extends View {
mPrimary = a.getDrawable(R.styleable.ChartGridView_primaryDrawable);
mSecondary = a.getDrawable(R.styleable.ChartGridView_secondaryDrawable);
mBorder = a.getDrawable(R.styleable.ChartGridView_borderDrawable);
mLabelColor = a.getColor(R.styleable.ChartGridView_labelColor, Color.RED);
final int taId = a.getResourceId(R.styleable.ChartGridView_android_textAppearance, -1);
final TypedArray ta = context.obtainStyledAttributes(taId,
com.android.internal.R.styleable.TextAppearance);
mLabelSize = ta.getDimensionPixelSize(
com.android.internal.R.styleable.TextAppearance_textSize, 0);
ta.recycle();
final ColorStateList labelColor = a.getColorStateList(
R.styleable.ChartGridView_android_textColor);
mLabelColor = labelColor.getDefaultColor();
a.recycle();
}
@@ -83,71 +99,83 @@ public class ChartGridView extends View {
void setBounds(long start, long end) {
final Context context = getContext();
mLayoutStart = makeLayout(formatDateRange(context, start, start));
mLayoutEnd = makeLayout(formatDateRange(context, end, end));
final long mid = (start + end) / 2;
mLabelStart = makeLabel(formatDateRange(context, start, start));
mLabelMid = makeLabel(formatDateRange(context, mid, mid));
mLabelEnd = makeLabel(formatDateRange(context, end, end));
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
final int width = getWidth();
final int height = getHeight();
final int height = getHeight() - getPaddingBottom();
final Drawable secondary = mSecondary;
final int secondaryHeight = mSecondary.getIntrinsicHeight();
if (secondary != null) {
final int secondaryHeight = secondary.getIntrinsicHeight();
final float[] vertTicks = mVert.getTickPoints();
for (float y : vertTicks) {
final int bottom = (int) Math.min(y + secondaryHeight, height);
secondary.setBounds(0, (int) y, width, bottom);
secondary.draw(canvas);
final float[] vertTicks = mVert.getTickPoints();
for (float y : vertTicks) {
final int bottom = (int) Math.min(y + secondaryHeight, height);
secondary.setBounds(0, (int) y, width, bottom);
secondary.draw(canvas);
}
}
final Drawable primary = mPrimary;
final int primaryWidth = mPrimary.getIntrinsicWidth();
final int primaryHeight = mPrimary.getIntrinsicHeight();
if (primary != null) {
final int primaryWidth = primary.getIntrinsicWidth();
final int primaryHeight = primary.getIntrinsicHeight();
final float[] horizTicks = mHoriz.getTickPoints();
for (float x : horizTicks) {
final int right = (int) Math.min(x + primaryWidth, width);
primary.setBounds((int) x, 0, right, height);
primary.draw(canvas);
final float[] horizTicks = mHoriz.getTickPoints();
for (float x : horizTicks) {
final int right = (int) Math.min(x + primaryWidth, width);
primary.setBounds((int) x, 0, right, height);
primary.draw(canvas);
}
}
mBorder.setBounds(0, 0, width, height);
mBorder.draw(canvas);
final int padding = mLayoutStart != null ? mLayoutStart.getHeight() / 8 : 0;
final int padding = mLabelStart != null ? mLabelStart.getHeight() / 8 : 0;
final Layout start = mLayoutStart;
final Layout start = mLabelStart;
if (start != null) {
canvas.save();
final int saveCount = canvas.save();
canvas.translate(0, height + padding);
start.draw(canvas);
canvas.restore();
canvas.restoreToCount(saveCount);
}
final Layout end = mLayoutEnd;
final Layout mid = mLabelMid;
if (mid != null) {
final int saveCount = canvas.save();
canvas.translate((width - mid.getWidth()) / 2, height + padding);
mid.draw(canvas);
canvas.restoreToCount(saveCount);
}
final Layout end = mLabelEnd;
if (end != null) {
canvas.save();
final int saveCount = canvas.save();
canvas.translate(width - end.getWidth(), height + padding);
end.draw(canvas);
canvas.restore();
canvas.restoreToCount(saveCount);
}
}
private Layout makeLayout(CharSequence text) {
private Layout makeLabel(CharSequence text) {
final Resources res = getResources();
final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
paint.density = res.getDisplayMetrics().density;
paint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
paint.setColor(mLabelColor);
paint.setTextSize(
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, res.getDisplayMetrics()));
paint.setTextSize(mLabelSize);
return new StaticLayout(text, paint,
(int) Math.ceil(Layout.getDesiredWidth(text, paint)),
Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
}
}

View File

@@ -60,17 +60,17 @@ public class ChartNetworkSeriesView extends View {
private Path mPathFill;
private Path mPathEstimate;
private int mSafeRegion;
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 boolean mSecondary = false;
private long mMax;
private long mMaxEstimate;
@@ -93,8 +93,11 @@ public class ChartNetworkSeriesView extends View {
final int fill = a.getColor(R.styleable.ChartNetworkSeriesView_fillColor, Color.RED);
final int fillSecondary = a.getColor(
R.styleable.ChartNetworkSeriesView_fillColorSecondary, Color.RED);
final int safeRegion = a.getDimensionPixelSize(
R.styleable.ChartNetworkSeriesView_safeRegion, 0);
setChartColor(stroke, fill, fillSecondary);
setSafeRegion(safeRegion);
setWillNotDraw(false);
a.recycle();
@@ -134,6 +137,10 @@ public class ChartNetworkSeriesView extends View {
mPaintEstimate.setPathEffect(new DashPathEffect(new float[] { 10, 10 }, 1));
}
public void setSafeRegion(int safeRegion) {
mSafeRegion = safeRegion;
}
public void bindNetworkStats(NetworkStatsHistory stats) {
mStats = stats;
invalidatePath();
@@ -145,14 +152,8 @@ public class ChartNetworkSeriesView extends View {
mEnd = end;
}
/**
* Set the range to paint with {@link #mPaintFill}, leaving the remaining
* area to be painted with {@link #mPaintFillSecondary}.
*/
public void setPrimaryRange(long left, long right) {
mPrimaryLeft = left;
mPrimaryRight = right;
invalidate();
public void setSecondary(boolean secondary) {
mSecondary = secondary;
}
public void invalidatePath() {
@@ -322,9 +323,6 @@ public class ChartNetworkSeriesView extends View {
generatePath();
}
final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
if (mEstimateVisible) {
save = canvas.save();
canvas.clipRect(0, 0, getWidth(), getHeight());
@@ -332,21 +330,11 @@ public class ChartNetworkSeriesView extends View {
canvas.restoreToCount(save);
}
save = canvas.save();
canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
canvas.drawPath(mPathFill, mPaintFillSecondary);
canvas.restoreToCount(save);
final Paint paintFill = mSecondary ? mPaintFillSecondary : mPaintFill;
save = canvas.save();
canvas.clipRect(primaryRightPoint, 0, getWidth(), getHeight());
canvas.drawPath(mPathFill, mPaintFillSecondary);
canvas.clipRect(mSafeRegion, 0, getWidth(), getHeight() - mSafeRegion);
canvas.drawPath(mPathFill, paintFill);
canvas.restoreToCount(save);
save = canvas.save();
canvas.clipRect(primaryLeftPoint, 0, primaryRightPoint, getHeight());
canvas.drawPath(mPathFill, mPaintFill);
canvas.drawPath(mPathStroke, mPaintStroke);
canvas.restoreToCount(save);
}
}

View File

@@ -58,6 +58,7 @@ public class ChartSweepView extends View {
private Rect mMargins = new Rect();
private float mNeighborMargin;
private int mSafeRegion;
private int mFollowAxis;
@@ -125,6 +126,7 @@ public class ChartSweepView extends View {
setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
setNeighborMargin(a.getDimensionPixelSize(R.styleable.ChartSweepView_neighborMargin, 0));
setSafeRegion(a.getDimensionPixelSize(R.styleable.ChartSweepView_safeRegion, 0));
setLabelMinSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
@@ -259,7 +261,6 @@ public class ChartSweepView extends View {
paint.density = getResources().getDisplayMetrics().density;
paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
paint.setColor(mLabelColor);
paint.setShadowLayer(4 * paint.density, 0, 0, Color.BLACK);
mLabelTemplate = new SpannableStringBuilder(template);
mLabelLayout = new DynamicLayout(
@@ -383,6 +384,10 @@ public class ChartSweepView extends View {
mNeighborMargin = neighborMargin;
}
public void setSafeRegion(int safeRegion) {
mSafeRegion = safeRegion;
}
/**
* Set valid range this sweep can move within, defined by the given
* {@link ChartSweepView}. The most restrictive combination of all valid
@@ -709,7 +714,7 @@ public class ChartSweepView extends View {
mLabelLayout.draw(canvas);
}
canvas.restoreToCount(count);
labelSize = (int) mLabelSize;
labelSize = (int) mLabelSize + mSafeRegion;
} else {
labelSize = 0;
}

View File

@@ -112,12 +112,18 @@ public class ChartView extends FrameLayout {
parentRect.set(mContent);
if (child instanceof ChartNetworkSeriesView || child instanceof ChartGridView) {
if (child instanceof ChartNetworkSeriesView) {
// series are always laid out to fill entire graph area
// TODO: handle scrolling for series larger than content area
Gravity.apply(params.gravity, width, height, parentRect, childRect);
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
} else if (child instanceof ChartGridView) {
// Grid uses some extra room for labels
Gravity.apply(params.gravity, width, height, parentRect, childRect);
child.layout(childRect.left, childRect.top, childRect.right,
childRect.bottom + child.getPaddingBottom());
} else if (child instanceof ChartSweepView) {
layoutSweep((ChartSweepView) child, parentRect, childRect);
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
@@ -154,5 +160,4 @@ public class ChartView extends FrameLayout {
parentRect, childRect);
}
}
}

View File

@@ -1,244 +0,0 @@
/*
* 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.widget;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.google.android.collect.Lists;
import java.util.ArrayList;
/**
* Pie chart with multiple items.
*/
public class PieChartView extends View {
public static final String TAG = "PieChartView";
public static final boolean LOGD = false;
private static final boolean FILL_GRADIENT = false;
private ArrayList<Slice> mSlices = Lists.newArrayList();
private int mOriginAngle;
private Matrix mMatrix = new Matrix();
private Paint mPaintOutline = new Paint();
private Path mPathSide = new Path();
private Path mPathSideOutline = new Path();
private Path mPathOutline = new Path();
private int mSideWidth;
public class Slice {
public long value;
public Path path = new Path();
public Path pathSide = new Path();
public Path pathOutline = new Path();
public Paint paint;
public Slice(long value, int color) {
this.value = value;
this.paint = buildFillPaint(color, getResources());
}
}
public PieChartView(Context context) {
this(context, null);
}
public PieChartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PieChartView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mPaintOutline.setColor(Color.BLACK);
mPaintOutline.setStyle(Style.STROKE);
mPaintOutline.setStrokeWidth(3f * getResources().getDisplayMetrics().density);
mPaintOutline.setAntiAlias(true);
mSideWidth = (int) (20 * getResources().getDisplayMetrics().density);
setWillNotDraw(false);
}
private static Paint buildFillPaint(int color, Resources res) {
final Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(Style.FILL_AND_STROKE);
paint.setAntiAlias(true);
if (FILL_GRADIENT) {
final int width = (int) (280 * res.getDisplayMetrics().density);
paint.setShader(new RadialGradient(0, 0, width, color, darken(color), TileMode.MIRROR));
}
return paint;
}
public void setOriginAngle(int originAngle) {
mOriginAngle = originAngle;
}
public void addSlice(long value, int color) {
mSlices.add(new Slice(value, color));
}
public void removeAllSlices() {
mSlices.clear();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final float centerX = getWidth() / 2;
final float centerY = getHeight() / 2;
mMatrix.reset();
mMatrix.postScale(0.665f, 0.95f, centerX, centerY);
mMatrix.postRotate(-40, centerX, centerY);
generatePath();
}
public void generatePath() {
if (LOGD) Log.d(TAG, "generatePath()");
long total = 0;
for (Slice slice : mSlices) {
slice.path.reset();
slice.pathSide.reset();
slice.pathOutline.reset();
total += slice.value;
}
mPathSide.reset();
mPathSideOutline.reset();
mPathOutline.reset();
// bail when not enough stats to render
if (total == 0) {
invalidate();
return;
}
final int width = getWidth();
final int height = getHeight();
final RectF rect = new RectF(0, 0, width, height);
final RectF rectSide = new RectF();
rectSide.set(rect);
rectSide.offset(-mSideWidth, 0);
mPathSide.addOval(rectSide, Direction.CW);
mPathSideOutline.addOval(rectSide, Direction.CW);
mPathOutline.addOval(rect, Direction.CW);
int startAngle = mOriginAngle;
for (Slice slice : mSlices) {
final int sweepAngle = (int) (slice.value * 360 / total);
final int endAngle = startAngle + sweepAngle;
final float startAngleMod = startAngle % 360;
final float endAngleMod = endAngle % 360;
final boolean startSideVisible = startAngleMod > 90 && startAngleMod < 270;
final boolean endSideVisible = endAngleMod > 90 && endAngleMod < 270;
// draw slice
slice.path.moveTo(rect.centerX(), rect.centerY());
slice.path.arcTo(rect, startAngle, sweepAngle);
slice.path.lineTo(rect.centerX(), rect.centerY());
if (startSideVisible || endSideVisible) {
// when start is beyond horizon, push until visible
final float startAngleSide = startSideVisible ? startAngle : 450;
final float endAngleSide = endSideVisible ? endAngle : 270;
final float sweepAngleSide = endAngleSide - startAngleSide;
// draw slice side
slice.pathSide.moveTo(rect.centerX(), rect.centerY());
slice.pathSide.arcTo(rect, startAngleSide, 0);
slice.pathSide.rLineTo(-mSideWidth, 0);
slice.pathSide.arcTo(rectSide, startAngleSide, sweepAngleSide);
slice.pathSide.rLineTo(mSideWidth, 0);
slice.pathSide.arcTo(rect, endAngleSide, -sweepAngleSide);
}
// draw slice outline
slice.pathOutline.moveTo(rect.centerX(), rect.centerY());
slice.pathOutline.arcTo(rect, startAngle, 0);
if (startSideVisible) {
slice.pathOutline.rLineTo(-mSideWidth, 0);
}
slice.pathOutline.moveTo(rect.centerX(), rect.centerY());
slice.pathOutline.arcTo(rect, startAngle + sweepAngle, 0);
if (endSideVisible) {
slice.pathOutline.rLineTo(-mSideWidth, 0);
}
startAngle += sweepAngle;
}
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
canvas.concat(mMatrix);
for (Slice slice : mSlices) {
canvas.drawPath(slice.pathSide, slice.paint);
}
canvas.drawPath(mPathSideOutline, mPaintOutline);
for (Slice slice : mSlices) {
canvas.drawPath(slice.path, slice.paint);
canvas.drawPath(slice.pathOutline, mPaintOutline);
}
canvas.drawPath(mPathOutline, mPaintOutline);
}
public static int darken(int color) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] /= 2;
hsv[1] /= 2;
return Color.HSVToColor(hsv);
}
}