Merge "Polish of the accessibility settings."
diff --git a/res/drawable/data_usage_sweep_background.xml b/res/drawable/data_usage_sweep_background.xml
new file mode 100644
index 0000000..86f1d09
--- /dev/null
+++ b/res/drawable/data_usage_sweep_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+
+    <item android:state_window_focused="false" android:drawable="@android:color/transparent" />
+
+    <item android:state_focused="true" android:state_enabled="false" android:drawable="@*android:drawable/list_selector_background_disabled" />
+    <item android:state_focused="true" android:drawable="@*android:drawable/list_selector_background_focused" />
+    <item android:drawable="@android:color/transparent" />
+
+</selector>
diff --git a/res/layout/data_usage_bytes_editor.xml b/res/layout/data_usage_bytes_editor.xml
new file mode 100644
index 0000000..6207391b
--- /dev/null
+++ b/res/layout/data_usage_bytes_editor.xml
@@ -0,0 +1,40 @@
+<?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:orientation="horizontal"
+    android:gravity="center"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <NumberPicker
+        android:id="@+id/bytes"
+        android:layout_width="48dip"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginLeft="16dip"
+        android:layout_marginRight="16dip"
+        android:focusable="true"
+        android:focusableInTouchMode="true" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@*android:string/megabyteShort" />
+
+</LinearLayout>
diff --git a/res/layout/data_usage_chart.xml b/res/layout/data_usage_chart.xml
index a1a7ec4..38f1c11 100644
--- a/res/layout/data_usage_chart.xml
+++ b/res/layout/data_usage_chart.xml
@@ -59,6 +59,30 @@
         settings:fillColorSecondary="#60ba7f3e" />
 
     <com.android.settings.widget.ChartSweepView
+        android:id="@+id/sweep_warning"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:nextFocusUp="@+id/sweep_limit"
+        settings:sweepDrawable="@drawable/data_sweep_warning"
+        settings:followAxis="vertical"
+        settings:neighborMargin="5dip"
+        settings:labelSize="60dip"
+        settings:labelTemplate="@string/data_usage_sweep_warning"
+        settings:labelColor="#f7931d" />
+
+    <com.android.settings.widget.ChartSweepView
+        android:id="@+id/sweep_limit"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:nextFocusDown="@+id/sweep_warning"
+        settings:sweepDrawable="@drawable/data_sweep_limit"
+        settings:followAxis="vertical"
+        settings:neighborMargin="5dip"
+        settings:labelSize="60dip"
+        settings:labelTemplate="@string/data_usage_sweep_limit"
+        settings:labelColor="#c01a2c" />
+
+    <com.android.settings.widget.ChartSweepView
         android:id="@+id/sweep_left"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
@@ -74,26 +98,4 @@
         settings:followAxis="horizontal"
         settings:neighborMargin="5dip" />
 
-    <com.android.settings.widget.ChartSweepView
-        android:id="@+id/sweep_warning"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        settings:sweepDrawable="@drawable/data_sweep_warning"
-        settings:followAxis="vertical"
-        settings:neighborMargin="5dip"
-        settings:labelSize="60dip"
-        settings:labelTemplate="@string/data_usage_sweep_warning"
-        settings:labelColor="#f7931d" />
-
-    <com.android.settings.widget.ChartSweepView
-        android:id="@+id/sweep_limit"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        settings:sweepDrawable="@drawable/data_sweep_limit"
-        settings:followAxis="vertical"
-        settings:neighborMargin="5dip"
-        settings:labelSize="60dip"
-        settings:labelTemplate="@string/data_usage_sweep_limit"
-        settings:labelColor="#c01a2c" />
-
 </com.android.settings.widget.ChartDataUsageView>
diff --git a/res/layout/ownerinfo.xml b/res/layout/ownerinfo.xml
index 14cdbcb..1001a21 100644
--- a/res/layout/ownerinfo.xml
+++ b/res/layout/ownerinfo.xml
@@ -61,6 +61,7 @@
                 android:hint="@string/owner_info_settings_edit_text_hint"
                 android:lines="8"
                 android:maxLines="8"
+                android:inputType="textNoSuggestions|textMultiLine"
             />
 
         </LinearLayout>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 1c6bc24..b722b78 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -146,21 +146,15 @@
     </string-array>
     
     <string-array name="entries_font_size">
