More data usage chart iteration, app details.

Moved app details back into single Fragment to support animations and
template tabs.  Show the network in background behind app details
chart series to match designs.

Clamping sweeps at axis boundaries.

Bug: 4813014, 4598460, 4818029
Change-Id: I72c0b21ee1d595e4da31d293ae0dab9e801041f3
diff --git a/res/drawable-hdpi/data_sweep_left_activated.9.png b/res/drawable-hdpi/data_sweep_left_activated.9.png
index e91ccf5..28efd35 100644
--- a/res/drawable-hdpi/data_sweep_left_activated.9.png
+++ b/res/drawable-hdpi/data_sweep_left_activated.9.png
Binary files differ
diff --git a/res/drawable-hdpi/data_sweep_left_default.9.png b/res/drawable-hdpi/data_sweep_left_default.9.png
index 76f33a5..4b6f2df 100644
--- a/res/drawable-hdpi/data_sweep_left_default.9.png
+++ b/res/drawable-hdpi/data_sweep_left_default.9.png
Binary files differ
diff --git a/res/drawable-hdpi/data_sweep_limit_activated.9.png b/res/drawable-hdpi/data_sweep_limit_activated.9.png
index 4744037..59de0d3 100644
--- a/res/drawable-hdpi/data_sweep_limit_activated.9.png
+++ b/res/drawable-hdpi/data_sweep_limit_activated.9.png
Binary files differ
diff --git a/res/drawable-hdpi/data_sweep_limit_default.9.png b/res/drawable-hdpi/data_sweep_limit_default.9.png
index dea3a0b..d7f3a88 100644
--- a/res/drawable-hdpi/data_sweep_limit_default.9.png
+++ b/res/drawable-hdpi/data_sweep_limit_default.9.png
Binary files differ
diff --git a/res/drawable-hdpi/data_sweep_right_activated.9.png b/res/drawable-hdpi/data_sweep_right_activated.9.png
index afb15d6..ccd7ff9 100644
--- a/res/drawable-hdpi/data_sweep_right_activated.9.png
+++ b/res/drawable-hdpi/data_sweep_right_activated.9.png
Binary files differ
diff --git a/res/drawable-hdpi/data_sweep_right_default.9.png b/res/drawable-hdpi/data_sweep_right_default.9.png
index 9d3808f..1179fde 100644
--- a/res/drawable-hdpi/data_sweep_right_default.9.png
+++ b/res/drawable-hdpi/data_sweep_right_default.9.png
Binary files differ
diff --git a/res/drawable-hdpi/data_sweep_warning_activated.9.png b/res/drawable-hdpi/data_sweep_warning_activated.9.png
index 81a7aa8..9ecd28b 100644
--- a/res/drawable-hdpi/data_sweep_warning_activated.9.png
+++ b/res/drawable-hdpi/data_sweep_warning_activated.9.png
Binary files differ
diff --git a/res/drawable-hdpi/data_sweep_warning_default.9.png b/res/drawable-hdpi/data_sweep_warning_default.9.png
index e2485fe..ec2b9df 100644
--- a/res/drawable-hdpi/data_sweep_warning_default.9.png
+++ b/res/drawable-hdpi/data_sweep_warning_default.9.png
Binary files differ
diff --git a/res/drawable-mdpi/data_sweep_left_activated.9.png b/res/drawable-mdpi/data_sweep_left_activated.9.png
index 39111d4..fc6764a 100644
--- a/res/drawable-mdpi/data_sweep_left_activated.9.png
+++ b/res/drawable-mdpi/data_sweep_left_activated.9.png
Binary files differ
diff --git a/res/drawable-mdpi/data_sweep_left_default.9.png b/res/drawable-mdpi/data_sweep_left_default.9.png
index 139ed1e..31343e7 100644
--- a/res/drawable-mdpi/data_sweep_left_default.9.png
+++ b/res/drawable-mdpi/data_sweep_left_default.9.png
Binary files differ
diff --git a/res/drawable-mdpi/data_sweep_limit_activated.9.png b/res/drawable-mdpi/data_sweep_limit_activated.9.png
index 64d539a..b01d5f0 100644
--- a/res/drawable-mdpi/data_sweep_limit_activated.9.png
+++ b/res/drawable-mdpi/data_sweep_limit_activated.9.png
Binary files differ
diff --git a/res/drawable-mdpi/data_sweep_limit_default.9.png b/res/drawable-mdpi/data_sweep_limit_default.9.png
index db41d66..a59649a 100644
--- a/res/drawable-mdpi/data_sweep_limit_default.9.png
+++ b/res/drawable-mdpi/data_sweep_limit_default.9.png
Binary files differ
diff --git a/res/drawable-mdpi/data_sweep_right_activated.9.png b/res/drawable-mdpi/data_sweep_right_activated.9.png
index 2fba366..5314538 100644
--- a/res/drawable-mdpi/data_sweep_right_activated.9.png
+++ b/res/drawable-mdpi/data_sweep_right_activated.9.png
Binary files differ
diff --git a/res/drawable-mdpi/data_sweep_right_default.9.png b/res/drawable-mdpi/data_sweep_right_default.9.png
index 533f165..cc3b586 100644
--- a/res/drawable-mdpi/data_sweep_right_default.9.png
+++ b/res/drawable-mdpi/data_sweep_right_default.9.png
Binary files differ
diff --git a/res/drawable-mdpi/data_sweep_warning_activated.9.png b/res/drawable-mdpi/data_sweep_warning_activated.9.png
index 6813526..ed44d4d 100644
--- a/res/drawable-mdpi/data_sweep_warning_activated.9.png
+++ b/res/drawable-mdpi/data_sweep_warning_activated.9.png
Binary files differ
diff --git a/res/drawable-mdpi/data_sweep_warning_default.9.png b/res/drawable-mdpi/data_sweep_warning_default.9.png
index 65ab32f..760ea18 100644
--- a/res/drawable-mdpi/data_sweep_warning_default.9.png
+++ b/res/drawable-mdpi/data_sweep_warning_default.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_sweep_left_activated.9.png b/res/drawable-xhdpi/data_sweep_left_activated.9.png
index 83883be..ff9631e 100644
--- a/res/drawable-xhdpi/data_sweep_left_activated.9.png
+++ b/res/drawable-xhdpi/data_sweep_left_activated.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_sweep_left_default.9.png b/res/drawable-xhdpi/data_sweep_left_default.9.png
index 968825b..2e0651c 100644
--- a/res/drawable-xhdpi/data_sweep_left_default.9.png
+++ b/res/drawable-xhdpi/data_sweep_left_default.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_sweep_limit_activated.9.png b/res/drawable-xhdpi/data_sweep_limit_activated.9.png
index de988ae..ae02388 100644
--- a/res/drawable-xhdpi/data_sweep_limit_activated.9.png
+++ b/res/drawable-xhdpi/data_sweep_limit_activated.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_sweep_limit_default.9.png b/res/drawable-xhdpi/data_sweep_limit_default.9.png
index 2c9b534..7391148 100644
--- a/res/drawable-xhdpi/data_sweep_limit_default.9.png
+++ b/res/drawable-xhdpi/data_sweep_limit_default.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_sweep_right_activated.9.png b/res/drawable-xhdpi/data_sweep_right_activated.9.png
index 25508b5..81c1a3c 100644
--- a/res/drawable-xhdpi/data_sweep_right_activated.9.png
+++ b/res/drawable-xhdpi/data_sweep_right_activated.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_sweep_right_default.9.png b/res/drawable-xhdpi/data_sweep_right_default.9.png
index 7363cd7..94a9f61 100644
--- a/res/drawable-xhdpi/data_sweep_right_default.9.png
+++ b/res/drawable-xhdpi/data_sweep_right_default.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_sweep_warning_activated.9.png b/res/drawable-xhdpi/data_sweep_warning_activated.9.png
index 2da464b..0a2d2b1 100644
--- a/res/drawable-xhdpi/data_sweep_warning_activated.9.png
+++ b/res/drawable-xhdpi/data_sweep_warning_activated.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_sweep_warning_default.9.png b/res/drawable-xhdpi/data_sweep_warning_default.9.png
index c7a7b29..d5f6044 100644
--- a/res/drawable-xhdpi/data_sweep_warning_default.9.png
+++ b/res/drawable-xhdpi/data_sweep_warning_default.9.png
Binary files differ
diff --git a/res/drawable/data_sweep_left.xml b/res/drawable/data_sweep_left.xml
index 739a74e..cb801a0 100644
--- a/res/drawable/data_sweep_left.xml
+++ b/res/drawable/data_sweep_left.xml
@@ -17,6 +17,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
     android:exitFadeDuration="@android:integer/config_mediumAnimTime">
 
