Data usage app labels and system clustering.

Cluster all non-applications under single "Android OS" label, and
correctly label uninstalled applications.  Match API changes that now
return correct historical usage around current time.

Bug: 4975314, 4691901
Change-Id: Icec1c54ff3404e1525878516b0a438f757dcb758
diff --git a/res/values/strings.xml b/res/values/strings.xml
index af46595..16eeadf 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3427,6 +3427,9 @@
     <!-- Label displaying current network data usage limit threshold. [CHAR LIMIT=18] -->
     <string name="data_usage_sweep_limit"><font size="21"><xliff:g id="number" example="128">^1</xliff:g></font> <font size="9"><xliff:g id="unit" example="KB">^2</xliff:g></font>\n<font size="12">limit</font></string>
 
+    <!-- Title of data usage item that represents all uninstalled applications. [CHAR LIMIT=48] -->
+    <string name="data_usage_uninstalled_apps">Removed apps</string>
+
     <!-- Button at the bottom of the CryptKeeper screen to make an emergency call. -->
     <string name="cryptkeeper_emergency_call">Emergency call</string>
     <!-- Button at the bottom of the CryptKeeper screen that lets the user return to a call -->
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 4edda7d..739ed58 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -48,6 +48,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
@@ -56,6 +57,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.TrafficStats;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -105,7 +107,6 @@
 import com.google.android.collect.Lists;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Locale;
 
