Merge "Data usage app bars, draw estimated cycle usage."
diff --git a/res/drawable/data_usage_bar.xml b/res/drawable/data_usage_bar.xml
new file mode 100644
index 0000000..96f1cae
--- /dev/null
+++ b/res/drawable/data_usage_bar.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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background">
+        <shape>
+            <solid android:color="#ff28262c" />
+        </shape>
+    </item>
+    <item android:id="@android:id/secondaryProgress">
+        <clip>
+            <shape>
+                <solid android:color="#c050ade5" />
+            </shape>
+        </clip>
+    </item>
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape>
+                <solid android:color="#c050ade5" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>
diff --git a/res/layout/data_usage_item.xml b/res/layout/data_usage_item.xml
index 6451e21..d709753 100644
--- a/res/layout/data_usage_item.xml
+++ b/res/layout/data_usage_item.xml
@@ -14,26 +14,34 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:minHeight="48dip"
-    android:orientation="vertical">
+    android:padding="8dip"
+    android:columnCount="2">
 
+    <!-- TODO: consider using canShrink -->
     <TextView
-        android:id="@android:id/text1"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginLeft="6dip"
-        android:layout_marginTop="6dip"
+        android:id="@android:id/title"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:layout_columnFlexibility="canStretch"
         android:textAppearance="?android:attr/textAppearanceMedium" />
 
     <TextView
-        android:id="@android:id/text2"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginLeft="6dip"
-        android:layout_marginBottom="6dip"
+        android:id="@android:id/summary"
+        android:layout_gravity="right"
+        android:layout_marginLeft="8dip"
         android:textAppearance="?android:attr/textAppearanceSmall" />
 
-</LinearLayout>
+    <ProgressBar
+        android:id="@android:id/progress"
+        android:layout_height="12dip"
+        android:layout_columnSpan="2"
+        android:layout_gravity="fill_horizontal"
+        android:layout_marginTop="4dip"
+        android:max="100"
+        android:progressDrawable="@drawable/data_usage_bar"
+        style="?android:attr/progressBarStyleHorizontal" />
+
+</GridLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b72dbae..f573181 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3475,6 +3475,8 @@
 
     <!-- Title of data usage item that represents all uninstalled applications. [CHAR LIMIT=48] -->
     <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>
 
     <!-- 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 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);