-    <item android:state_activated="true" android:drawable="@drawable/data_sweep_left_activated" />
-    <item android:state_activated="false" android:drawable="@drawable/data_sweep_left_default" />
+    <item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_left_activated" />
+    <item android:drawable="@drawable/data_sweep_left_default" />
 </selector>
diff --git a/res/drawable/data_sweep_limit.xml b/res/drawable/data_sweep_limit.xml
index 29ecec8..378b0aa 100644
--- a/res/drawable/data_sweep_limit.xml
+++ b/res/drawable/data_sweep_limit.xml
@@ -17,6 +17,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
     android:exitFadeDuration="@android:integer/config_mediumAnimTime">
 
-    <item android:state_activated="true" android:drawable="@drawable/data_sweep_limit_activated" />
-    <item android:state_activated="false" android:drawable="@drawable/data_sweep_limit_default" />
+    <item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_limit_activated" />
+    <item android:drawable="@drawable/data_sweep_limit_default" />
 </selector>
diff --git a/res/drawable/data_sweep_right.xml b/res/drawable/data_sweep_right.xml
index 1a11469..a75a1b2 100644
--- a/res/drawable/data_sweep_right.xml
+++ b/res/drawable/data_sweep_right.xml
@@ -17,6 +17,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
     android:exitFadeDuration="@android:integer/config_mediumAnimTime">
 
-    <item android:state_activated="true" android:drawable="@drawable/data_sweep_right_activated" />
-    <item android:state_activated="false" android:drawable="@drawable/data_sweep_right_default" />
+    <item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_right_activated" />
+    <item android:drawable="@drawable/data_sweep_right_default" />
 </selector>
diff --git a/res/drawable/data_sweep_warning.xml b/res/drawable/data_sweep_warning.xml
index 5cafe06..001d0c5 100644
--- a/res/drawable/data_sweep_warning.xml
+++ b/res/drawable/data_sweep_warning.xml
@@ -17,6 +17,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
     android:exitFadeDuration="@android:integer/config_mediumAnimTime">
 
-    <item android:state_activated="true" android:drawable="@drawable/data_sweep_warning_activated" />
-    <item android:state_activated="false" android:drawable="@drawable/data_sweep_warning_default" />
+    <item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_warning_activated" />
+    <item android:drawable="@drawable/data_sweep_warning_default" />
 </selector>
diff --git a/res/layout/data_usage_chart.xml b/res/layout/data_usage_chart.xml
index 5fd640f..199a38e 100644
--- a/res/layout/data_usage_chart.xml
+++ b/res/layout/data_usage_chart.xml
@@ -17,6 +17,7 @@
 <com.android.settings.widget.DataUsageChartView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+    android:id="@+id/chart"
     android:layout_width="match_parent"
     android:layout_height="220dip"
     android:padding="16dip">
@@ -40,11 +41,19 @@
         settings:fillColor="#c050ade5"
         settings:fillColorSecondary="#88566abc" />
 
+    <com.android.settings.widget.ChartNetworkSeriesView
+        android:id="@+id/detail_series"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="left|bottom"
+        settings:strokeColor="#d88d3a"
+        settings:fillColor="#c0ba7f3e"
+        settings:fillColorSecondary="#0000" />
+
     <com.android.settings.widget.ChartSweepView
         android:id="@+id/sweep_left"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:layout_gravity="center_horizontal"
         settings:sweepDrawable="@drawable/data_sweep_left"
         settings:followAxis="horizontal"
         settings:showLabel="false" />
@@ -53,7 +62,6 @@
         android:id="@+id/sweep_right"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:layout_gravity="center_horizontal"
         settings:sweepDrawable="@drawable/data_sweep_right"
         settings:followAxis="horizontal"
         settings:showLabel="false" />
@@ -62,7 +70,6 @@
         android:id="@+id/sweep_limit"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
         settings:sweepDrawable="@drawable/data_sweep_limit"
         settings:followAxis="vertical"
         settings:showLabel="true" />
@@ -71,7 +78,6 @@
         android:id="@+id/sweep_warning"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
         settings:sweepDrawable="@drawable/data_sweep_warning"
         settings:followAxis="vertical"
         settings:showLabel="true" />
