Merge "Data Usage, materialized!" into lmp-dev
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user