Data usage app bars, draw estimated cycle usage.

Show application list with normalized percentage data usage as
horizontal bar chart.  Draw estimated usage for remainder of cycle
to reinforce log scale, and break out received/sent when showing app
details.

Bug: 5038591, 4948713, 4818025
Change-Id: Ib71e73d4f692adc7fe8ae87edd0cec2a8f3976d3
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index f7bb32f..1f433fb 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -97,6 +97,7 @@
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.NumberPicker;
+import android.widget.ProgressBar;
 import android.widget.Spinner;
 import android.widget.Switch;
 import android.widget.TabHost;
@@ -946,8 +947,11 @@
                 final long now = System.currentTimeMillis();
                 final NetworkStatsHistory.Entry entry = mDetailHistory.getValues(
                         start, end, now, null);
-                final long total = entry.rxBytes + entry.txBytes;
-                mAppSubtitle.setText(Formatter.formatFileSize(context, total));
+
+                mAppSubtitle.setText(
+                        getString(R.string.data_usage_received_sent,
+                                Formatter.formatFileSize(context, entry.rxBytes),
+                                Formatter.formatFileSize(context, entry.txBytes)));
             }
 
             getLoaderManager().destroyLoader(LOADER_SUMMARY);
@@ -1110,6 +1114,7 @@
      */
     public static class DataUsageAdapter extends BaseAdapter {
         private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
+        private long mLargest;
 
         /**
          * Bind the given {@link NetworkStats}, or {@code null} to clear list.
@@ -1143,6 +1148,7 @@
             }
 
             Collections.sort(mItems);
+            mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0;
             notifyDataSetChanged();
         }
 
@@ -1170,12 +1176,17 @@
 
             final Context context = parent.getContext();
 
-            final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
-            final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
+            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
+            final ProgressBar progress = (ProgressBar) convertView.findViewById(
+                    android.R.id.progress);
 
             final AppUsageItem item = mItems.get(position);
-            text1.setText(resolveLabelForUid(context, item.uid));
-            text2.setText(Formatter.formatFileSize(context, item.total));
+            title.setText(resolveLabelForUid(context, item.uid));
+            summary.setText(Formatter.formatFileSize(context, item.total));
+
+            final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
+            progress.setProgress(percentTotal);
 
             return convertView;
         }
@@ -1187,7 +1198,7 @@
      * {@link DataUsageSummary}.
      */
     public static class AppDetailsFragment extends Fragment {
-        public static final String EXTRA_UID = "uid";
+        private static final String EXTRA_UID = "uid";
 
         public static void show(DataUsageSummary parent, int uid) {
             final Bundle args = new Bundle();
@@ -1225,8 +1236,8 @@
      * {@link NetworkPolicy#limitBytes}.
      */
     public static class ConfirmLimitFragment extends DialogFragment {
-        public static final String EXTRA_MESSAGE_ID = "messageId";
-        public static final String EXTRA_LIMIT_BYTES = "limitBytes";
+        private static final String EXTRA_MESSAGE_ID = "messageId";
+        private static final String EXTRA_LIMIT_BYTES = "limitBytes";
 
         public static void show(DataUsageSummary parent) {
             final Bundle args = new Bundle();
@@ -1278,7 +1289,7 @@
      * Dialog to edit {@link NetworkPolicy#cycleDay}.
      */
     public static class CycleEditorFragment extends DialogFragment {
-        public static final String EXTRA_CYCLE_DAY = "cycleDay";
+        private static final String EXTRA_CYCLE_DAY = "cycleDay";
 
         public static void show(DataUsageSummary parent) {
             final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
@@ -1331,7 +1342,7 @@
      * and giving the user an option to bypass.
      */
     public static class PolicyLimitFragment extends DialogFragment {
-        public static final String EXTRA_TITLE_ID = "titleId";
+        private static final String EXTRA_TITLE_ID = "titleId";
 
         public static void show(DataUsageSummary parent) {
             final Bundle args = new Bundle();
diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java
index 10d8976..51c3c2c 100644
--- a/src/com/android/settings/widget/ChartNetworkSeriesView.java
+++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java
@@ -16,6 +16,9 @@
 
 package com.android.settings.widget;
 
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
@@ -46,11 +49,13 @@
     private Paint mPaintStroke;
     private Paint mPaintFill;
     private Paint mPaintFillSecondary;
+    private Paint mPaintEstimate;
 
     private NetworkStatsHistory mStats;
 
     private Path mPathStroke;
     private Path mPathFill;
+    private Path mPathEstimate;
 
     private long mPrimaryLeft;
     private long mPrimaryRight;
@@ -81,6 +86,7 @@
 
         mPathStroke = new Path();
         mPathFill = new Path();
+        mPathEstimate = new Path();
     }
 
     void init(ChartAxis horiz, ChartAxis vert) {
@@ -104,6 +110,12 @@
         mPaintFillSecondary.setColor(fillSecondary);
         mPaintFillSecondary.setStyle(Style.FILL);
         mPaintFillSecondary.setAntiAlias(true);
+
+        mPaintEstimate = new Paint();
+        mPaintEstimate.setStrokeWidth(3.0f);
+        mPaintEstimate.setColor(fillSecondary);
+        mPaintEstimate.setStyle(Style.STROKE);
+        mPaintEstimate.setAntiAlias(true);
     }
 
     public void bindNetworkStats(NetworkStatsHistory stats) {
@@ -111,6 +123,7 @@
 
         mPathStroke.reset();
         mPathFill.reset();
+        mPathEstimate.reset();
         invalidate();
     }
 
@@ -138,6 +151,7 @@
 
         mPathStroke.reset();
         mPathFill.reset();
+        mPathEstimate.reset();
 
         // bail when not enough stats to render
         if (mStats == null || mStats.size() < 2) return;
@@ -149,6 +163,7 @@
         float firstX = 0;
         float lastX = 0;
         float lastY = 0;
+        long lastTime = Long.MIN_VALUE;
 
         // TODO: count fractional data from first bucket crossing start;
         // currently it only accepts first full bucket.
@@ -159,6 +174,7 @@
         for (int i = 0; i < mStats.size(); i++) {
             entry = mStats.getValues(i, entry);
 
+            lastTime = entry.bucketStart;
             final float x = mHoriz.convertToPoint(entry.bucketStart);
             final float y = mVert.convertToPoint(totalData);
 
@@ -193,6 +209,35 @@
         // drop to bottom of graph from current location
         mPathFill.lineTo(lastX, height);
         mPathFill.lineTo(firstX, height);
+
+        // build estimated data
+        mPathEstimate.moveTo(lastX, lastY);
+
+        final long now = System.currentTimeMillis();
+        final long bucketDuration = mStats.getBucketDuration();
+
+        // long window is average over two weeks
+        entry = mStats.getValues(lastTime - WEEK_IN_MILLIS * 2, lastTime, now, entry);
+        final long longWindow = (entry.rxBytes + entry.txBytes) * bucketDuration
+                / entry.bucketDuration;
+
+        long futureTime = 0;
+        while (lastX < width) {
+            futureTime += bucketDuration;
+
+            // short window is day average last week
+            final long lastWeekTime = lastTime - WEEK_IN_MILLIS + (futureTime % WEEK_IN_MILLIS);
+            entry = mStats.getValues(lastWeekTime - DAY_IN_MILLIS, lastWeekTime, now, entry);
+            final long shortWindow = (entry.rxBytes + entry.txBytes) * bucketDuration
+                    / entry.bucketDuration;
+
+            totalData += (longWindow * 7 + shortWindow * 3) / 10;
+
+            lastX = mHoriz.convertToPoint(lastTime + futureTime);
+            final float y = mVert.convertToPoint(totalData);
+
+            mPathEstimate.lineTo(lastX, y);
+        }
     }
 
     @Override
@@ -203,6 +248,11 @@
         final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
 
         save = canvas.save();
+        canvas.clipRect(0, 0, getWidth(), getHeight());
+        canvas.drawPath(mPathEstimate, mPaintEstimate);
+        canvas.restoreToCount(save);
+
+        save = canvas.save();
         canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
         canvas.drawPath(mPathFill, mPaintFillSecondary);
         canvas.restoreToCount(save);