Merge "Data usage fit and finish."
diff --git a/res/layout/data_usage_chart.xml b/res/layout/data_usage_chart.xml
index a942bb3..3409d64 100644
--- a/res/layout/data_usage_chart.xml
+++ b/res/layout/data_usage_chart.xml
@@ -51,20 +51,6 @@
         settings:fillColorSecondary="#0000" />
 
     <com.android.settings.widget.ChartSweepView
-        android:id="@+id/sweep_left"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        settings:sweepDrawable="@drawable/data_sweep_left"
-        settings:followAxis="horizontal" />
-
-    <com.android.settings.widget.ChartSweepView
-        android:id="@+id/sweep_right"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        settings:sweepDrawable="@drawable/data_sweep_right"
-        settings:followAxis="horizontal" />
-
-    <com.android.settings.widget.ChartSweepView
         android:id="@+id/sweep_warning"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -84,4 +70,18 @@
         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"
+        settings:sweepDrawable="@drawable/data_sweep_left"
+        settings:followAxis="horizontal" />
+
+    <com.android.settings.widget.ChartSweepView
+        android:id="@+id/sweep_right"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        settings:sweepDrawable="@drawable/data_sweep_right"
+        settings:followAxis="horizontal" />
+
 </com.android.settings.widget.DataUsageChartView>
diff --git a/res/layout/data_usage_cycles.xml b/res/layout/data_usage_cycles.xml
new file mode 100644
index 0000000..225fb4b
--- /dev/null
+++ b/res/layout/data_usage_cycles.xml
@@ -0,0 +1,37 @@
+<?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:id="@+id/cycles"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:paddingLeft="16dip"
+    android:paddingRight="16dip">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:text="@string/data_usage_cycle" />
+
+    <Spinner
+        android:id="@+id/cycles_spinner"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/res/layout/data_usage_header.xml b/res/layout/data_usage_header.xml
index 547d85d..52f56c1 100644
--- a/res/layout/data_usage_header.xml
+++ b/res/layout/data_usage_header.xml
@@ -32,28 +32,19 @@
             android:divider="?android:attr/listDivider" />
     </FrameLayout>
 
-    <LinearLayout
+    <include layout="@layout/data_usage_cycles" />
+    <include layout="@layout/data_usage_chart" />
+
+    <TextView
+        android:id="@+id/usage_summary"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="horizontal"
         android:paddingLeft="16dip"
-        android:paddingRight="16dip">
+        android:paddingRight="16dip"
+        android:paddingTop="8dip"
+        android:paddingBottom="8dip"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
 
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:text="@string/data_usage_cycle" />
-
-        <Spinner
-            android:id="@+id/cycles"
-            android:layout_width="0dip"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" />
-
-    </LinearLayout>
-
-    <include layout="@layout/data_usage_chart" />
     <include layout="@layout/data_usage_detail" />
 
 </LinearLayout>
diff --git a/res/layout/data_usage_item.xml b/res/layout/data_usage_item.xml
index d709753..36f8b4d 100644
--- a/res/layout/data_usage_item.xml
+++ b/res/layout/data_usage_item.xml
@@ -17,20 +17,22 @@
 <GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:padding="8dip"
+    android:paddingLeft="16dip"
+    android:paddingRight="16dip"
+    android:paddingTop="8dip"
+    android:paddingBottom="8dip"
     android:columnCount="2">
 
-    <!-- TODO: consider using canShrink -->
     <TextView
         android:id="@android:id/title"
+        android:layout_width="0dip"
+        android:layout_gravity="fill_horizontal"
         android:singleLine="true"
         android:ellipsize="marquee"
-        android:layout_columnFlexibility="canStretch"
         android:textAppearance="?android:attr/textAppearanceMedium" />
 
     <TextView
         android:id="@android:id/summary"
-        android:layout_gravity="right"
         android:layout_marginLeft="8dip"
         android:textAppearance="?android:attr/textAppearanceSmall" />
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 47fed2f..7fd38c2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3468,6 +3468,8 @@
     <string name="data_usage_uninstalled_apps">Removed apps</string>
     <!-- Combination of total network bytes sent and received by an application. [CHAR LIMIT=NONE] -->
     <string name="data_usage_received_sent"><xliff:g id="received" example="128KB">%1$s</xliff:g> received, <xliff:g id="sent" example="1.3GB">%2$s</xliff:g> sent</string>