diff --git a/res/layout/data_usage_detail.xml b/res/layout/data_usage_detail.xml
index 9415b3f..2c8e490 100644
--- a/res/layout/data_usage_detail.xml
+++ b/res/layout/data_usage_detail.xml
@@ -14,43 +14,35 @@
      limitations under the License.
 -->
 
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/app_detail"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <LinearLayout
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/app_title"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="16dip" />
 
-        <FrameLayout
-            android:id="@+id/chart_container"
-            android:layout_width="match_parent"
-            android:layout_height="233dip" />
+    <TextView
+        android:id="@+id/app_subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="16dip" />
 
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="16dip" />
+    <Button
+        android:id="@+id/app_settings"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dip"
+        android:text="@string/data_usage_app_settings" />
 
-        <TextView
-            android:id="@android:id/text1"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="16dip" />
+    <LinearLayout
+        android:id="@+id/app_switches"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" />
 
-        <Button
-            android:id="@+id/data_usage_app_settings"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dip"
-            android:text="@string/data_usage_app_settings" />
-
-        <LinearLayout
-            android:id="@+id/switches"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical" />
-
-    </LinearLayout>
-</ScrollView>
+</LinearLayout>
diff --git a/res/layout/data_usage_header.xml b/res/layout/data_usage_header.xml
index 8e6f054..3f4ca5b 100644
--- a/res/layout/data_usage_header.xml
+++ b/res/layout/data_usage_header.xml
@@ -20,7 +20,7 @@
     android:orientation="vertical">
 
     <LinearLayout
-        android:id="@+id/switches"
+        android:id="@+id/network_switches"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical"
@@ -48,4 +48,7 @@
 
     </LinearLayout>
 
+    <include layout="@layout/data_usage_chart" />
+    <include layout="@layout/data_usage_detail" />
+
 </LinearLayout>
diff --git a/src/com/android/settings/DataUsageAppDetail.java b/src/com/android/settings/DataUsageAppDetail.java
deleted file mode 100644
index 6294ad3..0000000
--- a/src/com/android/settings/DataUsageAppDetail.java
+++ /dev/null
@@ -1,309 +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;
-
-import static android.net.NetworkPolicyManager.POLICY_NONE;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
-import static com.android.settings.DataUsageSummary.getHistoryBounds;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Color;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
-import android.net.NetworkPolicyManager;
-import android.net.NetworkStats;
-import android.net.NetworkStatsHistory;
-import android.net.NetworkTemplate;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.preference.CheckBoxPreference;
-import android.preference.Preference;
-import android.text.format.DateUtils;
-import android.text.format.Formatter;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.settings.widget.DataUsageChartView;
-import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
-
-public class DataUsageAppDetail extends Fragment {
-    private static final String TAG = "DataUsage";
-    private static final boolean LOGD = true;
-
-    public static final String EXTRA_UID = "uid";
-    public static final String EXTRA_NETWORK_TEMPLATE = "networkTemplate";
-
-    private int mUid;
-    private NetworkTemplate mTemplate;
-
-    private Intent mAppSettingsIntent;
-
-    private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
-
-    private INetworkStatsService mStatsService;
-    private INetworkPolicyManager mPolicyService;
-
-    private CheckBoxPreference mRestrictBackground;
-    private View mRestrictBackgroundView;
-
-    private FrameLayout mChartContainer;
-    private TextView mTitle;
-    private TextView mText1;
-    private Button mAppSettings;
-    private LinearLayout mSwitches;
-
-    private DataUsageChartView mChart;
-    private NetworkStatsHistory mHistory;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mStatsService = INetworkStatsService.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
-        mPolicyService = INetworkPolicyManager.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-
-        final Context context = inflater.getContext();
-        final View view = inflater.inflate(R.layout.data_usage_detail, container, false);
-
-        mChartContainer = (FrameLayout) view.findViewById(R.id.chart_container);
-        mTitle = (TextView) view.findViewById(android.R.id.title);
-        mText1 = (TextView) view.findViewById(android.R.id.text1);
-        mAppSettings = (Button) view.findViewById(R.id.data_usage_app_settings);
-        mSwitches = (LinearLayout) view.findViewById(R.id.switches);
-
-        mRestrictBackground = new CheckBoxPreference(context);
-        mRestrictBackground.setTitle(R.string.data_usage_app_restrict_background);
-        mRestrictBackground.setSummary(R.string.data_usage_app_restrict_background_summary);
-
-        // kick refresh once to force-create views
-        refreshPreferenceViews();
-
-        mSwitches.addView(mRestrictBackgroundView);
-        mRestrictBackgroundView.setOnClickListener(mRestrictBackgroundListener);
-
-        mAppSettings.setOnClickListener(mAppSettingsListener);
-
-        mChart = new DataUsageChartView(context);
-        mChartContainer.addView(mChart);
-
-        mChart.setListener(mChartListener);
-        mChart.setChartColor(Color.parseColor("#d88d3a"), Color.parseColor("#c0ba7f3e"),
-                Color.parseColor("#88566abc"));
-
-        return view;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        updateBody();
-    }
-
-    private void updateBody() {
-        final PackageManager pm = getActivity().getPackageManager();
-
-        mUid = getArguments().getInt(EXTRA_UID);
-        mTemplate = getArguments().getParcelable(EXTRA_NETWORK_TEMPLATE);
-
-        mTitle.setText(pm.getNameForUid(mUid));
-
-        // enable settings button when package provides it
-        // TODO: target torwards entire UID instead of just first package
-        final String[] packageNames = pm.getPackagesForUid(mUid);
-        if (packageNames != null && packageNames.length > 0) {
-            mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
-            mAppSettingsIntent.setPackage(packageNames[0]);
-            mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
-
-            final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null;
-            mAppSettings.setEnabled(matchFound);
-
-        } else {
-            mAppSettingsIntent = null;
-            mAppSettings.setEnabled(false);
-        }
-
-        try {
-            // load stats for current uid and template
-            // TODO: read template from extras
-            mHistory = mStatsService.getHistoryForUid(mTemplate, mUid, NetworkStats.TAG_NONE);
-        } catch (RemoteException e) {
-            // since we can't do much without history, and we don't want to
-            // leave with half-baked UI, we bail hard.
-            throw new RuntimeException("problem reading network stats", e);
-        }
-
-        // bind chart to historical stats
-        mChart.bindNetworkStats(mHistory);
-
-        // show entire history known
-        final long[] bounds = getHistoryBounds(mHistory);
-        mChart.setVisibleRange(bounds[0], bounds[1] + DateUtils.WEEK_IN_MILLIS, bounds[1]);
-        updateDetailData();
-
-        final Context context = getActivity();
-        if (NetworkPolicyManager.isUidValidForPolicy(context, mUid)) {
-            mRestrictBackgroundView.setVisibility(View.VISIBLE);
-
-            final int uidPolicy;
-            try {
-                uidPolicy = mPolicyService.getUidPolicy(mUid);
-            } catch (RemoteException e) {
-                // since we can't do much without policy, we bail hard.
-                throw new RuntimeException("problem reading network policy", e);
-            }
-
-            // update policy checkbox
-            final boolean restrictBackground = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
-            mRestrictBackground.setChecked(restrictBackground);
-
-            // kick preference views so they rebind from changes above
-            refreshPreferenceViews();
-
-        } else {
-            mRestrictBackgroundView.setVisibility(View.GONE);
-        }
-    }
-
-    private void updateDetailData() {
-        if (LOGD) Log.d(TAG, "updateDetailData()");
-
-        final Context context = mChart.getContext();
-        final long[] range = mChart.getInspectRange();
-        final long[] total = mHistory.getTotalData(range[0], range[1], null);
-        final long totalCombined = total[0] + total[1];
-        mText1.setText(Formatter.formatFileSize(context, totalCombined));
-    }
-
-    private void setRestrictBackground(boolean restrictBackground) {
-        if (LOGD) Log.d(TAG, "setRestrictBackground()");
-        try {
-            mPolicyService.setUidPolicy(
-                    mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
-        } catch (RemoteException e) {
-            throw new RuntimeException("unable to save policy", e);
-        }
-
-        mRestrictBackground.setChecked(restrictBackground);
-        refreshPreferenceViews();
-    }
-
-    /**
-     * Force rebind of hijacked {@link Preference} views.
-     */
-    private void refreshPreferenceViews() {
-        mRestrictBackgroundView = mRestrictBackground.getView(mRestrictBackgroundView, mSwitches);
-    }
-
-    private DataUsageChartListener mChartListener = new DataUsageChartListener() {
-        /** {@inheritDoc} */
-        public void onInspectRangeChanged() {
-            if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
-            updateDetailData();
-        }
-
-        /** {@inheritDoc} */
-        public void onWarningChanged() {
-            // ignored
-        }
-
-        /** {@inheritDoc} */
-        public void onLimitChanged() {
-            // ignored
-        }
-    };
-
-    private OnClickListener mAppSettingsListener = new OnClickListener() {
-        /** {@inheritDoc} */
-        public void onClick(View v) {
-            // TODO: target torwards entire UID instead of just first package
-            startActivity(mAppSettingsIntent);
-        }
-    };
-
-    private OnClickListener mRestrictBackgroundListener = new OnClickListener() {
-        /** {@inheritDoc} */
-        public void onClick(View v) {
-            final boolean restrictBackground = !mRestrictBackground.isChecked();
-
-            if (restrictBackground) {
-                // enabling restriction; show confirmation dialog which
-                // eventually calls setRestrictBackground() once user confirms.
-                ConfirmRestrictFragment.show(DataUsageAppDetail.this);
-            } else {
-                setRestrictBackground(false);
-            }
-        }
-    };
-
-    /**
-     * Dialog to request user confirmation before setting
-     * {@link #POLICY_REJECT_METERED_BACKGROUND}.
-     */
-    public static class ConfirmRestrictFragment extends DialogFragment {
-        public static void show(DataUsageAppDetail parent) {
-            final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
-            dialog.setTargetFragment(parent, 0);
-            dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            final Context context = getActivity();
-
-            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
-            builder.setTitle(R.string.data_usage_app_restrict_dialog_title);
-            builder.setMessage(R.string.data_usage_app_restrict_dialog);
-
-            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-                public void onClick(DialogInterface dialog, int which) {
-                    final DataUsageAppDetail target = (DataUsageAppDetail) getTargetFragment();
-                    if (target != null) {
-                        target.setRestrictBackground(true);
-                    }
-                }
-            });
-            builder.setNegativeButton(android.R.string.cancel, null);
-
-            return builder.create();
-        }
-    }
-
-}
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 692c753..098f57a 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -21,6 +21,8 @@
 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.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