-        <item>Extremely Small</item>
-        <item>Extra Small</item>
-        <item>Small</item>
-        <item>Normal</item>
-        <item>Large</item>
-        <item>Extra Large</item>
-        <item>Extremely Large</item>
+        <item msgid="6490061470416867723">Small</item>
+        <item msgid="3579015730662088893">Normal</item>
+        <item msgid="1678068858001018666">Large</item>
+        <item msgid="490158884605093126">Extra Large</item>
     </string-array>
 
     <string-array name="entryvalues_font_size" translatable="false">
-        <item>0.70</item>
         <item>0.85</item>
-        <item>0.95</item>
         <item>1.0</item>
-        <item>1.05</item>
         <item>1.15</item>
         <item>1.30</item>
     </string-array>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e54c260..b2d21c6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2804,7 +2804,7 @@
     <!-- Message to show when battery usage data is not available [CHAR LIMIT=30] -->
     <string name="power_usage_not_available">Battery usage data not available</string>
     <!-- Display the battery level and status [CHAR_LIMIT=30] -->
-    <string name="power_usage_level_and_status">Battery level <xliff:g id="level">%1$s</xliff:g> - <xliff:g id="status">%2$s</xliff:g></string>
+    <string name="power_usage_level_and_status"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="status">%2$s</xliff:g></string>
     <!-- Battery usage since unplugged -->
     <string name="battery_since_unplugged">Battery use since unplugged</string>
     <!-- Battery usage since user reset the stats -->
@@ -3538,6 +3538,8 @@
     <string name="data_usage_app_restrict_dialog_title">Restrict background data?</string>
     <!-- Body of dialog shown when user restricts background data usage of a specific application. [CHAR LIMIT=NONE] -->
     <string name="data_usage_app_restrict_dialog">This feature may negatively impact applications which depend on background data usage.\n\nMore appropriate data usage controls may be found within this application\'s settings.</string>
+    <!-- Body of dialog shown when user attempts to restrict background data before a network data limit has been set. [CHAR LIMIT=NONE] -->
+    <string name="data_usage_restrict_denied_dialog">Restricting background data is only available when you\'ve set a network data limit.</string>
 
     <!-- Title of dialog for editing data usage cycle reset date. [CHAR LIMIT=48] -->
     <string name="data_usage_cycle_editor_title">Usage cycle reset date</string>
@@ -3546,6 +3548,11 @@
     <!-- Positive button title for data usage cycle editor, confirming that changes should be saved. [CHAR LIMIT=32] -->
     <string name="data_usage_cycle_editor_positive">Set</string>
 
+    <!-- Title of dialog for editing data usage warning in bytes. [CHAR LIMIT=48] -->
+    <string name="data_usage_warning_editor_title">Set data usage warning</string>
+    <!-- Title of dialog for editing data usage limit in bytes. [CHAR LIMIT=48] -->
+    <string name="data_usage_limit_editor_title">Set data usage limit</string>
+
     <!-- Title of dialog shown before user limits data usage. [CHAR LIMIT=48] -->
     <string name="data_usage_limit_dialog_title">Limiting data usage</string>
     <!-- Body of dialog shown before user limits mobile data usage. [CHAR LIMIT=NONE] -->
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 0e08075..bbc3606 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -20,6 +20,7 @@
 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.NetworkPolicy.WARNING_DISABLED;
 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
@@ -129,6 +130,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
 
 import libcore.util.Objects;
@@ -156,7 +158,10 @@
     private static final String TAG_CONFIRM_DATA_ROAMING = "confirmDataRoaming";
     private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
     private static final String TAG_CYCLE_EDITOR = "cycleEditor";
+    private static final String TAG_WARNING_EDITOR = "warningEditor";
+    private static final String TAG_LIMIT_EDITOR = "limitEditor";
     private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
+    private static final String TAG_DENIED_RESTRICT = "deniedRestrict";
     private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
     private static final String TAG_APP_DETAILS = "appDetails";
 
@@ -295,7 +300,10 @@
         mTabHost.setOnTabChangedListener(mTabListener);
 
         mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