+    <!-- Label displaying total network data transferred during a specific time period. [CHAR LIMIT=64] -->
+    <string name="data_usage_total_during_range">Data usage: <xliff:g id="total" example="128KB">%1$s</xliff:g> between <xliff:g id="range" example="Jul 1 - Jul 31">%2$s</xliff:g></string>
 
     <!-- Button at the bottom of the CryptKeeper screen to make an emergency call. -->
     <string name="cryptkeeper_emergency_call">Emergency call</string>
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 0b5d295..d87080f 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -128,6 +128,7 @@
     private static final boolean LOGD = true;
 
     // TODO: remove this testing code
+    private static final boolean TEST_ANIM = false;
     private static final boolean TEST_RADIOS = false;
     private static final String TEST_RADIOS_PROP = "test.radios";
 
@@ -175,10 +176,12 @@
     private CheckBox mDisableAtLimit;
     private View mDisableAtLimitView;
 
+    private View mCycleView;
     private Spinner mCycleSpinner;
     private CycleAdapter mCycleAdapter;
 
     private DataUsageChartView mChart;
+    private TextView mUsageSummary;
 
     private View mAppDetail;
     private TextView mAppTitle;
@@ -272,7 +275,8 @@
         }
 
         // bind cycle dropdown
-        mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
+        mCycleView = mHeader.findViewById(R.id.cycles);
+        mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
         mCycleAdapter = new CycleAdapter(context);
         mCycleSpinner.setAdapter(mCycleAdapter);
         mCycleSpinner.setOnItemSelectedListener(mCycleListener);
@@ -282,12 +286,12 @@
 
         {
             // 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);
+            mAppDetail = mHeader.findViewById(R.id.app_detail);
+            mAppTitle = (TextView) mAppDetail.findViewById(R.id.app_title);
+            mAppSubtitle = (TextView) mAppDetail.findViewById(R.id.app_subtitle);
+            mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches);
 
-            mAppSettings = (Button) view.findViewById(R.id.app_settings);
+            mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings);
             mAppSettings.setOnClickListener(mAppSettingsListener);
 
             mAppRestrict = new CheckBox(inflater.getContext());
@@ -300,8 +304,10 @@
             mAppSwitches.addView(mAppRestrictView);
         }
 
+        mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary);
+
         // only assign layout transitions once first layout is finished
-        mHeader.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
+        mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
 
         mAdapter = new DataUsageAdapter();
         mListView.setOnItemClickListener(mListListener);
@@ -443,19 +449,28 @@
     private OnGlobalLayoutListener mFirstLayoutListener = new OnGlobalLayoutListener() {
         /** {@inheritDoc} */
         public void onGlobalLayout() {
-            mHeader.getViewTreeObserver().removeGlobalOnLayoutListener(mFirstLayoutListener);
+            mListView.getViewTreeObserver().removeGlobalOnLayoutListener(mFirstLayoutListener);
 
-            mTabsContainer.setLayoutTransition(new LayoutTransition());
-            mHeader.setLayoutTransition(new LayoutTransition());
-            mNetworkSwitchesContainer.setLayoutTransition(new LayoutTransition());
+            mTabsContainer.setLayoutTransition(buildLayoutTransition());
+            mHeader.setLayoutTransition(buildLayoutTransition());
+            mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition());
 
-            final LayoutTransition chartTransition = new LayoutTransition();
+            final LayoutTransition chartTransition = buildLayoutTransition();
             chartTransition.setStartDelay(LayoutTransition.APPEARING, 0);
             chartTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
             mChart.setLayoutTransition(chartTransition);
         }
     };
 