@@ -29,10 +31,12 @@
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import android.animation.LayoutTransition;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.Fragment;
+import android.app.FragmentTransaction;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -54,7 +58,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.preference.Preference;
-import android.preference.PreferenceActivity;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -66,12 +69,14 @@
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 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;
@@ -106,8 +111,6 @@
     private static final String TAG = "DataUsage";
     private static final boolean LOGD = true;
 
-    private static final int TEMPLATE_INVALID = -1;
-
     private static final String TAB_3G = "3g";
     private static final String TAB_4G = "4g";
     private static final String TAB_MOBILE = "mobile";
@@ -116,6 +119,8 @@
     private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
     private static final String TAG_CYCLE_EDITOR = "cycleEditor";
     private static final String TAG_POLICY_LIMIT = "policyLimit";
+    private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
+    private static final String TAG_APP_DETAILS = "appDetails";
 
     private static final long KB_IN_BYTES = 1024;
     private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
@@ -135,25 +140,41 @@
     private ListView mListView;
     private DataUsageAdapter mAdapter;
 
-    private View mHeader;
-    private LinearLayout mSwitches;
+    private ViewGroup mHeader;
 
+    private LinearLayout mNetworkSwitches;
     private Switch mDataEnabled;
-    private CheckBox mDisableAtLimit;
     private View mDataEnabledView;
+    private CheckBox mDisableAtLimit;
     private View mDisableAtLimitView;
 
-    private DataUsageChartView mChart;
-
     private Spinner mCycleSpinner;
     private CycleAdapter mCycleAdapter;
 
+    private DataUsageChartView mChart;
+
+    private View mAppDetail;
+    private TextView mAppTitle;
+    private TextView mAppSubtitle;
+    private Button mAppSettings;
+
+    private LinearLayout mAppSwitches;
+    private CheckBox mAppRestrict;
+    private View mAppRestrictView;
+
     private boolean mShowWifi = false;
 
     private NetworkTemplate mTemplate = null;
 
+    private static final int UID_NONE = -1;
+    private int mUid = UID_NONE;
+
+    private Intent mAppSettingsIntent;
+
     private NetworkPolicyEditor mPolicyEditor;
+
     private NetworkStatsHistory mHistory;
