From 29d56b303f39012c0fb83a2b138e227b5413f4ce Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 20 Jun 2011 17:06:52 -0700 Subject: [PATCH] Detect radios in data usage, control them. Teach data usage to inspect hardware radios to determine which tabs and options to display. Control "Mobile data enabled" state through ConnectivityManager. Persist "Show Wi-Fi" state. Bug: 4599714, 4645276, 4620024, 4599271, 4596812 Change-Id: I4479593d74a8ba744a056767422f1e03182a7a94 --- res/layout/preference.xml | 63 ++++++ res/values/dimens.xml | 1 + .../android/settings/DataUsageSummary.java | 214 +++++++++++++----- 3 files changed, 217 insertions(+), 61 deletions(-) create mode 100644 res/layout/preference.xml diff --git a/res/layout/preference.xml b/res/layout/preference.xml new file mode 100644 index 00000000000..06d8f249e0b --- /dev/null +++ b/res/layout/preference.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 418121b6b8a..516b5f22e87 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -20,4 +20,5 @@ 90sp 16dip 32dip + 220dip diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java index 79d0baf2072..ef2282aef97 100644 --- a/src/com/android/settings/DataUsageSummary.java +++ b/src/com/android/settings/DataUsageSummary.java @@ -16,6 +16,8 @@ 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; @@ -26,6 +28,7 @@ import static android.net.NetworkTemplate.MATCH_MOBILE_4G; 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; @@ -34,10 +37,12 @@ import android.app.Fragment; 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; @@ -49,10 +54,8 @@ import android.os.AsyncTask; 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; @@ -64,7 +67,6 @@ import android.view.Menu; 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; @@ -72,10 +74,14 @@ import android.widget.AdapterView.OnItemClickListener; 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; @@ -83,6 +89,7 @@ import android.widget.TabHost.TabSpec; 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; @@ -93,6 +100,10 @@ import java.util.Arrays; 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; @@ -114,6 +125,12 @@ public class DataUsageSummary extends Fragment { 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; @@ -123,8 +140,8 @@ public class DataUsageSummary extends Fragment { private View mHeader; private LinearLayout mSwitches; - private SwitchPreference mDataEnabled; - private CheckBoxPreference mDisableAtLimit; + private Switch mDataEnabled; + private CheckBox mDisableAtLimit; private View mDataEnabledView; private View mDisableAtLimitView; @@ -133,7 +150,6 @@ public class DataUsageSummary extends Fragment { private Spinner mCycleSpinner; private CycleAdapter mCycleAdapter; - // TODO: persist show wifi flag private boolean mShowWifi = false; private NetworkTemplate mTemplate = null; @@ -143,6 +159,9 @@ public class DataUsageSummary extends Fragment { private String mIntentTab = null; + /** Flag used to ignore listeners during binding. */ + private boolean mBinding; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -151,10 +170,15 @@ public class DataUsageSummary extends Fragment { 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); } @@ -175,17 +199,12 @@ public class DataUsageSummary extends Fragment { mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false); mListView.addHeaderView(mHeader, null, false); - mDataEnabled = new SwitchPreference(context); - mDisableAtLimit = new CheckBoxPreference(context); + mDataEnabled = new Switch(inflater.getContext()); + mDataEnabledView = inflatePreference(inflater, mSwitches, mDataEnabled); + mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener); - // kick refresh once to force-create views - refreshPreferenceViews(); - - // 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); @@ -197,9 +216,11 @@ public class DataUsageSummary extends Fragment { 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(); @@ -235,8 +256,19 @@ public class DataUsageSummary extends Fragment { @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 @@ -251,6 +283,7 @@ public class DataUsageSummary extends Fragment { } case R.id.action_show_wifi: { mShowWifi = !item.isChecked(); + mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply(); item.setChecked(mShowWifi); updateTabs(); return true; @@ -273,24 +306,25 @@ public class DataUsageSummary extends Fragment { * 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); @@ -340,11 +374,21 @@ public class DataUsageSummary extends Fragment { * 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); @@ -360,26 +404,26 @@ public class DataUsageSummary extends Fragment { 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); @@ -397,8 +441,7 @@ public class DataUsageSummary extends Fragment { // 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) { @@ -434,9 +477,6 @@ public class DataUsageSummary extends Fragment { // generate cycle list based on policy and available history updateCycleList(policy); } - - // kick preference views so they rebind from changes above - refreshPreferenceViews(); } /** @@ -506,25 +546,23 @@ public class DataUsageSummary extends Fragment { 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(); @@ -724,12 +762,10 @@ public class DataUsageSummary extends Fragment { 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); @@ -995,4 +1031,60 @@ public class DataUsageSummary extends Fragment { 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); + } + }