+    private static LayoutTransition buildLayoutTransition() {
+        final LayoutTransition transition = new LayoutTransition();
+        if (TEST_ANIM) {
+            transition.setDuration(1500);
+        }
+        transition.setAnimateParentHierarchy(false);
+        return transition;
+    }
+
     /**
      * Rebuild all tabs based on {@link NetworkPolicyEditor} and
      * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
@@ -668,7 +683,7 @@
         updateDetailData();
 
         final Context context = getActivity();
-        if (NetworkPolicyManager.isUidValidForPolicy(context, mUid)) {
+        if (NetworkPolicyManager.isUidValidForPolicy(context, mUid) && !getRestrictBackground()) {
             mAppRestrictView.setVisibility(View.VISIBLE);
             mAppRestrict.setChecked(getAppRestrictBackground());
 
@@ -710,13 +725,22 @@
     }
 
     private boolean getRestrictBackground() {
-        return !mConnService.getBackgroundDataSetting();
+        try {
+            return mPolicyService.getRestrictBackground();
+        } catch (RemoteException e) {
+            Log.w(TAG, "problem talking with policy service: " + e);
+            return false;
+        }
     }
 
     private void setRestrictBackground(boolean restrictBackground) {
         if (LOGD) Log.d(TAG, "setRestrictBackground()");
-        mConnService.setBackgroundDataSetting(!restrictBackground);
-        mMenuRestrictBackground.setChecked(restrictBackground);
+        try {
+            mPolicyService.setRestrictBackground(restrictBackground);
+            mMenuRestrictBackground.setChecked(restrictBackground);
+        } catch (RemoteException e) {
+            Log.w(TAG, "problem talking with policy service: " + e);
+        }
     }
 
     private boolean getAppRestrictBackground() {
@@ -732,7 +756,7 @@
     }
 
     private void setAppRestrictBackground(boolean restrictBackground) {
-        if (LOGD) Log.d(TAG, "setRestrictBackground()");
+        if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
         try {
             mPolicyService.setUidPolicy(
                     mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
@@ -909,7 +933,7 @@
 
                 // update chart to show selected cycle, and update detail data
                 // to match updated sweep bounds.
-                mChart.setVisibleRange(cycle.start, cycle.end, mHistory.getEnd());
+                mChart.setVisibleRange(cycle.start, cycle.end);
 
                 updateDetailData();
             }
@@ -932,28 +956,40 @@
 
         final long start = mChart.getInspectStart();
         final long end = mChart.getInspectEnd();
+        final long now = System.currentTimeMillis();
+
+        final Context context = getActivity();
+        final NetworkStatsHistory.Entry entry;
 
         if (isAppDetailMode()) {
             if (mDetailHistory != null) {
-                final Context context = mChart.getContext();
-                final long now = System.currentTimeMillis();
-                final NetworkStatsHistory.Entry entry = mDetailHistory.getValues(
-                        start, end, now, null);
+                entry = mDetailHistory.getValues(start, end, now, null);
 
                 mAppSubtitle.setText(
                         getString(R.string.data_usage_received_sent,
                                 Formatter.formatFileSize(context, entry.rxBytes),
                                 Formatter.formatFileSize(context, entry.txBytes)));
+            } else {
+                entry = null;
             }
 
             getLoaderManager().destroyLoader(LOADER_SUMMARY);
 
         } else {
+            entry = mHistory.getValues(start, end, now, null);
+
             // kick off loader for detailed stats
             getLoaderManager().restartLoader(LOADER_SUMMARY,
                     SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
 
         }
+
+        final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
+        final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
+        final String rangePhrase = formatDateRangeUtc(context, start, end);
+
+        mUsageSummary.setText(
+                getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
     }
 
     private final LoaderCallbacks<NetworkStats> mSummaryForAllUid = new LoaderCallbacks<
@@ -1022,10 +1058,6 @@
         public long start;
         public long end;
 
-        private static final StringBuilder sBuilder = new StringBuilder(50);
-        private static final java.util.Formatter sFormatter = new java.util.Formatter(
-                sBuilder, Locale.getDefault());
-
         CycleItem(CharSequence label) {
             this.label = label;
         }
@@ -1036,21 +1068,30 @@
             this.end = end;
         }
 
-        private static String formatDateRangeUtc(Context context, long start, long end) {
-            synchronized (sBuilder) {
-                sBuilder.setLength(0);
-                return DateUtils.formatDateRange(context, sFormatter, start, end,
-                        DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
-                        Time.TIMEZONE_UTC).toString();
-            }
-        }
-
         @Override
         public String toString() {
             return label.toString();
         }
     }
 
+    private static final StringBuilder sBuilder = new StringBuilder(50);
+    private static final java.util.Formatter sFormatter = new java.util.Formatter(
+            sBuilder, Locale.getDefault());
+
+    private static String formatDateRangeUtc(Context context, long start, long end) {
+        synchronized (sBuilder) {
+            int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH;
+            if (Time.getJulianDay(start, 0) == Time.getJulianDay(end, 0)) {
+                // when times are on same day, include time detail
+                flags |= DateUtils.FORMAT_SHOW_TIME;
+            }
+
+            sBuilder.setLength(0);
+            return DateUtils.formatDateRange(
+                    context, sFormatter, start, end, flags, Time.TIMEZONE_UTC).toString();
+        }
+    }
+
     /**
      * Special-case data usage cycle that triggers dialog to change
      * {@link NetworkPolicy#cycleDay}.
@@ -1366,7 +1407,7 @@
 
     /**
      * Dialog to request user confirmation before setting
-     * {@link ConnectivityManager#setBackgroundDataSetting(boolean)}.
+     * {@link INetworkPolicyManager#setRestrictBackground(boolean)}.
      */
     public static class ConfirmRestrictFragment extends DialogFragment {
         public static void show(DataUsageSummary parent) {
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 4e37657..99c35bd 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -61,8 +61,11 @@
     private ChartAxis mAxis;
     private long mValue;
 
-    private ChartSweepView mClampAfter;
-    private ChartSweepView mClampBefore;
+    private long mValidAfter;
+    private long mValidBefore;
+    private ChartSweepView mValidAfterDynamic;
+    private ChartSweepView mValidBeforeDynamic;
+    private long mValidBufferArea;
 
     public static final int HORIZONTAL = 0;
     public static final int VERTICAL = 1;
@@ -259,12 +262,25 @@
         }
     }
 