+    private NetworkStatsHistory mDetailHistory;
 
     private String mIntentTab = null;
 
@@ -194,30 +215,57 @@
         mTabHost.setup();
         mTabHost.setOnTabChangedListener(mTabListener);
 
-        mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
+        mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
         mListView.addHeaderView(mHeader, null, false);
 
-        mDataEnabled = new Switch(inflater.getContext());
-        mDataEnabledView = inflatePreference(inflater, mSwitches, mDataEnabled);
-        mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
+        {
+            // bind network switches
+            mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches);
 
-        mDisableAtLimit = new CheckBox(inflater.getContext());
-        mDisableAtLimit.setClickable(false);
-        mDisableAtLimitView = inflatePreference(inflater, mSwitches, mDisableAtLimit);
-        mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
+            mDataEnabled = new Switch(inflater.getContext());
+            mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled);
+            mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
+            mNetworkSwitches.addView(mDataEnabledView);
 
-        mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
-        mSwitches.addView(mDataEnabledView);
-        mSwitches.addView(mDisableAtLimitView);
+            mDisableAtLimit = new CheckBox(inflater.getContext());
+            mDisableAtLimit.setClickable(false);
+            mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
+            mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
+            mNetworkSwitches.addView(mDisableAtLimitView);
+        }
 
+        // bind cycle dropdown
         mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
         mCycleAdapter = new CycleAdapter(context);
         mCycleSpinner.setAdapter(mCycleAdapter);
         mCycleSpinner.setOnItemSelectedListener(mCycleListener);
 
-        mChart = (DataUsageChartView) inflater.inflate(R.layout.data_usage_chart, mListView, false);
+        mChart = (DataUsageChartView) mHeader.findViewById(R.id.chart);
         mChart.setListener(mChartListener);
-        mListView.addHeaderView(mChart, null, false);
+
+        {
+            // bind app detail controls
+            mAppDetail = view.findViewById(R.id.app_detail);
+            mAppTitle = (TextView) view.findViewById(R.id.app_title);
+            mAppSubtitle = (TextView) view.findViewById(R.id.app_subtitle);
+            mAppSwitches = (LinearLayout) view.findViewById(R.id.app_switches);
+
+            mAppSettings = (Button) view.findViewById(R.id.app_settings);
+            mAppSettings.setOnClickListener(mAppSettingsListener);
+
+            mAppRestrict = new CheckBox(inflater.getContext());
+            mAppRestrict.setClickable(false);
+            mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
+            setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
+            setPreferenceSummary(
+                    mAppRestrictView, R.string.data_usage_app_restrict_background_summary);
+            mAppRestrictView.setOnClickListener(mAppRestrictListener);
+            mAppSwitches.addView(mAppRestrictView);
+        }
+
+        // TODO: tweak these transitions
+        final LayoutTransition transition = new LayoutTransition();
+        mHeader.setLayoutTransition(transition);
 
         mAdapter = new DataUsageAdapter();
         mListView.setOnItemClickListener(mListListener);
@@ -433,6 +481,7 @@
         mChart.bindNetworkStats(mHistory);
 
         updatePolicy(true);
+        updateAppDetail();
 
         // force scroll to top of body
         mListView.smoothScrollToPosition(0);
@@ -440,6 +489,84 @@
         mBinding = false;
     }
 
+    private boolean isAppDetailMode() {
+        return mUid != UID_NONE;
+    }
+
+    /**
+     * Update UID details panels to match {@link #mUid}, showing or hiding them
+     * depending on {@link #isAppDetailMode()}.
+     */
+    private void updateAppDetail() {
+        if (isAppDetailMode()) {
+            mAppDetail.setVisibility(View.VISIBLE);
+        } else {
+            mAppDetail.setVisibility(View.GONE);
+
+            // hide detail stats when not in detail mode
+            mChart.bindDetailNetworkStats(null);
+            return;
+        }
+
+        // remove warning/limit sweeps while in detail mode
+        mChart.bindNetworkPolicy(null);
+
+        final PackageManager pm = getActivity().getPackageManager();
+        mAppTitle.setText(pm.getNameForUid(mUid));
+
+        // enable settings button when package provides it
+        // TODO: target torwards entire UID instead of just first package
+        final String[] packageNames = pm.getPackagesForUid(mUid);
+        if (packageNames != null && packageNames.length > 0) {
+            mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
+            mAppSettingsIntent.setPackage(packageNames[0]);
+            mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
+
+            final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null;
+            mAppSettings.setEnabled(matchFound);
+
+        } else {
+            mAppSettingsIntent = null;
+            mAppSettings.setEnabled(false);
+        }
+
+        try {
+            // load stats for current uid and template
+            // TODO: read template from extras
+            mDetailHistory = mStatsService.getHistoryForUid(mTemplate, mUid, NetworkStats.TAG_NONE);
+        } catch (RemoteException e) {
+            // since we can't do much without history, and we don't want to
+            // leave with half-baked UI, we bail hard.
+            throw new RuntimeException("problem reading network stats", e);
+        }
+
+        // bind chart to historical stats
+        mChart.bindDetailNetworkStats(mDetailHistory);
+
+        updateDetailData();
+
+        final Context context = getActivity();
+        if (NetworkPolicyManager.isUidValidForPolicy(context, mUid)) {
+            mAppRestrictView.setVisibility(View.VISIBLE);
+
+            final int uidPolicy;
+            try {
+                uidPolicy = mPolicyService.getUidPolicy(mUid);
+            } catch (RemoteException e) {
+                // since we can't do much without policy, we bail hard.
+                throw new RuntimeException("problem reading network policy", e);
+            }
+
+            // update policy checkbox
+            final boolean restrictBackground = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
+            mAppRestrict.setChecked(restrictBackground);
+
+        } else {
+            mAppRestrictView.setVisibility(View.GONE);
+        }
+
+    }
+
     private void setPolicyCycleDay(int cycleDay) {
         if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
         mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
@@ -458,11 +585,30 @@
         updatePolicy(false);
     }
 
+    private void setAppRestrictBackground(boolean restrictBackground) {
+        if (LOGD) Log.d(TAG, "setRestrictBackground()");
+        try {
+            mPolicyService.setUidPolicy(
+                    mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
+        } catch (RemoteException e) {
+            throw new RuntimeException("unable to save policy", e);
+        }
+
+        mAppRestrict.setChecked(restrictBackground);
+    }
+
     /**
      * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
      * current {@link #mTemplate}.
      */
     private void updatePolicy(boolean refreshCycle) {
+        if (isAppDetailMode()) {
+            mNetworkSwitches.setVisibility(View.GONE);
+            // we fall through to update cycle list for detail mode
+        } else {
+            mNetworkSwitches.setVisibility(View.VISIBLE);
+        }
+
         final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
 
         // reflect policy limit in checkbox
@@ -572,18 +718,34 @@
         }
     };
 