-        mListView.addHeaderView(mHeader, null, false);
+        mHeader.setClickable(true);
+
+        mListView.addHeaderView(mHeader, null, true);
+        mListView.setItemsCanFocus(true);
 
         if (mInsetSide > 0) {
             // inset selector and divider drawables
@@ -316,7 +324,10 @@
 
             mDisableAtLimit = new CheckBox(inflater.getContext());
             mDisableAtLimit.setClickable(false);
+            mDisableAtLimit.setFocusable(false);
             mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
+            mDisableAtLimitView.setClickable(true);
+            mDisableAtLimitView.setFocusable(true);
             mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
             mNetworkSwitches.addView(mDisableAtLimitView);
         }
@@ -346,7 +357,10 @@
 
             mAppRestrict = new CheckBox(inflater.getContext());
             mAppRestrict.setClickable(false);
+            mAppRestrict.setFocusable(false);
             mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
+            mAppRestrictView.setClickable(true);
+            mAppRestrictView.setFocusable(true);
             mAppRestrictView.setOnClickListener(mAppRestrictListener);
             mAppSwitches.addView(mAppRestrictView);
         }
@@ -456,7 +470,11 @@
             case R.id.data_usage_menu_restrict_background: {
                 final boolean restrictBackground = !item.isChecked();
                 if (restrictBackground) {
-                    ConfirmRestrictFragment.show(this);
+                    if (hasLimitedNetworks()) {
+                        ConfirmRestrictFragment.show(this);
+                    } else {
+                        DeniedRestrictFragment.show(this);
+                    }
                 } else {
                     // no confirmation to drop restriction
                     setRestrictBackground(false);
@@ -619,7 +637,6 @@
         if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
 
         mDataEnabledView.setVisibility(View.VISIBLE);
-        mDisableAtLimitView.setVisibility(View.VISIBLE);
 
         if (TAB_MOBILE.equals(currentTab)) {
             setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
@@ -735,7 +752,7 @@
             setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
             setPreferenceSummary(mAppRestrictView,
                     getString(R.string.data_usage_app_restrict_background_summary,
-                            buildLimitedNetworksList()));
+                            buildLimitedNetworksString()));
 
             mAppRestrictView.setVisibility(View.VISIBLE);
             mAppRestrict.setChecked(getAppRestrictBackground());
@@ -745,12 +762,6 @@
         }
     }
 
-    private void setPolicyCycleDay(int cycleDay) {
-        if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
-        mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
-        updatePolicy(true);
-    }
-
     private void setPolicyWarningBytes(long warningBytes) {
         if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
         mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
@@ -863,15 +874,8 @@
     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);
-
-            // when heading back to summary without cycle refresh, kick details
-            // update to repopulate list.
-            if (!refreshCycle) {
-                updateDetailData();
-            }
         }
 
         // TODO: move enabled state directly into policy
@@ -907,6 +911,8 @@
      * item, updating the inspection range on {@link #mChart}.
      */
     private void updateCycleList(NetworkPolicy policy) {
+        // stash away currently selected cycle to try restoring below
+        final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem();
         mCycleAdapter.clear();
 
         final Context context = mCycleSpinner.getContext();
@@ -954,8 +960,18 @@
 
         // force pick the current cycle (first item)
         if (mCycleAdapter.getCount() > 0) {
-            mCycleSpinner.setSelection(0);
-            mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
+            final int position = mCycleAdapter.findNearestPosition(previousItem);
+            mCycleSpinner.setSelection(position);
+
+            // only force-update cycle when changed; skipping preserves any
+            // user-defined inspection region.
+            final CycleItem selectedItem = mCycleAdapter.getItem(position);
+            if (!Objects.equal(selectedItem, previousItem)) {
+                mCycleListener.onItemSelected(mCycleSpinner, null, position, 0);
+            } else {
+                // but still kick off loader for detailed list
+                updateDetailData();
+            }
         } else {
             updateDetailData();
         }
@@ -1002,9 +1018,16 @@
             final boolean restrictBackground = !mAppRestrict.isChecked();
 
             if (restrictBackground) {
-                // enabling restriction; show confirmation dialog which
-                // eventually calls setRestrictBackground() once user confirms.
-                ConfirmAppRestrictFragment.show(DataUsageSummary.this);
+                if (hasLimitedNetworks()) {
+                    // enabling restriction; show confirmation dialog which
+                    // eventually calls setRestrictBackground() once user
+                    // confirms.
+                    ConfirmAppRestrictFragment.show(DataUsageSummary.this);
+                } else {
+                    // no limited networks; show dialog to guide user towards
+                    // setting a network limit. doesn't mutate restrict state.
+                    DeniedRestrictFragment.show(DataUsageSummary.this);
+                }
             } else {
                 setAppRestrictBackground(false);
             }
@@ -1211,13 +1234,22 @@
         public void onLimitChanged() {
             setPolicyLimitBytes(mChart.getLimitBytes());
         }
-    };
 
+        /** {@inheritDoc} */
+        public void requestWarningEdit() {
+            WarningEditorFragment.show(DataUsageSummary.this);
+        }
+
+        /** {@inheritDoc} */
+        public void requestLimitEdit() {
+            LimitEditorFragment.show(DataUsageSummary.this);
+        }
+    };
 
     /**
      * List item that reflects a specific data usage cycle.
      */