-    public void setClampAfter(ChartSweepView clampAfter) {
-        mClampAfter = clampAfter;
+    /**
+     * Set valid range this sweep can move within, in {@link #mAxis} values. The
+     * most restrictive combination of all valid ranges is used.
+     */
+    public void setValidRange(long validAfter, long validBefore) {
+        mValidAfter = validAfter;
+        mValidBefore = validBefore;
     }
 
-    public void setClampBefore(ChartSweepView clampBefore) {
-        mClampBefore = clampBefore;
+    /**
+     * Set valid range this sweep can move within, defined by the given
+     * {@link ChartSweepView}. The most restrictive combination of all valid
+     * ranges is used.
+     */
+    public void setValidRangeDynamic(
+            ChartSweepView validAfter, ChartSweepView validBefore, long bufferArea) {
+        mValidAfterDynamic = validAfter;
+        mValidBeforeDynamic = validBefore;
+        mValidBufferArea = bufferArea;
     }
 
     @Override
@@ -285,6 +301,12 @@
 
                 if (accept) {
                     mTracking = event.copy();
+
+                    // starting drag should activate entire chart
+                    if (!parent.isActivated()) {
+                        parent.setActivated(true);
+                    }
+
                     return true;
                 } else {
                     return false;
@@ -336,31 +358,52 @@
         }
     }
 
+    @Override
+    public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
+        // ignored to keep LayoutTransition from animating us
+    }
+
+    @Override
+    public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) {
+        // ignored to keep LayoutTransition from animating us
+    }
+
+    private long getValidAfterValue() {
+        final ChartSweepView dynamic = mValidAfterDynamic;
+        final boolean dynamicEnabled = dynamic != null && dynamic.isEnabled();
+        return Math.max(mValidAfter,
+                dynamicEnabled ? dynamic.getValue() + mValidBufferArea : Long.MIN_VALUE);
+    }
+
+    private long getValidBeforeValue() {
+        final ChartSweepView dynamic = mValidBeforeDynamic;
+        final boolean dynamicEnabled = dynamic != null && dynamic.isEnabled();
+        return Math.min(mValidBefore,
+                dynamicEnabled ? dynamic.getValue() - mValidBufferArea : Long.MAX_VALUE);
+    }
+
     /**
      * Compute {@link Rect} in {@link #getParent()} coordinates that we should
-     * be clamped inside of, usually from {@link #setClampAfter(ChartSweepView)}
+     * be clamped inside of, usually from {@link #setValidRange(long, long)}
      * style rules.
      */
     private Rect computeClampRect(Rect parentContent) {
         final Rect clampRect = new Rect(parentContent);
 
-        final ChartSweepView after = mClampAfter;
-        final ChartSweepView before = mClampBefore;
+        float validAfterPoint = mAxis.convertToPoint(getValidAfterValue());
+        float validBeforePoint = mAxis.convertToPoint(getValidBeforeValue());
+        if (validAfterPoint > validBeforePoint) {
+            float swap = validBeforePoint;
+            validBeforePoint = validAfterPoint;
+            validAfterPoint = swap;
+        }
 
         if (mFollowAxis == VERTICAL) {
-            if (after != null) {
-                clampRect.top += after.getPoint();
-            }
-            if (before != null) {
-                clampRect.bottom -= clampRect.height() - before.getPoint();
-            }
+            clampRect.bottom = clampRect.top + (int) validBeforePoint;
+            clampRect.top += validAfterPoint;
         } else {
-            if (after != null) {
-                clampRect.left += after.getPoint();
-            }
-            if (before != null) {
-                clampRect.right -= clampRect.width() - before.getPoint();
-            }
+            clampRect.right = clampRect.left + (int) validBeforePoint;
+            clampRect.left += validAfterPoint;
         }
         return clampRect;
     }
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java
index affede0..839171e 100644
--- a/src/com/android/settings/widget/DataUsageChartView.java
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.widget;
 
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.NetworkPolicy;
@@ -47,6 +49,8 @@
     private ChartNetworkSeriesView mSeries;
     private ChartNetworkSeriesView mDetailSeries;
 
