package com.android.settings;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT;
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.ConnectivityManager;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.NetworkPolicy;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
-import android.preference.SwitchPreference;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.NumberPicker;
import android.widget.Spinner;
+import android.widget.Switch;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabHost.TabContentFactory;
import android.widget.TabWidget;
import android.widget.TextView;
+import com.android.internal.telephony.Phone;
import com.android.settings.net.NetworkPolicyEditor;
import com.android.settings.widget.DataUsageChartView;
import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
import java.util.Collections;
import java.util.Locale;
+/**
+ * Panel show data usage history across various networks, including options to
+ * inspect based on usage cycle and control through {@link NetworkPolicy}.
+ */
public class DataUsageSummary extends Fragment {
private static final String TAG = "DataUsage";
private static final boolean LOGD = true;
private INetworkStatsService mStatsService;
private INetworkPolicyManager mPolicyService;
+ private ConnectivityManager mConnService;
+
+ private static final String PREF_FILE = "data_usage";
+ private static final String PREF_SHOW_WIFI = "show_wifi";
+
+ private SharedPreferences mPrefs;
private TabHost mTabHost;
private TabWidget mTabWidget;
private View mHeader;
private LinearLayout mSwitches;
- private SwitchPreference mDataEnabled;
- private CheckBoxPreference mDisableAtLimit;
+ private Switch mDataEnabled;
+ private CheckBox mDisableAtLimit;
private View mDataEnabledView;
private View mDisableAtLimitView;
private Spinner mCycleSpinner;
private CycleAdapter mCycleAdapter;
- // TODO: persist show wifi flag
private boolean mShowWifi = false;
private NetworkTemplate mTemplate = null;
private String mIntentTab = null;
+ /** Flag used to ignore listeners during binding. */
+ private boolean mBinding;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
mPolicyService = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+ mConnService = (ConnectivityManager) getActivity().getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
mPolicyEditor = new NetworkPolicyEditor(mPolicyService);
mPolicyEditor.read();
+ mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false);
+
setHasOptionsMenu(true);
}
mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
mListView.addHeaderView(mHeader, null, false);
- mDataEnabled = new SwitchPreference(context);
- mDisableAtLimit = new CheckBoxPreference(context);
-
- // kick refresh once to force-create views
- refreshPreferenceViews();
+ mDataEnabled = new Switch(inflater.getContext());
+ mDataEnabledView = inflatePreference(inflater, mSwitches, mDataEnabled);
+ mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
- // TODO: remove once thin preferences are supported (48dip)
- mDataEnabledView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
- mDisableAtLimitView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
-
- mDataEnabledView.setOnClickListener(mDataEnabledListener);
+ mDisableAtLimit = new CheckBox(inflater.getContext());
+ mDisableAtLimitView = inflatePreference(inflater, mSwitches, mDisableAtLimit);
mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
mCycleSpinner.setAdapter(mCycleAdapter);
mCycleSpinner.setOnItemSelectedListener(mCycleListener);
+ final int chartHeight = getResources().getDimensionPixelSize(
+ R.dimen.data_usage_chart_height);
mChart = new DataUsageChartView(context);
mChart.setListener(mChartListener);
- mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, 350));
+ mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, chartHeight));
mListView.addHeaderView(mChart, null, false);
mAdapter = new DataUsageAdapter();
@Override
public void onPrepareOptionsMenu(Menu menu) {
+ final Context context = getActivity();
+
final MenuItem split4g = menu.findItem(R.id.action_split_4g);
+ split4g.setVisible(hasMobile4gRadio(context));
split4g.setChecked(isMobilePolicySplit());
+
+ final MenuItem showWifi = menu.findItem(R.id.action_show_wifi);
+ showWifi.setVisible(hasMobileRadio(context) && hasWifiRadio(context));
+ showWifi.setChecked(mShowWifi);
+
+ final MenuItem settings = menu.findItem(R.id.action_settings);
+ settings.setVisible(split4g.isVisible() || showWifi.isVisible());
+
}
@Override
}
case R.id.action_show_wifi: {
mShowWifi = !item.isChecked();
+ mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply();
item.setChecked(mShowWifi);
updateTabs();
return true;
* first tab, and kicks off a full rebind of body contents.
*/
private void updateTabs() {
- final boolean mobileSplit = isMobilePolicySplit();
- final boolean tabsVisible = mobileSplit || mShowWifi;
- mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
+ final Context context = getActivity();
mTabHost.clearAllTabs();
- if (mobileSplit) {
+ final boolean mobileSplit = isMobilePolicySplit();
+ if (mobileSplit && hasMobile4gRadio(context)) {
mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
}
- if (mShowWifi) {
+ if (mShowWifi && hasWifiRadio(context) && hasMobileRadio(context)) {
if (!mobileSplit) {
mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
}
mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
}
- if (mTabWidget.getTabCount() > 0) {
+ final boolean hasTabs = mTabWidget.getTabCount() > 0;
+ mTabWidget.setVisibility(hasTabs ? View.VISIBLE : View.GONE);
+ if (hasTabs) {
if (mIntentTab != null) {
// select default tab, which will kick off updateBody()
mTabHost.setCurrentTabByTag(mIntentTab);
* binds them to visible controls.
*/
private void updateBody() {
- final String tabTag = mTabHost.getCurrentTabTag();
- final String currentTab = tabTag != null ? tabTag : TAB_MOBILE;
+ mBinding = true;
final Context context = getActivity();
- final String subscriberId = getActiveSubscriberId(context);
+ final String tabTag = mTabHost.getCurrentTabTag();
+
+ final String currentTab;
+ if (tabTag != null) {
+ currentTab = tabTag;
+ } else if (hasMobileRadio(context)) {
+ currentTab = TAB_MOBILE;
+ } else if (hasWifiRadio(context)) {
+ currentTab = TAB_WIFI;
+ } else {
+ throw new IllegalStateException("no mobile or wifi radios");
+ }
if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
mDisableAtLimitView.setVisibility(View.VISIBLE);
}
+ final String subscriberId = getActiveSubscriberId(context);
if (TAB_MOBILE.equals(currentTab)) {
- mDataEnabled.setTitle(R.string.data_usage_enable_mobile);
- mDisableAtLimit.setTitle(R.string.data_usage_disable_mobile_limit);
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit);
+ mDataEnabled.setChecked(mConnService.getMobileDataEnabled());
mTemplate = new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId);
} else if (TAB_3G.equals(currentTab)) {
- mDataEnabled.setTitle(R.string.data_usage_enable_3g);
- mDisableAtLimit.setTitle(R.string.data_usage_disable_3g_limit);
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit);
+ // TODO: bind mDataEnabled to 3G radio state
mTemplate = new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId);
} else if (TAB_4G.equals(currentTab)) {
- mDataEnabled.setTitle(R.string.data_usage_enable_4g);
- mDisableAtLimit.setTitle(R.string.data_usage_disable_4g_limit);
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit);
+ // TODO: bind mDataEnabled to 4G radio state
mTemplate = new NetworkTemplate(MATCH_MOBILE_4G, subscriberId);
-
}
- // TODO: populate checkbox based on radio preferences
- mDataEnabled.setChecked(true);
-
try {
// load stats for current template
mHistory = mStatsService.getHistoryForNetwork(mTemplate);
// force scroll to top of body
mListView.smoothScrollToPosition(0);
- // kick preference views so they rebind from changes above
- refreshPreferenceViews();
+ mBinding = false;
}
private void setPolicyCycleDay(int cycleDay) {
// generate cycle list based on policy and available history
updateCycleList(policy);
}
-
- // kick preference views so they rebind from changes above
- refreshPreferenceViews();
}
/**
mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
}
- /**
- * Force rebind of hijacked {@link Preference} views.
- */
- private void refreshPreferenceViews() {
- mDataEnabledView = mDataEnabled.getView(mDataEnabledView, mListView);
- mDisableAtLimitView = mDisableAtLimit.getView(mDisableAtLimitView, mListView);
- }
-
- private OnClickListener mDataEnabledListener = new OnClickListener() {
+ private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
/** {@inheritDoc} */
- public void onClick(View v) {
- mDataEnabled.setChecked(!mDataEnabled.isChecked());
- refreshPreferenceViews();
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (mBinding) return;
- // TODO: wire up to telephony to enable/disable radios
+ final boolean dataEnabled = isChecked;
+ mDataEnabled.setChecked(dataEnabled);
+
+ switch (mTemplate.getMatchRule()) {
+ case MATCH_MOBILE_ALL: {
+ mConnService.setMobileDataEnabled(dataEnabled);
+ }
+ }
}
};
- private OnClickListener mDisableAtLimitListener = new OnClickListener() {
+ private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() {
/** {@inheritDoc} */
public void onClick(View v) {
final boolean disableAtLimit = !mDisableAtLimit.isChecked();
for (int i = 0; i < stats.size; i++) {
final long total = stats.rx[i] + stats.tx[i];
- if (total > 0) {
- final AppUsageItem item = new AppUsageItem();
- item.uid = stats.uid[i];
- item.total = total;
- mItems.add(item);
- }
+ final AppUsageItem item = new AppUsageItem();
+ item.uid = stats.uid[i];
+ item.total = total;
+ mItems.add(item);
}
Collections.sort(mItems);
return label;
}
+ /**
+ * Test if device has a mobile data radio.
+ */
+ private static boolean hasMobileRadio(Context context) {
+ final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+
+ // mobile devices should have MOBILE network tracker regardless of
+ // connection status.
+ return conn.getNetworkInfo(TYPE_MOBILE) != null;
+ }
+
+ /**
+ * Test if device has a mobile 4G data radio.
+ */
+ private static boolean hasMobile4gRadio(Context context) {
+ final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+
+ // WiMAX devices should have WiMAX network tracker regardless of
+ // connection status.
+ final boolean hasWimax = conn.getNetworkInfo(TYPE_WIMAX) != null;
+ final boolean hasLte = telephony.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE;
+ return hasWimax || hasLte;
+ }
+
+ /**
+ * Test if device has a Wi-Fi data radio.
+ */
+ private static boolean hasWifiRadio(Context context) {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
+ }
+
+ /**
+ * Inflate a {@link Preference} style layout, adding the given {@link View}
+ * widget into {@link android.R.id#widget_frame}.
+ */
+ private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) {
+ final View view = inflater.inflate(R.layout.preference, root, false);
+ final LinearLayout widgetFrame = (LinearLayout) view.findViewById(
+ android.R.id.widget_frame);
+ widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ return view;
+ }
+
+ /**
+ * Set {@link android.R.id#title} for a preference view inflated with
+ * {@link #inflatePreference(LayoutInflater, View, View)}.
+ */
+ private static void setPreferenceTitle(View parent, int resId) {
+ final TextView title = (TextView) parent.findViewById(android.R.id.title);
+ title.setText(resId);
+ }
+
}