-    public static class CycleItem {
+    public static class CycleItem implements Comparable<CycleItem> {
         public CharSequence label;
         public long start;
         public long end;
@@ -1236,6 +1268,20 @@
         public String toString() {
             return label.toString();
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof CycleItem) {
+                final CycleItem another = (CycleItem) o;
+                return start == another.start && end == another.end;
+            }
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        public int compareTo(CycleItem another) {
+            return Long.compare(start, another.start);
+        }
     }
 
     private static final StringBuilder sBuilder = new StringBuilder(50);
@@ -1291,6 +1337,25 @@
                 add(mChangeItem);
             }
         }
+
+        /**
+         * Find position of {@link CycleItem} in this adapter which is nearest
+         * the given {@link CycleItem}.
+         */
+        public int findNearestPosition(CycleItem target) {
+            if (target != null) {
+                final int count = getCount();
+                for (int i = count - 1; i >= 0; i--) {
+                    final CycleItem item = getItem(i);
+                    if (item instanceof CycleChangeItem) {
+                        continue;
+                    } else if (item.compareTo(target) >= 0) {
+                        return i;
+                    }
+                }
+            }
+            return 0;
+        }
     }
 
     private static class AppUsageItem implements Comparable<AppUsageItem> {
@@ -1528,12 +1593,11 @@
      * Dialog to edit {@link NetworkPolicy#cycleDay}.
      */
     public static class CycleEditorFragment extends DialogFragment {
-        private static final String EXTRA_CYCLE_DAY = "cycleDay";
+        private static final String EXTRA_TEMPLATE = "template";
 
         public static void show(DataUsageSummary parent) {
-            final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
             final Bundle args = new Bundle();
-            args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
+            args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
 
             final CycleEditorFragment dialog = new CycleEditorFragment();
             dialog.setArguments(args);
@@ -1544,6 +1608,8 @@
         @Override
         public Dialog onCreateDialog(Bundle savedInstanceState) {
             final Context context = getActivity();
+            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+            final NetworkPolicyEditor editor = target.mPolicyEditor;
 
             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
@@ -1551,11 +1617,12 @@
             final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
             final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
 
-            final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
+            final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+            final int cycleDay = editor.getPolicyCycleDay(template);
 
             cycleDayPicker.setMinValue(1);
             cycleDayPicker.setMaxValue(31);
-            cycleDayPicker.setValue(oldCycleDay);
+            cycleDayPicker.setValue(cycleDay);
             cycleDayPicker.setWrapSelectorWheel(true);
 
             builder.setTitle(R.string.data_usage_cycle_editor_title);
@@ -1565,10 +1632,8 @@
                     new DialogInterface.OnClickListener() {
                         public void onClick(DialogInterface dialog, int which) {
                             final int cycleDay = cycleDayPicker.getValue();
-                            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
-                            if (target != null) {
-                                target.setPolicyCycleDay(cycleDay);
-                            }
+                            editor.setPolicyCycleDay(template, cycleDay);
+                            target.updatePolicy(true);
                         }
                     });
 
@@ -1577,6 +1642,125 @@
     }
 
     /**
+     * Dialog to edit {@link NetworkPolicy#warningBytes}.
+     */
+    public static class WarningEditorFragment extends DialogFragment {
+        private static final String EXTRA_TEMPLATE = "template";
+
+        public static void show(DataUsageSummary parent) {
+            final Bundle args = new Bundle();
+            args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
+
+            final WarningEditorFragment dialog = new WarningEditorFragment();
+            dialog.setArguments(args);
+            dialog.setTargetFragment(parent, 0);
+            dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Context context = getActivity();
+            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+            final NetworkPolicyEditor editor = target.mPolicyEditor;
+
+            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+            final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+            final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
+            final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
+
+            final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+            final long warningBytes = editor.getPolicyWarningBytes(template);
+            final long limitBytes = editor.getPolicyLimitBytes(template);
+
+            bytesPicker.setMinValue(0);
+            if (limitBytes != LIMIT_DISABLED) {
+                bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1);
+            } else {
+                bytesPicker.setMaxValue(Integer.MAX_VALUE);
+            }
+            bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES));
+            bytesPicker.setWrapSelectorWheel(false);
+
+            builder.setTitle(R.string.data_usage_warning_editor_title);
+            builder.setView(view);
+
+            builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            // clear focus to finish pending text edits
+                            bytesPicker.clearFocus();
+
+                            final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
+                            editor.setPolicyWarningBytes(template, bytes);
+                            target.updatePolicy(false);
+                        }
+                    });
+
+            return builder.create();
+        }
+    }
+
+    /**
+     * Dialog to edit {@link NetworkPolicy#limitBytes}.
+     */
+    public static class LimitEditorFragment extends DialogFragment {
+        private static final String EXTRA_TEMPLATE = "template";
+
+        public static void show(DataUsageSummary parent) {
+            final Bundle args = new Bundle();
+            args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
+
+            final LimitEditorFragment dialog = new LimitEditorFragment();
+            dialog.setArguments(args);
+            dialog.setTargetFragment(parent, 0);
+            dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Context context = getActivity();
+            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+            final NetworkPolicyEditor editor = target.mPolicyEditor;
+
+            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+            final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+            final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
+            final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
+
+            final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+            final long warningBytes = editor.getPolicyWarningBytes(template);
+            final long limitBytes = editor.getPolicyLimitBytes(template);
+
+            bytesPicker.setMaxValue(Integer.MAX_VALUE);
+            if (warningBytes != WARNING_DISABLED) {
+                bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1);
+            } else {
+                bytesPicker.setMinValue(0);
+            }
+            bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES));
+            bytesPicker.setWrapSelectorWheel(false);
+
+            builder.setTitle(R.string.data_usage_limit_editor_title);
+            builder.setView(view);
+
+            builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            // clear focus to finish pending text edits
+                            bytesPicker.clearFocus();
+
+                            final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
+                            editor.setPolicyLimitBytes(template, bytes);
+                            target.updatePolicy(false);
+                        }
+                    });
+
+            return builder.create();
+        }
+    }
+    /**
      * Dialog to request user confirmation before disabling data.
      */
     public static class ConfirmDataDisableFragment extends DialogFragment {
@@ -1661,7 +1845,7 @@
 
             final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
             if (target != null) {
-                final CharSequence limitedNetworks = target.buildLimitedNetworksList();
+                final CharSequence limitedNetworks = target.buildLimitedNetworksString();
                 builder.setMessage(
                         getString(R.string.data_usage_restrict_background, limitedNetworks));
             }
@@ -1681,6 +1865,31 @@
     }
 
     /**
+     * Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND}
+     * change has been denied, usually based on
+     * {@link DataUsageSummary#hasLimitedNetworks()}.
+     */
+    public static class DeniedRestrictFragment extends DialogFragment {
+        public static void show(DataUsageSummary parent) {
+            final DeniedRestrictFragment dialog = new DeniedRestrictFragment();
+            dialog.setTargetFragment(parent, 0);
+            dialog.show(parent.getFragmentManager(), TAG_DENIED_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_background);
+            builder.setMessage(R.string.data_usage_restrict_denied_dialog);
+            builder.setPositiveButton(android.R.string.ok, null);
+
+            return builder.create();
+        }
+    }
+
+    /**
      * Dialog to request user confirmation before setting
      * {@link #POLICY_REJECT_METERED_BACKGROUND}.
      */