+    private NetworkStatsHistory mHistory;
+
     private ChartSweepView mSweepLeft;
     private ChartSweepView mSweepRight;
     private ChartSweepView mSweepWarning;
@@ -88,10 +92,14 @@
         mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
 
         // prevent sweeps from crossing each other
-        mSweepLeft.setClampBefore(mSweepRight);
-        mSweepRight.setClampAfter(mSweepLeft);
-        mSweepLimit.setClampBefore(mSweepWarning);
-        mSweepWarning.setClampAfter(mSweepLimit);
+        mSweepLeft.setValidRangeDynamic(null, mSweepRight, HOUR_IN_MILLIS);
+        mSweepRight.setValidRangeDynamic(mSweepLeft, null, HOUR_IN_MILLIS);
+
+        // TODO: assign these ranges as user changes data axis
+        mSweepWarning.setValidRange(0L, 5 * GB_IN_BYTES);
+        mSweepWarning.setValidRangeDynamic(null, mSweepLimit, MB_IN_BYTES);
+        mSweepLimit.setValidRange(0L, 5 * GB_IN_BYTES);
+        mSweepLimit.setValidRangeDynamic(mSweepWarning, null, MB_IN_BYTES);
 
         mSweepLeft.addOnSweepListener(mSweepListener);
         mSweepRight.addOnSweepListener(mSweepListener);
@@ -116,6 +124,7 @@
 
     public void bindNetworkStats(NetworkStatsHistory stats) {
         mSeries.bindNetworkStats(stats);
+        mHistory = stats;
         updatePrimaryRange();
         requestLayout();
     }
@@ -197,15 +206,6 @@
         }
     }
 
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (!isActivated()) {
-            return true;
-        } else {
-            return super.onInterceptTouchEvent(ev);
-        }
-    }
-
     public long getInspectStart() {
         return mSweepLeft.getValue();
     }
@@ -222,18 +222,33 @@
         return mSweepLimit.getValue();
     }
 
+    private long getStatsStart() {
+        return mHistory != null ? mHistory.getStart() : Long.MIN_VALUE;
+    }
+
+    private long getStatsEnd() {
+        return mHistory != null ? mHistory.getEnd() : Long.MAX_VALUE;
+    }
+
     /**
      * Set the exact time range that should be displayed, updating how
      * {@link ChartNetworkSeriesView} paints. Moves inspection ranges to be the
      * last "week" of available data, without triggering listener events.
      */
-    public void setVisibleRange(long start, long end, long dataBoundary) {
-        mHoriz.setBounds(start, end);
+    public void setVisibleRange(long visibleStart, long visibleEnd) {
+        mHoriz.setBounds(visibleStart, visibleEnd);
+
+        final long validStart = Math.max(visibleStart, getStatsStart());
+        final long validEnd = Math.min(visibleEnd, getStatsEnd());
+
+        // prevent time sweeps from leaving valid data
+        mSweepLeft.setValidRange(validStart, validEnd);
+        mSweepRight.setValidRange(validStart, validEnd);
 
         // default sweeps to last week of data
-        final long halfRange = (end + start) / 2;
-        final long sweepMax = Math.min(end, dataBoundary);
-        final long sweepMin = Math.max(start, (sweepMax - DateUtils.WEEK_IN_MILLIS));
+        final long halfRange = (visibleEnd + visibleStart) / 2;
+        final long sweepMax = validEnd;
+        final long sweepMin = Math.max(visibleStart, (sweepMax - DateUtils.WEEK_IN_MILLIS));
 
         mSweepLeft.setValue(sweepMin);
         mSweepRight.setValue(sweepMax);