Merge "Detect radios in data usage, control them."
diff --git a/res/layout/preference.xml b/res/layout/preference.xml
new file mode 100644
index 0000000..06d8f24
--- /dev/null
+++ b/res/layout/preference.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dip"
+ android:gravity="center_vertical"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:background="?android:attr/selectableItemBackground">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="15dip"
+ android:layout_marginRight="6dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignLeft="@android:id/title"
+ android:visibility="gone"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="4" />
+
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 418121b..516b5f2 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,4 +20,5 @@
<dimen name="vpn_connect_input_box_label_width">90sp</dimen>
<dimen name="device_memory_usage_button_width">16dip</dimen>
<dimen name="device_memory_usage_button_height">32dip</dimen>
+ <dimen name="data_usage_chart_height">220dip</dimen>
</resources>
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 79d0baf..ef2282a 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_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.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.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.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.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.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.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 @@
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 @@
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 @@
private Spinner mCycleSpinner;
private CycleAdapter mCycleAdapter;
- // TODO: persist show wifi flag
private boolean mShowWifi = false;
private NetworkTemplate mTemplate = null;
@@ -143,6 +159,9 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
}
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 @@
* 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 @@
* 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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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);
+ }
+
}