@@ -1879,10 +2088,32 @@
     }
 
     /**
+     * Test if any networks are currently limited.
+     */
+    private boolean hasLimitedNetworks() {
+        return !buildLimitedNetworksList().isEmpty();
+    }
+
+    /**
      * Build string describing currently limited networks, which defines when
      * background data is restricted.
      */
-    private CharSequence buildLimitedNetworksList() {
+    private CharSequence buildLimitedNetworksString() {
+        final List<CharSequence> limited = buildLimitedNetworksList();
+
+        // handle case where no networks limited
+        if (limited.isEmpty()) {
+            limited.add(getText(R.string.data_usage_list_none));
+        }
+
+        return TextUtils.join(limited);
+    }
+
+    /**
+     * Build list of currently limited networks, which defines when background
+     * data is restricted.
+     */
+    private List<CharSequence> buildLimitedNetworksList() {
         final Context context = getActivity();
         final String subscriberId = getActiveSubscriberId(context);
 
@@ -1904,12 +2135,7 @@
             limited.add(getText(R.string.data_usage_tab_ethernet));
         }
 
-        // handle case where no networks limited
-        if (limited.isEmpty()) {
-            limited.add(getText(R.string.data_usage_list_none));
-        }
-
-        return TextUtils.join(limited);
+        return limited;
     }
 
     /**
diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java
index bb5a2c3..5ba8ca4 100644
--- a/src/com/android/settings/net/NetworkPolicyEditor.java
+++ b/src/com/android/settings/net/NetworkPolicyEditor.java
@@ -149,6 +149,10 @@
                 template, cycleDay, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER);
     }
 
+    public int getPolicyCycleDay(NetworkTemplate template) {
+        return getPolicy(template).cycleDay;
+    }
+
     public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
         final NetworkPolicy policy = getOrCreatePolicy(template);
         policy.cycleDay = cycleDay;
@@ -156,6 +160,10 @@
         writeAsync();
     }
 
+    public long getPolicyWarningBytes(NetworkTemplate template) {
+        return getPolicy(template).warningBytes;
+    }
+
     public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
         final NetworkPolicy policy = getOrCreatePolicy(template);
         policy.warningBytes = warningBytes;
@@ -163,6 +171,10 @@
         writeAsync();
     }
 
+    public long getPolicyLimitBytes(NetworkTemplate template) {
+        return getPolicy(template).limitBytes;
+    }
+
     public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
         final NetworkPolicy policy = getOrCreatePolicy(template);
         policy.limitBytes = limitBytes;
diff --git a/src/com/android/settings/widget/ChartDataUsageView.java b/src/com/android/settings/widget/ChartDataUsageView.java
index e831cc1..43ce97c 100644
--- a/src/com/android/settings/widget/ChartDataUsageView.java
+++ b/src/com/android/settings/widget/ChartDataUsageView.java
@@ -69,6 +69,8 @@
         public void onInspectRangeChanged();
         public void onWarningChanged();
         public void onLimitChanged();
+        public void requestWarningEdit();
+        public void requestLimitEdit();
     }
 
     private DataUsageChartListener mListener;
@@ -123,6 +125,15 @@
         mSweepWarning.addOnSweepListener(mVertListener);
         mSweepLimit.addOnSweepListener(mVertListener);
 
+        mSweepWarning.setDragInterval(5 * MB_IN_BYTES);
+        mSweepLimit.setDragInterval(5 * MB_IN_BYTES);
+
+        // TODO: make time sweeps adjustable through dpad
+        mSweepLeft.setClickable(false);
+        mSweepLeft.setFocusable(false);
+        mSweepRight.setClickable(false);
+        mSweepRight.setFocusable(false);
+
         // tell everyone about our axis
         mGrid.init(mHoriz, mVert);
         mSeries.init(mHoriz, mVert);
@@ -276,6 +287,7 @@
     }
 
     private OnSweepListener mHorizListener = new OnSweepListener() {
+        /** {@inheritDoc} */
         public void onSweep(ChartSweepView sweep, boolean sweepDone) {
             updatePrimaryRange();
 
@@ -284,6 +296,11 @@
                 mListener.onInspectRangeChanged();
             }
         }
