Data usage strings, sweep touches, fixes.

Change strings around limiting background data.  Move limit/warning
sweeps above inspection sweeps, and teach about additional neighbors
on different axis.

Guard against DialogFragment.show(), fix pie chart to draw edges, and
remove data usage from battery UI.

Bug: 5341374, 5337650, 5337385, 5319465, 5236335
Change-Id: Iea8c2a2ab405b645d85abe34a0178d4b8874cdd5
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index d6c40b2..6096029 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -751,9 +751,13 @@
         if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid)
                 && !getRestrictBackground() && isBandwidthControlEnabled()) {
             setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
-            setPreferenceSummary(mAppRestrictView,
-                    getString(R.string.data_usage_app_restrict_background_summary,
-                            buildLimitedNetworksString()));
+            if (hasLimitedNetworks()) {
+                setPreferenceSummary(mAppRestrictView,
+                        getString(R.string.data_usage_app_restrict_background_summary));
+            } else {
+                setPreferenceSummary(mAppRestrictView,
+                        getString(R.string.data_usage_app_restrict_background_summary_disabled));
+            }
 
             mAppRestrictView.setVisibility(View.VISIBLE);
             mAppRestrict.setChecked(getAppRestrictBackground());
@@ -925,8 +929,9 @@
             historyEnd = mChartData.network.getEnd();
         }
 