+    private View.OnClickListener mAppRestrictListener = new View.OnClickListener() {
+        /** {@inheritDoc} */
+        public void onClick(View v) {
+            final boolean restrictBackground = !mAppRestrict.isChecked();
+
+            if (restrictBackground) {
+                // enabling restriction; show confirmation dialog which
+                // eventually calls setRestrictBackground() once user confirms.
+                ConfirmRestrictFragment.show(DataUsageSummary.this);
+            } else {
+                setAppRestrictBackground(false);
+            }
+        }
+    };
+
+    private OnClickListener mAppSettingsListener = new OnClickListener() {
+        /** {@inheritDoc} */
+        public void onClick(View v) {
+            // TODO: target torwards entire UID instead of just first package
+            startActivity(mAppSettingsIntent);
+        }
+    };
+
     private OnItemClickListener mListListener = new OnItemClickListener() {
         /** {@inheritDoc} */
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
-
-            final Bundle args = new Bundle();
-            args.putParcelable(DataUsageAppDetail.EXTRA_NETWORK_TEMPLATE, mTemplate);
-            args.putInt(DataUsageAppDetail.EXTRA_UID, app.uid);
-
-            final PreferenceActivity activity = (PreferenceActivity) getActivity();
-            activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
-                    R.string.data_usage_summary_title, null, null, 0);
+            AppDetailsFragment.show(DataUsageSummary.this, app.uid);
         }
     };
 
@@ -621,12 +783,30 @@
     };
 
     /**
-     * Update {@link #mAdapter} with sorted list of applications data usage,
-     * based on current inspection from {@link #mChart}.
+     * Update details based on {@link #mChart} inspection range depending on
+     * current mode. In network mode, updates {@link #mAdapter} with sorted list
+     * of applications data usage, and when {@link #isAppDetailMode()} update
+     * app details.
      */
     private void updateDetailData() {
         if (LOGD) Log.d(TAG, "updateDetailData()");
 
+        if (isAppDetailMode()) {
+            if (mDetailHistory != null) {
+                final Context context = mChart.getContext();
+                final long[] range = mChart.getInspectRange();
+                final long[] total = mDetailHistory.getTotalData(range[0], range[1], null);
+                final long totalCombined = total[0] + total[1];
+                mAppSubtitle.setText(Formatter.formatFileSize(context, totalCombined));
+            }
+
+            // clear any existing app list details
+            mAdapter.bindStats(null);
+
+            return;
+        }
+
+        // otherwise kick off task to update list
         new AsyncTask<Void, Void, NetworkStats>() {
             @Override
             protected NetworkStats doInBackground(Void... params) {
@@ -753,15 +933,20 @@
     public static class DataUsageAdapter extends BaseAdapter {
         private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
 
+        /**
+         * Bind the given {@link NetworkStats}, or {@code null} to clear list.
+         */
         public void bindStats(NetworkStats stats) {
             mItems.clear();
 
-            for (int i = 0; i < stats.size; i++) {
-                final long total = stats.rx[i] + stats.tx[i];
-                final AppUsageItem item = new AppUsageItem();
-                item.uid = stats.uid[i];
-                item.total = total;
-                mItems.add(item);
+            if (stats != null) {
+                for (int i = 0; i < stats.size; i++) {
+                    final long total = stats.rx[i] + stats.tx[i];
+                    final AppUsageItem item = new AppUsageItem();
+                    item.uid = stats.uid[i];
+                    item.total = total;
+                    mItems.add(item);
+                }
             }
 
             Collections.sort(mItems);
@@ -806,6 +991,44 @@
     }
 
     /**
+     * Empty {@link Fragment} that controls display of UID details in
+     * {@link DataUsageSummary}.
+     */
+    public static class AppDetailsFragment extends Fragment {
+        public static final String EXTRA_UID = "uid";
+
+        public static void show(DataUsageSummary parent, int uid) {
+            final Bundle args = new Bundle();
+            args.putInt(EXTRA_UID, uid);
+
+            final AppDetailsFragment fragment = new AppDetailsFragment();
+            fragment.setArguments(args);
+            fragment.setTargetFragment(parent, 0);
+
+            final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
+            ft.add(fragment, TAG_APP_DETAILS);
+            ft.addToBackStack(TAG_APP_DETAILS);
+            ft.commit();
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+            target.mUid = getArguments().getInt(EXTRA_UID);
+            target.updateBody();
+        }
+
+        @Override
+        public void onStop() {
+            super.onStop();
+            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+            target.mUid = UID_NONE;
+            target.updateBody();
+        }
+    }
+
+    /**
      * Dialog to request user confirmation before setting
      * {@link NetworkPolicy#limitBytes}.
      */
@@ -976,11 +1199,44 @@
     }
 
     /**
+     * Dialog to request user confirmation before setting
+     * {@link #POLICY_REJECT_METERED_BACKGROUND}.
+     */
+    public static class ConfirmRestrictFragment extends DialogFragment {
+        public static void show(DataUsageSummary parent) {
+            final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
+            dialog.setTargetFragment(parent, 0);
+            dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Context context = getActivity();
+
+            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+            builder.setTitle(R.string.data_usage_app_restrict_dialog_title);
+            builder.setMessage(R.string.data_usage_app_restrict_dialog);
+
+            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {
+                    final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+                    if (target != null) {
+                        target.setAppRestrictBackground(true);
+                    }
+                }
+            });
+            builder.setNegativeButton(android.R.string.cancel, null);
+
+            return builder.create();
+        }
+    }
+
+    /**
      * Compute default tab that should be selected, based on
      * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
      */
     private static String computeTabFromIntent(Intent intent) {
-        final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, TEMPLATE_INVALID);
+        final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, MATCH_MOBILE_ALL);
         switch (networkTemplate) {
             case MATCH_MOBILE_3G_LOWER:
                 return TAB_3G;
@@ -1083,4 +1339,14 @@
         title.setText(resId);
     }
 
+    /**
+     * Set {@link android.R.id#summary} for a preference view inflated with
+     * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
+     */
+    private static void setPreferenceSummary(View parent, int resId) {
+        final TextView summary = (TextView) parent.findViewById(android.R.id.summary);
+        summary.setVisibility(View.VISIBLE);
+        summary.setText(resId);
+    }
+
 }
diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java
index c50a490..61c2550 100644
--- a/src/com/android/settings/net/NetworkPolicyEditor.java
+++ b/src/com/android/settings/net/NetworkPolicyEditor.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.net;
 
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
 import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
 import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
@@ -51,6 +53,14 @@
             final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies();
             mPolicies.clear();
             for (NetworkPolicy policy : policies) {
+                // TODO: find better place to clamp these
+                if (policy.limitBytes < -1) {
+                    policy.limitBytes = LIMIT_DISABLED;
+                }
+                if (policy.warningBytes < -1) {
+                    policy.warningBytes = WARNING_DISABLED;
+                }
+
                 mPolicies.add(policy);
             }
         } catch (RemoteException e) {
diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java
index 0a34565..83c10cd 100644
--- a/src/com/android/settings/widget/ChartNetworkSeriesView.java
+++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java
@@ -75,6 +75,7 @@
                 R.styleable.ChartNetworkSeriesView_fillColorSecondary, Color.RED);
 
         setChartColor(stroke, fill, fillSecondary);
+        setWillNotDraw(false);
 
         a.recycle();
 
@@ -110,8 +111,13 @@
 
         mPathStroke.reset();
         mPathFill.reset();
+        invalidate();
     }
 
+    /**
+     * 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;
@@ -190,18 +196,21 @@
     protected void onDraw(Canvas canvas) {
         int save;
 
+        final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
+        final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
+
         save = canvas.save();
-        canvas.clipRect(0, 0, mPrimaryLeft, getHeight());
+        canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
         canvas.drawPath(mPathFill, mPaintFillSecondary);
         canvas.restoreToCount(save);
 
         save = canvas.save();
-        canvas.clipRect(mPrimaryRight, 0, getWidth(), getHeight());
+        canvas.clipRect(primaryRightPoint, 0, getWidth(), getHeight());
         canvas.drawPath(mPathFill, mPaintFillSecondary);
         canvas.restoreToCount(save);
 
         save = canvas.save();
-        canvas.clipRect(mPrimaryLeft, 0, mPrimaryRight, getHeight());
+        canvas.clipRect(primaryLeftPoint, 0, primaryRightPoint, getHeight());
         canvas.drawPath(mPathFill, mPaintFill);
         canvas.drawPath(mPathStroke, mPaintStroke);
         canvas.restoreToCount(save);
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 881fde4..d8344d5 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -39,6 +39,8 @@
     // TODO: paint label when requested
 
     private Drawable mSweep;
+    private Rect mSweepMargins = new Rect();
+
     private int mFollowAxis;
     private boolean mShowLabel;
 
@@ -88,8 +90,28 @@
         return mFollowAxis;
     }
 
-    public void getExtraMargins(Rect rect) {
-        mSweep.getPadding(rect);
+    /**
+     * Return margins of {@link #setSweepDrawable(Drawable)}, indicating how the
+     * sweep should be displayed around a content region.
+     */
+    public Rect getSweepMargins() {
+        return mSweepMargins;
+    }
+
+    /**
+     * Return the number of pixels that the "target" area is inset from the
+     * {@link View} edge, along the current {@link #setFollowAxis(int)}.
+     */
+    public float getTargetInset() {
+        if (mFollowAxis == VERTICAL) {
+            final float targetHeight = mSweep.getIntrinsicHeight() - mSweepMargins.top
+                    - mSweepMargins.bottom;
+            return mSweepMargins.top + (targetHeight / 2);
+        } else {
+            final float targetWidth = mSweep.getIntrinsicWidth() - mSweepMargins.left
+                    - mSweepMargins.right;
+            return mSweepMargins.left + (targetWidth / 2);
+        }
     }
 
     public void addOnSweepListener(OnSweepListener listener) {
@@ -115,6 +137,7 @@
             }
             sweep.setVisible(getVisibility() == VISIBLE, false);
             mSweep = sweep;
+            sweep.getPadding(mSweepMargins);
         } else {
             mSweep = null;
         }
@@ -175,33 +198,51 @@
         final View parent = (View) getParent();
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN: {
-                mTracking = event.copy();
-                return true;
+
+                // only start tracking when in sweet spot
+                final boolean accept;
+                if (mFollowAxis == VERTICAL) {
+                    accept = event.getX() > getWidth() - (mSweepMargins.right * 2);
+                } else {
+                    accept = event.getY() > getHeight() - (mSweepMargins.bottom * 2);
+                }
+
+                if (accept) {
+                    mTracking = event.copy();
+                    return true;
+                } else {
+                    return false;
+                }
             }
             case MotionEvent.ACTION_MOVE: {
                 getParent().requestDisallowInterceptTouchEvent(true);
 
+                final Rect sweepMargins = mSweepMargins;
+
+                // content area of parent
+                final Rect parentContent = new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
+                        parent.getWidth() - parent.getPaddingRight(),
+                        parent.getHeight() - parent.getPaddingBottom());
+
                 if (mFollowAxis == VERTICAL) {
-                    final float chartHeight = parent.getHeight() - parent.getPaddingTop()
-                            - parent.getPaddingBottom();
-                    final float translationY = MathUtils.constrain(
-                            event.getRawY() - mTracking.getRawY(), -getTop(),
-                            chartHeight - getTop());
-                    setTranslationY(translationY);
-                    final float point = (getTop() + getTranslationY() + (getHeight() / 2))
-                            - parent.getPaddingTop();
-                    mValue = mAxis.convertToValue(point);
+                    final float currentTargetY = getTop() + getTargetInset();
+                    final float requestedTargetY = currentTargetY
+                            + (event.getRawY() - mTracking.getRawY());
+                    final float clampedTargetY = MathUtils.constrain(
+                            requestedTargetY, parentContent.top, parentContent.bottom);
+                    setTranslationY(clampedTargetY - currentTargetY);
+
+                    mValue = mAxis.convertToValue(clampedTargetY - parentContent.top);
                     dispatchOnSweep(false);
                 } else {
-                    final float chartWidth = parent.getWidth() - parent.getPaddingLeft()
-                            - parent.getPaddingRight();
-                    final float translationX = MathUtils.constrain(
-                            event.getRawX() - mTracking.getRawX(), -getLeft(),
-                            chartWidth - getLeft());
-                    setTranslationX(translationX);
-                    final float point = (getLeft() + getTranslationX() + (getWidth() / 2))
-                            - parent.getPaddingLeft();
-                    mValue = mAxis.convertToValue(point);
+                    final float currentTargetX = getLeft() + getTargetInset();
+                    final float requestedTargetX = currentTargetX
+                            + (event.getRawX() - mTracking.getRawX());
+                    final float clampedTargetX = MathUtils.constrain(
+                            requestedTargetX, parentContent.left, parentContent.right);
+                    setTranslationX(clampedTargetX - currentTargetX);
+
+                    mValue = mAxis.convertToValue(clampedTargetX - parentContent.left);
                     dispatchOnSweep(false);
                 }
                 return true;