+
+        /** {@inheritDoc} */
+        public void requestEdit(ChartSweepView sweep) {
+            // ignored
+        }
     };
 
     private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
@@ -298,6 +315,7 @@
     }
 
     private OnSweepListener mVertListener = new OnSweepListener() {
+        /** {@inheritDoc} */
         public void onSweep(ChartSweepView sweep, boolean sweepDone) {
             if (sweepDone) {
                 clearUpdateAxisDelayed(sweep);
@@ -313,6 +331,15 @@
                 sendUpdateAxisDelayed(sweep, false);
             }
         }
+
+        /** {@inheritDoc} */
+        public void requestEdit(ChartSweepView sweep) {
+            if (sweep == mSweepWarning && mListener != null) {
+                mListener.requestWarningEdit();
+            } else if (sweep == mSweepLimit && mListener != null) {
+                mListener.requestLimitEdit();
+            }
+        }
     };
 
     @Override
@@ -540,7 +567,7 @@
 
             final CharSequence unit;
             final long unitFactor;
-            if (value <= 100 * MB_IN_BYTES) {
+            if (value < 1000 * MB_IN_BYTES) {
                 unit = res.getText(com.android.internal.R.string.megabyteShort);
                 unitFactor = MB_IN_BYTES;
             } else {
@@ -551,6 +578,7 @@
             final double result = (double) value / unitFactor;
             final double resultRounded;
             final CharSequence size;
+
             if (result < 10) {
                 size = String.format("%.1f", result);
                 resultRounded = (unitFactor * Math.round(result * 10)) / 10;
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 2190588..22a6478 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -83,11 +83,22 @@
     public static final int HORIZONTAL = 0;
     public static final int VERTICAL = 1;
 
+    private int mTouchMode = MODE_NONE;
+
+    private static final int MODE_NONE = 0;
+    private static final int MODE_DRAG = 1;
+    private static final int MODE_LABEL = 2;
+
+    private long mDragInterval = 1;
+
     public interface OnSweepListener {
         public void onSweep(ChartSweepView sweep, boolean sweepDone);
+        public void requestEdit(ChartSweepView sweep);
     }
 
     private OnSweepListener mListener;
+
+    private float mTrackingStart;
     private MotionEvent mTracking;
 
     public ChartSweepView(Context context) {
@@ -112,15 +123,28 @@
         setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
         setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
 
+        // TODO: moved focused state directly into assets
+        setBackgroundResource(R.drawable.data_usage_sweep_background);
+
         mOutlinePaint.setColor(Color.RED);
         mOutlinePaint.setStrokeWidth(1f);
         mOutlinePaint.setStyle(Style.STROKE);
 
         a.recycle();
 
+        setClickable(true);
+        setFocusable(true);
+        setOnClickListener(mClickListener);
+
         setWillNotDraw(false);
     }
 
+    private OnClickListener mClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            dispatchRequestEdit();
+        }
+    };
+
     void init(ChartAxis axis) {
         mAxis = Preconditions.checkNotNull(axis, "missing axis");
     }
@@ -133,6 +157,10 @@
         return mMargins;
     }
 