-        if (historyStart == Long.MAX_VALUE) historyStart = System.currentTimeMillis();
-        if (historyEnd == Long.MIN_VALUE) historyEnd = System.currentTimeMillis();
+        final long now = System.currentTimeMillis();
+        if (historyStart == Long.MAX_VALUE) historyStart = now;
+        if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1;
 
         boolean hasCycles = false;
         if (policy != null) {
@@ -1489,6 +1494,8 @@
         private static final String EXTRA_UIDS = "uids";
 
         public static void show(DataUsageSummary parent, int[] uids, CharSequence label) {
+            if (!parent.isAdded()) return;
+
             final Bundle args = new Bundle();
             args.putIntArray(EXTRA_UIDS, uids);
 
@@ -1529,8 +1536,9 @@
         private static final String EXTRA_LIMIT_BYTES = "limitBytes";
 
         public static void show(DataUsageSummary parent) {
-            final Resources res = parent.getResources();
+            if (!parent.isAdded()) return;
 
+            final Resources res = parent.getResources();
             final CharSequence message;
             final long limitBytes;
 
@@ -1597,6 +1605,8 @@
         private static final String EXTRA_TEMPLATE = "template";
 
         public static void show(DataUsageSummary parent) {
+            if (!parent.isAdded()) return;
+
             final Bundle args = new Bundle();
             args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
 
@@ -1649,6 +1659,8 @@
         private static final String EXTRA_TEMPLATE = "template";
 
         public static void show(DataUsageSummary parent) {
+            if (!parent.isAdded()) return;
+
             final Bundle args = new Bundle();
             args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
 
@@ -1709,6 +1721,8 @@
         private static final String EXTRA_TEMPLATE = "template";
 
         public static void show(DataUsageSummary parent) {
+            if (!parent.isAdded()) return;
+
             final Bundle args = new Bundle();
             args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
 
@@ -1766,6 +1780,8 @@
      */
     public static class ConfirmDataDisableFragment extends DialogFragment {
         public static void show(DataUsageSummary parent) {
+            if (!parent.isAdded()) return;
+
             final ConfirmDataDisableFragment dialog = new ConfirmDataDisableFragment();
             dialog.setTargetFragment(parent, 0);
             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_DISABLE);
@@ -1799,6 +1815,8 @@
      */
     public static class ConfirmDataRoamingFragment extends DialogFragment {
         public static void show(DataUsageSummary parent) {
+            if (!parent.isAdded()) return;
+
             final ConfirmDataRoamingFragment dialog = new ConfirmDataRoamingFragment();
             dialog.setTargetFragment(parent, 0);
             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_ROAMING);
@@ -1832,6 +1850,8 @@
      */
     public static class ConfirmRestrictFragment extends DialogFragment {
         public static void show(DataUsageSummary parent) {
+            if (!parent.isAdded()) return;
+
             final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
             dialog.setTargetFragment(parent, 0);
             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
@@ -1843,13 +1863,7 @@
 
             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
             builder.setTitle(R.string.data_usage_restrict_background_title);
-
-            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
-            if (target != null) {
-                final CharSequence limitedNetworks = target.buildLimitedNetworksString();
-                builder.setMessage(
-                        getString(R.string.data_usage_restrict_background, limitedNetworks));
-            }
+            builder.setMessage(getString(R.string.data_usage_restrict_background));
 
             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                 public void onClick(DialogInterface dialog, int which) {
@@ -1872,6 +1886,8 @@
      */
     public static class DeniedRestrictFragment extends DialogFragment {
         public static void show(DataUsageSummary parent) {
+            if (!parent.isAdded()) return;
+
             final DeniedRestrictFragment dialog = new DeniedRestrictFragment();
             dialog.setTargetFragment(parent, 0);
             dialog.show(parent.getFragmentManager(), TAG_DENIED_RESTRICT);
@@ -1896,6 +1912,8 @@
      */
     public static class ConfirmAppRestrictFragment extends DialogFragment {
         public static void show(DataUsageSummary parent) {
+            if (!parent.isAdded()) return;
+
             final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment();
             dialog.setTargetFragment(parent, 0);
             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT);
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index c24c5ea..6ff3e0e 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -202,6 +202,8 @@
         switch (sipper.drainType) {
             case APP:
             {
+                // TODO: surface tcpBytesSent/tcpBytesReceived again once
+                // measured separately from uid_stats.
                 Uid uid = sipper.uidObj;
                 types = new int[] {
                     R.string.usage_type_cpu,
@@ -209,8 +211,6 @@
                     R.string.usage_type_wake_lock,
                     R.string.usage_type_gps,
                     R.string.usage_type_wifi_running,
-                    R.string.usage_type_data_send,
-                    R.string.usage_type_data_recv,
                     R.string.usage_type_audio,
                     R.string.usage_type_video,
                 };
@@ -220,8 +220,6 @@
                     sipper.wakeLockTime,
                     sipper.gpsTime,
                     sipper.wifiRunningTime,
-                    sipper.tcpBytesSent,
-                    sipper.tcpBytesReceived,
                     0,
                     0
                 };
diff --git a/src/com/android/settings/widget/ChartDataUsageView.java b/src/com/android/settings/widget/ChartDataUsageView.java
index 43ce97c..f10ea5e 100644
--- a/src/com/android/settings/widget/ChartDataUsageView.java
+++ b/src/com/android/settings/widget/ChartDataUsageView.java
@@ -120,6 +120,12 @@
         mSweepWarning.setValidRangeDynamic(null, mSweepLimit);
         mSweepLimit.setValidRangeDynamic(mSweepWarning, null);
 
+        // mark neighbors for checking touch events against
+        mSweepLeft.setNeighbors(mSweepRight);
+        mSweepRight.setNeighbors(mSweepLeft);
+        mSweepLimit.setNeighbors(mSweepWarning, mSweepLeft, mSweepRight);
+        mSweepWarning.setNeighbors(mSweepLimit, mSweepLeft, mSweepRight);
+
         mSweepLeft.addOnSweepListener(mHorizListener);
         mSweepRight.addOnSweepListener(mHorizListener);
         mSweepWarning.addOnSweepListener(mVertListener);
@@ -375,12 +381,12 @@
         return mSweepLimit.getLabelValue();
     }
 
-    private long getStatsStart() {
-        return mHistory != null ? mHistory.getStart() : Long.MIN_VALUE;
+    private long getHistoryStart() {
+        return mHistory != null ? mHistory.getStart() : Long.MAX_VALUE;
     }
 
-    private long getStatsEnd() {
-        return mHistory != null ? mHistory.getEnd() : Long.MAX_VALUE;
+    private long getHistoryEnd() {
+        return mHistory != null ? mHistory.getEnd() : Long.MIN_VALUE;
     }
 
     /**
@@ -394,8 +400,13 @@
         mSeries.setBounds(visibleStart, visibleEnd);
         mDetailSeries.setBounds(visibleStart, visibleEnd);
 
-        final long validStart = Math.max(visibleStart, getStatsStart());
-        final long validEnd = Math.min(visibleEnd, getStatsEnd());
+        final long historyStart = getHistoryStart();
+        final long historyEnd = getHistoryEnd();
+
+        final long validStart = historyStart == Long.MAX_VALUE ? visibleStart
+                : Math.max(visibleStart, historyStart);
+        final long validEnd = historyEnd == Long.MIN_VALUE ? visibleEnd
+                : Math.min(visibleEnd, historyEnd);
 
         if (LIMIT_SWEEPS_TO_VALID_DATA) {
             // prevent time sweeps from leaving valid data
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 22a6478..c5f2aba 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -101,6 +101,8 @@
     private float mTrackingStart;
     private MotionEvent mTracking;
 
+    private ChartSweepView[] mNeighbors = new ChartSweepView[0];
+
     public ChartSweepView(Context context) {
         this(context, null);
     }
@@ -149,6 +151,10 @@
         mAxis = Preconditions.checkNotNull(axis, "missing axis");
     }
 
+    public void setNeighbors(ChartSweepView... neighbors) {
+        mNeighbors = neighbors;
+    }
+
     public int getFollowAxis() {
         return mFollowAxis;
     }
@@ -381,18 +387,16 @@
      * {@link ChartSweepView} compared to ourselves.
      */
     public boolean isTouchCloserTo(MotionEvent eventInParent, ChartSweepView another) {
-        if (another == null) return false;
+        final float selfDist = getTouchDistanceFromTarget(eventInParent);
+        final float anotherDist = another.getTouchDistanceFromTarget(eventInParent);
+        return anotherDist < selfDist;
+    }
 
+    private float getTouchDistanceFromTarget(MotionEvent eventInParent) {
         if (mFollowAxis == HORIZONTAL) {
-            final float selfDist = Math.abs(eventInParent.getX() - (getX() + getTargetInset()));
-            final float anotherDist = Math.abs(
-                    eventInParent.getX() - (another.getX() + another.getTargetInset()));
-            return anotherDist < selfDist;
+            return Math.abs(eventInParent.getX() - (getX() + getTargetInset()));
         } else {
-            final float selfDist = Math.abs(eventInParent.getY() - (getY() + getTargetInset()));
-            final float anotherDist = Math.abs(
-                    eventInParent.getY() - (another.getY() + another.getTargetInset()));
-            return anotherDist < selfDist;
+            return Math.abs(eventInParent.getY() - (getY() + getTargetInset()));
         }
     }
 
@@ -421,9 +425,10 @@
                 eventInParent.offsetLocation(getLeft(), getTop());
 
                 // ignore event when closer to a neighbor
-                if (isTouchCloserTo(eventInParent, mValidAfterDynamic)
-                        || isTouchCloserTo(eventInParent, mValidBeforeDynamic)) {
-                    return false;
+                for (ChartSweepView neighbor : mNeighbors) {
+                    if (isTouchCloserTo(eventInParent, neighbor)) {
+                        return false;
+                    }
                 }
 
                 if (acceptDrag) {
diff --git a/src/com/android/settings/widget/PieChartView.java b/src/com/android/settings/widget/PieChartView.java
index 6765733..bd77f04 100644
--- a/src/com/android/settings/widget/PieChartView.java
+++ b/src/com/android/settings/widget/PieChartView.java
@@ -20,6 +20,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
 import android.graphics.Path;
@@ -42,31 +43,34 @@
     public static final String TAG = "PieChartView";
     public static final boolean LOGD = false;
 
+    private static final boolean FILL_GRADIENT = false;
+
     private ArrayList<Slice> mSlices = Lists.newArrayList();
 
     private int mOriginAngle;
+    private Matrix mMatrix = new Matrix();
 
-    private Paint mPaintPrimary = new Paint();
-    private Paint mPaintShadow = new Paint();
+    private Paint mPaintOutline = new Paint();
 
     private Path mPathSide = new Path();
-    private Path mPathSideShadow = new Path();
+    private Path mPathSideOutline = new Path();
 
-    private Path mPathShadow = new Path();
+    private Path mPathOutline = new Path();
 
     private int mSideWidth;
 
     public class Slice {
         public long value;
 
-        public Path pathPrimary = new Path();
-        public Path pathShadow = new Path();
+        public Path path = new Path();
+        public Path pathSide = new Path();
+        public Path pathOutline = new Path();
 
-        public Paint paintPrimary;
+        public Paint paint;
 
         public Slice(long value, int color) {
             this.value = value;
-            this.paintPrimary = buildFillPaint(color, getResources());
+            this.paint = buildFillPaint(color, getResources());
         }
     }
 
@@ -81,12 +85,10 @@
     public PieChartView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        mPaintPrimary = buildFillPaint(Color.parseColor("#666666"), getResources());
-
-        mPaintShadow.setColor(Color.BLACK);
-        mPaintShadow.setStyle(Style.STROKE);
-        mPaintShadow.setStrokeWidth(3f * getResources().getDisplayMetrics().density);
-        mPaintShadow.setAntiAlias(true);
+        mPaintOutline.setColor(Color.BLACK);
+        mPaintOutline.setStyle(Style.STROKE);
+        mPaintOutline.setStrokeWidth(3f * getResources().getDisplayMetrics().density);
+        mPaintOutline.setAntiAlias(true);
 
         mSideWidth = (int) (20 * getResources().getDisplayMetrics().density);
 
@@ -100,8 +102,10 @@
         paint.setStyle(Style.FILL_AND_STROKE);
         paint.setAntiAlias(true);
 
-        final int width = (int) (280 * res.getDisplayMetrics().density);
-        paint.setShader(new RadialGradient(0, 0, width, color, darken(color), TileMode.MIRROR));
+        if (FILL_GRADIENT) {
+            final int width = (int) (280 * res.getDisplayMetrics().density);
+            paint.setShader(new RadialGradient(0, 0, width, color, darken(color), TileMode.MIRROR));
+        }
 
         return paint;
     }
@@ -120,6 +124,13 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final float centerX = getWidth() / 2;
+        final float centerY = getHeight() / 2;
+
+        mMatrix.reset();
+        mMatrix.postScale(0.665f, 0.95f, centerX, centerY);
+        mMatrix.postRotate(-40, centerX, centerY);
+
         generatePath();
     }
 
@@ -128,14 +139,15 @@
 
         long total = 0;
         for (Slice slice : mSlices) {
-            slice.pathPrimary.reset();
-            slice.pathShadow.reset();
+            slice.path.reset();
+            slice.pathSide.reset();
+            slice.pathOutline.reset();
             total += slice.value;
         }
 
         mPathSide.reset();
-        mPathSideShadow.reset();
-        mPathShadow.reset();
+        mPathSideOutline.reset();
+        mPathOutline.reset();
 
         // bail when not enough stats to render
         if (total == 0) {
@@ -147,23 +159,56 @@
         final int height = getHeight();
 
         final RectF rect = new RectF(0, 0, width, height);
+        final RectF rectSide = new RectF();
+        rectSide.set(rect);
+        rectSide.offset(-mSideWidth, 0);
 
-        mPathSide.addOval(rect, Direction.CW);
-        mPathSideShadow.addOval(rect, Direction.CW);
-        mPathShadow.addOval(rect, Direction.CW);
+        mPathSide.addOval(rectSide, Direction.CW);
+        mPathSideOutline.addOval(rectSide, Direction.CW);
+        mPathOutline.addOval(rect, Direction.CW);
 
         int startAngle = mOriginAngle;
         for (Slice slice : mSlices) {
             final int sweepAngle = (int) (slice.value * 360 / total);
+            final int endAngle = startAngle + sweepAngle;
 
-            slice.pathPrimary.moveTo(rect.centerX(), rect.centerY());
-            slice.pathPrimary.arcTo(rect, startAngle, sweepAngle);
-            slice.pathPrimary.lineTo(rect.centerX(), rect.centerY());
+            final float startAngleMod = startAngle % 360;
+            final float endAngleMod = endAngle % 360;
+            final boolean startSideVisible = startAngleMod > 90 && startAngleMod < 270;
+            final boolean endSideVisible = endAngleMod > 90 && endAngleMod < 270;
 
-            slice.pathShadow.moveTo(rect.centerX(), rect.centerY());
-            slice.pathShadow.arcTo(rect, startAngle, 0);
-            slice.pathShadow.moveTo(rect.centerX(), rect.centerY());
-            slice.pathShadow.arcTo(rect, startAngle + sweepAngle, 0);
+            // draw slice
+            slice.path.moveTo(rect.centerX(), rect.centerY());
+            slice.path.arcTo(rect, startAngle, sweepAngle);
+            slice.path.lineTo(rect.centerX(), rect.centerY());
+
+            if (startSideVisible || endSideVisible) {
+
+                // when start is beyond horizon, push until visible
+                final float startAngleSide = startSideVisible ? startAngle : 450;
+                final float endAngleSide = endSideVisible ? endAngle : 270;
+                final float sweepAngleSide = endAngleSide - startAngleSide;
+
+                // draw slice side
+                slice.pathSide.moveTo(rect.centerX(), rect.centerY());
+                slice.pathSide.arcTo(rect, startAngleSide, 0);
+                slice.pathSide.rLineTo(-mSideWidth, 0);
+                slice.pathSide.arcTo(rectSide, startAngleSide, sweepAngleSide);
+                slice.pathSide.rLineTo(mSideWidth, 0);
+                slice.pathSide.arcTo(rect, endAngleSide, -sweepAngleSide);
+            }
+
+            // draw slice outline
+            slice.pathOutline.moveTo(rect.centerX(), rect.centerY());
+            slice.pathOutline.arcTo(rect, startAngle, 0);
+            if (startSideVisible) {
+                slice.pathOutline.rLineTo(-mSideWidth, 0);
+            }
+            slice.pathOutline.moveTo(rect.centerX(), rect.centerY());
+            slice.pathOutline.arcTo(rect, startAngle + sweepAngle, 0);
+            if (endSideVisible) {
+                slice.pathOutline.rLineTo(-mSideWidth, 0);
+            }
 
             startAngle += sweepAngle;
         }
@@ -174,21 +219,18 @@
     @Override
     protected void onDraw(Canvas canvas) {
 
-        canvas.translate(getWidth() * 0.25f, getHeight() * -0.05f);
-        canvas.rotate(-40, getWidth() * 0.5f, getHeight());
-        canvas.scale(0.7f, 1.0f, getWidth(), getHeight());
-
-        canvas.save();
-        canvas.translate(-mSideWidth, 0);
-        canvas.drawPath(mPathSide, mPaintPrimary);
-        canvas.drawPath(mPathSideShadow, mPaintShadow);
-        canvas.restore();
+        canvas.concat(mMatrix);
 
         for (Slice slice : mSlices) {
-            canvas.drawPath(slice.pathPrimary, slice.paintPrimary);
-            canvas.drawPath(slice.pathShadow, mPaintShadow);
+            canvas.drawPath(slice.pathSide, slice.paint);
         }
-        canvas.drawPath(mPathShadow, mPaintShadow);
+        canvas.drawPath(mPathSideOutline, mPaintOutline);
+
+        for (Slice slice : mSlices) {
+            canvas.drawPath(slice.path, slice.paint);
+            canvas.drawPath(slice.pathOutline, mPaintOutline);
+        }
+        canvas.drawPath(mPathOutline, mPaintOutline);
     }
 
     public static int darken(int color) {