@@ -737,27 +738,6 @@
     }
 
     /**
-     * Return full time bounds (earliest and latest time recorded) of the given
-     * {@link NetworkStatsHistory}.
-     */
-    public static long[] getHistoryBounds(NetworkStatsHistory history) {
-        final long currentTime = System.currentTimeMillis();
-
-        long start = currentTime;
-        long end = currentTime;
-
-        NetworkStatsHistory.Entry entry = null;
-        if (history.size() > 0) {
-            entry = history.getValues(0, entry);
-            start = entry.bucketStart;
-            entry = history.getValues(history.size() - 1, entry);
-            end = entry.bucketStart + entry.bucketDuration;
-        }
-
-        return new long[] { start, end };
-    }
-
-    /**
      * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
      * and available {@link NetworkStatsHistory} data. Always selects the newest
      * item, updating the inspection range on {@link #mChart}.
@@ -766,11 +746,15 @@
         mCycleAdapter.clear();
 
         final Context context = mCycleSpinner.getContext();
+        long historyStart = mHistory.getStart();
+        long historyEnd = mHistory.getEnd();
 
-        final long[] bounds = getHistoryBounds(mHistory);
-        final long historyStart = bounds[0];
-        final long historyEnd = bounds[1];
+        if (historyStart == Long.MAX_VALUE || historyEnd == Long.MIN_VALUE) {
+            historyStart = System.currentTimeMillis();
+            historyEnd = System.currentTimeMillis();
+        }
 
+        boolean hasCycles = false;
         if (policy != null) {
             // find the next cycle boundary
             long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
@@ -784,23 +768,24 @@
                         + historyStart);
                 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
                 cycleEnd = cycleStart;
+                hasCycles = true;
 
                 // TODO: remove this guard once we have better testing
                 if (guardCount++ > 50) {
-                    Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
-                            + " and policy=" + policy);
+                    Log.wtf(TAG, "stuck generating ranges for historyStart=" + historyStart
+                            + ", historyEnd=" + historyEnd + " and policy=" + policy);
                 }
             }
 
             // one last cycle entry to modify policy cycle day
             mCycleAdapter.setChangePossible(true);
+        }
 
-        } else {
-            // no valid cycle; show all data
+        if (!hasCycles) {
+            // no valid cycles; show all data
             // TODO: offer simple ranges like "last week" etc
             mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
             mCycleAdapter.setChangePossible(false);
-
         }
 
         // force pick the current cycle (first item)
@@ -889,8 +874,7 @@
 
                 // update chart to show selected cycle, and update detail data
                 // to match updated sweep bounds.
-                final long[] bounds = getHistoryBounds(mHistory);
-                mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
+                mChart.setVisibleRange(cycle.start, cycle.end, mHistory.getEnd());
 
                 updateDetailData();
             }
@@ -911,23 +895,25 @@
     private void updateDetailData() {
         if (LOGD) Log.d(TAG, "updateDetailData()");
 
+        final long start = mChart.getInspectStart();
+        final long end = mChart.getInspectEnd();
+
         if (isAppDetailMode()) {
             if (mDetailHistory != null) {
                 final Context context = mChart.getContext();
-                final long[] range = mChart.getInspectRange();
-                final long[] total = mDetailHistory.getTotalData(range[0], range[1], null);
-                final long totalCombined = total[0] + total[1];
-                mAppSubtitle.setText(Formatter.formatFileSize(context, totalCombined));
+                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));
             }
 
             getLoaderManager().destroyLoader(LOADER_SUMMARY);
 
         } else {
             // kick off loader for detailed stats
-            final long[] range = mChart.getInspectRange();
             getLoaderManager().restartLoader(LOADER_SUMMARY,
-                    SummaryForAllUidLoader.buildArgs(mTemplate, range[0], range[1]),
-                    mSummaryForAllUid);
+                    SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
 
         }
     }
@@ -1085,13 +1071,27 @@
             mItems.clear();
 
             if (stats != null) {
+                final AppUsageItem systemItem = new AppUsageItem();
+                systemItem.uid = android.os.Process.SYSTEM_UID;
+
                 NetworkStats.Entry entry = null;
                 for (int i = 0; i < stats.size(); i++) {
                     entry = stats.getValues(i, entry);
-                    final AppUsageItem item = new AppUsageItem();
-                    item.uid = entry.uid;
-                    item.total = entry.rxBytes + entry.txBytes;
-                    mItems.add(item);
+
+                    final boolean isApp = entry.uid >= android.os.Process.FIRST_APPLICATION_UID
+                            && entry.uid <= android.os.Process.LAST_APPLICATION_UID;
+                    if (isApp || entry.uid == TrafficStats.UID_REMOVED) {
+                        final AppUsageItem item = new AppUsageItem();
+                        item.uid = entry.uid;
+                        item.total = entry.rxBytes + entry.txBytes;
+                        mItems.add(item);
+                    } else {
+                        systemItem.total += entry.rxBytes + entry.txBytes;
+                    }
+                }
+
+                if (systemItem.total > 0) {
+                    mItems.add(systemItem);
                 }
             }
 
@@ -1122,13 +1122,12 @@
             }
 
             final Context context = parent.getContext();
-            final PackageManager pm = context.getPackageManager();
 
             final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
             final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
 
             final AppUsageItem item = mItems.get(position);
-            text1.setText(resolveLabelForUid(pm, item.uid));
+            text1.setText(resolveLabelForUid(context, item.uid));
             text2.setText(Formatter.formatFileSize(context, item.total));
 
             return convertView;
@@ -1470,7 +1469,19 @@
     /**
      * Resolve best descriptive label for the given UID.
      */
-    public static CharSequence resolveLabelForUid(PackageManager pm, int uid) {
+    public static CharSequence resolveLabelForUid(Context context, int uid) {
+        final Resources res = context.getResources();
+        final PackageManager pm = context.getPackageManager();
+
+        // handle special case labels
+        switch (uid) {
+            case android.os.Process.SYSTEM_UID:
+                return res.getText(R.string.process_kernel_label);
+            case TrafficStats.UID_REMOVED:
+                return res.getText(R.string.data_usage_uninstalled_apps);
+        }
+
+        // otherwise fall back to using packagemanager labels
         final String[] packageNames = pm.getPackagesForUid(uid);
         final int length = packageNames != null ? packageNames.length : 0;
 
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java
index 88f0a19..affede0 100644
--- a/src/com/android/settings/widget/DataUsageChartView.java
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -206,14 +206,12 @@
         }
     }
 
-    /**
-     * Return current inspection range (start and end time) based on internal
-     * {@link ChartSweepView} positions.
-     */
-    public long[] getInspectRange() {
-        final long start = mSweepLeft.getValue();
-        final long end = mSweepRight.getValue();
-        return new long[] { start, end };
+    public long getInspectStart() {
+        return mSweepLeft.getValue();
+    }
+
+    public long getInspectEnd() {
+        return mSweepRight.getValue();
     }
 
     public long getWarningBytes() {