+    public void setDragInterval(long dragInterval) {
+        mDragInterval = dragInterval;
+    }
+
     /**
      * Return the number of pixels that the "target" area is inset from the
      * {@link View} edge, along the current {@link #setFollowAxis(int)}.
@@ -159,9 +187,16 @@
         }
     }
 
+    private void dispatchRequestEdit() {
+        if (mListener != null) {
+            mListener.requestEdit(this);
+        }
+    }
+
     @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
+        setFocusable(enabled);
         requestLayout();
     }
 
@@ -232,6 +267,7 @@
     private void invalidateLabel() {
         if (mLabelTemplate != null && mAxis != null) {
             mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
+            setContentDescription(mLabelTemplate);
             invalidateLabelOffset();
             invalidate();
         } else {
@@ -369,11 +405,16 @@
             case MotionEvent.ACTION_DOWN: {
 
                 // only start tracking when in sweet spot
-                final boolean accept;
+                final boolean acceptDrag;
+                final boolean acceptLabel;
                 if (mFollowAxis == VERTICAL) {
-                    accept = event.getX() > getWidth() - (mSweepPadding.right * 8);
+                    acceptDrag = event.getX() > getWidth() - (mSweepPadding.right * 8);
+                    acceptLabel = mLabelLayout != null ? event.getX() < mLabelLayout.getWidth()
+                            : false;
                 } else {
-                    accept = event.getY() > getHeight() - (mSweepPadding.bottom * 8);
+                    acceptDrag = event.getY() > getHeight() - (mSweepPadding.bottom * 8);
+                    acceptLabel = mLabelLayout != null ? event.getY() < mLabelLayout.getHeight()
+                            : false;
                 }
 
                 final MotionEvent eventInParent = event.copy();
@@ -385,8 +426,14 @@
                     return false;
                 }
 
-                if (accept) {
+                if (acceptDrag) {
+                    if (mFollowAxis == VERTICAL) {
+                        mTrackingStart = getTop() - mMargins.top;
+                    } else {
+                        mTrackingStart = getLeft() - mMargins.left;
+                    }
                     mTracking = event.copy();
+                    mTouchMode = MODE_DRAG;
 
                     // starting drag should activate entire chart
                     if (!parent.isActivated()) {
@@ -394,47 +441,68 @@
                     }
 
                     return true;
+                } else if (acceptLabel) {
+                    mTouchMode = MODE_LABEL;
+                    return true;
                 } else {
+                    mTouchMode = MODE_NONE;
                     return false;
                 }
             }
             case MotionEvent.ACTION_MOVE: {
+                if (mTouchMode == MODE_LABEL) {
+                    return true;
+                }
+
                 getParent().requestDisallowInterceptTouchEvent(true);
 
                 // content area of parent
                 final Rect parentContent = getParentContentRect();
                 final Rect clampRect = computeClampRect(parentContent);
+                if (clampRect.isEmpty()) return true;
 
+                long value;
                 if (mFollowAxis == VERTICAL) {
                     final float currentTargetY = getTop() - mMargins.top;
-                    final float requestedTargetY = currentTargetY
+                    final float requestedTargetY = mTrackingStart
                             + (event.getRawY() - mTracking.getRawY());
                     final float clampedTargetY = MathUtils.constrain(
                             requestedTargetY, clampRect.top, clampRect.bottom);
                     setTranslationY(clampedTargetY - currentTargetY);
 
-                    setValue(mAxis.convertToValue(clampedTargetY - parentContent.top));
+                    value = mAxis.convertToValue(clampedTargetY - parentContent.top);
                 } else {
                     final float currentTargetX = getLeft() - mMargins.left;
-                    final float requestedTargetX = currentTargetX
+                    final float requestedTargetX = mTrackingStart
                             + (event.getRawX() - mTracking.getRawX());
                     final float clampedTargetX = MathUtils.constrain(
                             requestedTargetX, clampRect.left, clampRect.right);
                     setTranslationX(clampedTargetX - currentTargetX);
 
-                    setValue(mAxis.convertToValue(clampedTargetX - parentContent.left));
+                    value = mAxis.convertToValue(clampedTargetX - parentContent.left);
                 }
 
+                // round value from drag to nearest increment
+                value -= value % mDragInterval;
+                setValue(value);
+
                 dispatchOnSweep(false);
                 return true;
             }
             case MotionEvent.ACTION_UP: {
-                mTracking = null;
-                mValue = mLabelValue;
-                dispatchOnSweep(true);
-                setTranslationX(0);
-                setTranslationY(0);
-                requestLayout();
+                if (mTouchMode == MODE_LABEL) {
+                    performClick();
+                } else if (mTouchMode == MODE_DRAG) {
+                    mTrackingStart = 0;
+                    mTracking = null;
+                    mValue = mLabelValue;
+                    dispatchOnSweep(true);
+                    setTranslationX(0);
+                    setTranslationY(0);
+                    requestLayout();
+                }
+
+                mTouchMode = MODE_NONE;
                 return true;
             }
             default: {
@@ -501,7 +569,9 @@
         final Rect dynamicRect = buildClampRect(
                 parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
 
-        rect.intersect(dynamicRect);
+        if (!rect.intersect(dynamicRect)) {
+            rect.setEmpty();
+        }
         return rect;
     }
 
@@ -587,7 +657,7 @@
             mContentOffset.bottom -= offset;
             mMargins.bottom += offset;
         } else {
-            final int heightAfter = heightBefore * 3;
+            final int heightAfter = heightBefore * 2;
             setMeasuredDimension(widthBefore, heightAfter);
             mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
 
@@ -608,13 +678,11 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
         final int width = getWidth();
         final int height = getHeight();
 
-        if (DRAW_OUTLINE) {
-            canvas.drawRect(0, 0, width, height, mOutlinePaint);
-        }
-
         final int labelSize;
         if (isEnabled() && mLabelLayout != null) {
             final int count = canvas.save();
@@ -637,6 +705,11 @@
         }
 
         mSweep.draw(canvas);
+
+        if (DRAW_OUTLINE) {
+            mOutlinePaint.setColor(Color.RED);
+            canvas.drawRect(0, 0, width, height, mOutlinePaint);
+        }
     }
 
     public static float getLabelTop(ChartSweepView view) {