diff --git a/src/com/android/settings/widget/ChartView.java b/src/com/android/settings/widget/ChartView.java
index d762631..bf5616d 100644
--- a/src/com/android/settings/widget/ChartView.java
+++ b/src/com/android/settings/widget/ChartView.java
@@ -36,6 +36,8 @@
 
     // TODO: extend something that supports two-dimensional scrolling
 
+    private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT;
+
     ChartAxis mHoriz;
     ChartAxis mVert;
 
@@ -74,7 +76,6 @@
 
         final Rect parentRect = new Rect();
         final Rect childRect = new Rect();
-        final Rect extraMargins = new Rect();
 
         for (int i = 0; i < getChildCount(); i++) {
             final View child = getChildAt(i);
@@ -91,21 +92,23 @@
             } else if (child instanceof ChartSweepView) {
                 // sweep is always placed along specific dimension
                 final ChartSweepView sweep = (ChartSweepView) child;
+                final Rect sweepMargins = sweep.getSweepMargins();
                 final float point = sweep.getPoint();
-                sweep.getExtraMargins(extraMargins);
 
                 if (sweep.getFollowAxis() == ChartSweepView.HORIZONTAL) {
-                    parentRect.left = parentRect.right = (int) point + getPaddingLeft();
-                    parentRect.top -= extraMargins.top;
-                    parentRect.bottom += extraMargins.bottom;
-                    Gravity.apply(params.gravity, child.getMeasuredWidth(), parentRect.height(),
+                    parentRect.left = parentRect.right =
+                            (int) (sweep.getPoint() - sweep.getTargetInset()) + getPaddingLeft();
+                    parentRect.top -= sweepMargins.top;
+                    parentRect.bottom += sweepMargins.bottom;
+                    Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(),
                             parentRect, childRect);
 
                 } else {
-                    parentRect.top = parentRect.bottom = (int) point + getPaddingTop();
-                    parentRect.left -= extraMargins.left;
-                    parentRect.right += extraMargins.right;
-                    Gravity.apply(params.gravity, parentRect.width(), child.getMeasuredHeight(),
+                    parentRect.top = parentRect.bottom =
+                            (int) (sweep.getPoint() - sweep.getTargetInset()) + getPaddingTop();
+                    parentRect.left -= sweepMargins.left;
+                    parentRect.right += sweepMargins.right;
+                    Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(),
                             parentRect, childRect);
                 }
             }
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java
index 1c76291..6fe4042 100644
--- a/src/com/android/settings/widget/DataUsageChartView.java
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -38,10 +38,10 @@
     private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
 
     // TODO: enforce that sweeps cant cross each other
-    // TODO: limit sweeps at graph boundaries
 
     private ChartGridView mGrid;
     private ChartNetworkSeriesView mSeries;
+    private ChartNetworkSeriesView mDetailSeries;
 
     private ChartSweepView mSweepLeft;
     private ChartSweepView mSweepRight;
@@ -75,6 +75,8 @@
 
         mGrid = (ChartGridView) findViewById(R.id.grid);
         mSeries = (ChartNetworkSeriesView) findViewById(R.id.series);
+        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);
@@ -89,6 +91,7 @@
         // 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);
@@ -97,27 +100,21 @@
         setActivated(false);
     }
 
-    @Override
-    public void setActivated(boolean activated) {
-        super.setActivated(activated);
-
-        mSweepLeft.setEnabled(activated);
-        mSweepRight.setEnabled(activated);
-        mSweepWarning.setEnabled(activated);
-        mSweepLimit.setEnabled(activated);
-    }
-
-    @Deprecated
-    public void setChartColor(int stroke, int fill, int disabled) {
-        mSeries.setChartColor(stroke, fill, disabled);
-    }
-
     public void setListener(DataUsageChartListener listener) {
         mListener = listener;
     }
 
     public void bindNetworkStats(NetworkStatsHistory stats) {
         mSeries.bindNetworkStats(stats);
+        updatePrimaryRange();
+        requestLayout();
+    }
+
+    public void bindDetailNetworkStats(NetworkStatsHistory stats) {
+        mDetailSeries.bindNetworkStats(stats);
+        mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE);
+        updatePrimaryRange();
+        requestLayout();
     }
 
     public void bindNetworkPolicy(NetworkPolicy policy) {
@@ -146,22 +143,11 @@
         }
 
         requestLayout();
-
-        // TODO: eventually remove this; was to work around lack of sweep clamping
-        if (policy.limitBytes < -1 || policy.limitBytes > 5 * GB_IN_BYTES) {
-            policy.limitBytes = 5 * GB_IN_BYTES;
-            mLimitListener.onSweep(mSweepLimit, true);
-        }
-        if (policy.warningBytes < -1 || policy.warningBytes > 5 * GB_IN_BYTES) {
-            policy.warningBytes = 4 * GB_IN_BYTES;
-            mWarningListener.onSweep(mSweepWarning, true);
-        }
-
     }
 
     private OnSweepListener mSweepListener = new OnSweepListener() {
         public void onSweep(ChartSweepView sweep, boolean sweepDone) {
-            mSeries.setPrimaryRange(mSweepLeft.getValue(), mSweepRight.getValue());
+            updatePrimaryRange();
 
             // update detail list only when done sweeping
             if (sweepDone && mListener != null) {
@@ -236,13 +222,26 @@
 
         mSweepLeft.setValue(sweepMin);
         mSweepRight.setValue(sweepMax);
-        mSeries.setPrimaryRange(sweepMin, sweepMax);
+        updatePrimaryRange();
 
         requestLayout();
         mSeries.generatePath();
         mSeries.invalidate();
     }
 
+    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);
+        } else {
+            mSeries.setPrimaryRange(left, right);
+        }
+    }
+
     public static class TimeAxis implements ChartAxis {
         private static final long TICK_INTERVAL = DateUtils.DAY_IN_MILLIS * 7;