Proc stats UI improvements.

Now have a chart showing the memory state, and text showing the
current memory state.  Trying to do better at picking the application
to blame for a process hosting multiple apps.  Start of infrastructure
for more detailed reporting.

Change-Id: I93ca7ecf2fd0bc01e3be8d28b80212ac78fe7607
diff --git a/res/layout/preference_linearcolor.xml b/res/layout/preference_linearcolor.xml
new file mode 100644
index 0000000..bbaf7a1
--- /dev/null
+++ b/res/layout/preference_linearcolor.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<com.android.settings.applications.LinearColorBar
+        xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:id="@+android:id/linear_color_bar"
+    android:paddingEnd="?android:attr/scrollbarSize"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:shadowRadius="4"
+    android:shadowColor="?android:attr/colorBackground"
+    android:shadowDx="2"
+    android:shadowDy="2">
+</com.android.settings.applications.LinearColorBar>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 72a6b15..4498115 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1149,4 +1149,16 @@
         <item>Always allow</item>
     </string-array>
 
+    <!-- [CHAR LIMIT=30] Labels for memory states -->
+    <string-array name="ram_states">
+        <!-- Normal desired memory state. -->
+        <item>normal</item>
+        <!-- Moderate memory state, not as good as normal. -->
+        <item>moderate</item>
+        <!-- Memory is running low. -->
+        <item>low</item>
+        <!-- Memory is critical. -->
+        <item>critical</item>
+    </string-array>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b787c8d..cdf0243 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3557,7 +3557,10 @@
     <!-- [CHAR LIMIT=NONE] Label for amount of memory use -->
     <string name="app_memory_use">Memory use</string>
     <!-- [CHAR LIMIT=NONE] Label for process stats, duration of time the stats are over -->
-    <string name="process_stats_total_duration">Over <xliff:g id="time">%1$s</xliff:g></string>
+    <string name="process_stats_total_duration">Stats over <xliff:g id="time">%1$s</xliff:g></string>
+    <!-- [CHAR LIMIT=NONE] Label for process stats, duration of time the stats are over -->
+    <string name="process_stats_memory_status">Device memory is currently
+        <xliff:g id="memstate">%1$s</xliff:g></string>
 
     <!-- Voice input/output settings --><skip />
     <!-- Title of setting on main settings screen. This item will take the user to the screen to tweak settings related to speech functionality -->
diff --git a/src/com/android/settings/applications/LinearColorBar.java b/src/com/android/settings/applications/LinearColorBar.java
index 74fb02e..8343595 100644
--- a/src/com/android/settings/applications/LinearColorBar.java
+++ b/src/com/android/settings/applications/LinearColorBar.java
@@ -23,6 +23,11 @@
     private float mYellowRatio;
     private float mGreenRatio;
 
+    private int mLeftColor = LEFT_COLOR;
+    private int mMiddleColor = MIDDLE_COLOR;
+    private int mRightColor = RIGHT_COLOR;
+
+    private boolean mShowIndicator = true;
     private boolean mShowingGreen;
 
     final Rect mRect = new Rect();
@@ -57,6 +62,20 @@
         invalidate();
     }
 
+    public void setColors(int red, int yellow, int green) {
+        mLeftColor = red;
+        mMiddleColor = yellow;
+        mRightColor = green;
+        updateIndicator();
+        invalidate();
+    }
+
+    public void setShowIndicator(boolean showIndicator) {
+        mShowIndicator = showIndicator;
+        updateIndicator();
+        invalidate();
+    }
+
     public void setShowingGreen(boolean showingGreen) {
         if (mShowingGreen != showingGreen) {
             mShowingGreen = showingGreen;
@@ -70,12 +89,15 @@
         if (off < 0) off = 0;
         mRect.top = off;
         mRect.bottom = getHeight();
+        if (!mShowIndicator) {
+            return;
+        }
         if (mShowingGreen) {
             mColorGradientPaint.setShader(new LinearGradient(
-                    0, 0, 0, off-2, RIGHT_COLOR&0xffffff, RIGHT_COLOR, Shader.TileMode.CLAMP));
+                    0, 0, 0, off-2, mRightColor &0xffffff, mRightColor, Shader.TileMode.CLAMP));
         } else {
             mColorGradientPaint.setShader(new LinearGradient(
-                    0, 0, 0, off-2, MIDDLE_COLOR&0xffffff, MIDDLE_COLOR, Shader.TileMode.CLAMP));
+                    0, 0, 0, off-2, mMiddleColor&0xffffff, mMiddleColor, Shader.TileMode.CLAMP));
         }
         mEdgeGradientPaint.setShader(new LinearGradient(
                 0, 0, 0, off/2, 0x00a0a0a0, 0xffa0a0a0, Shader.TileMode.CLAMP));
@@ -111,7 +133,7 @@
         if (mLastInterestingLeft != indicatorLeft || mLastInterestingRight != indicatorRight) {
             mColorPath.reset();
             mEdgePath.reset();
-            if (indicatorLeft < indicatorRight) {
+            if (mShowIndicator && indicatorLeft < indicatorRight) {
                 final int midTopY = mRect.top;
                 final int midBottomY = 0;
                 final int xoff = 2;
@@ -146,7 +168,7 @@
         if (left < right) {
             mRect.left = left;
             mRect.right = right;
-            mPaint.setColor(LEFT_COLOR);
+            mPaint.setColor(mLeftColor);
             canvas.drawRect(mRect, mPaint);
             width -= (right-left);
             left = right;
@@ -157,7 +179,7 @@
         if (left < right) {
             mRect.left = left;
             mRect.right = right;
-            mPaint.setColor(MIDDLE_COLOR);
+            mPaint.setColor(mMiddleColor);
             canvas.drawRect(mRect, mPaint);
             width -= (right-left);
             left = right;
@@ -168,7 +190,7 @@
         if (left < right) {
             mRect.left = left;
             mRect.right = right;
-            mPaint.setColor(RIGHT_COLOR);
+            mPaint.setColor(mRightColor);
             canvas.drawRect(mRect, mPaint);
         }
     }
diff --git a/src/com/android/settings/applications/LinearColorPreference.java b/src/com/android/settings/applications/LinearColorPreference.java
new file mode 100644
index 0000000..6d9a399
--- /dev/null
+++ b/src/com/android/settings/applications/LinearColorPreference.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.view.View;
+import com.android.settings.R;
+
+public class LinearColorPreference extends Preference {
+    float mRedRatio;
+    float mYellowRatio;
+    float mGreenRatio;
+
+    public LinearColorPreference(Context context) {
+        super(context);
+        setLayoutResource(R.layout.preference_linearcolor);
+    }
+
+    public void setRatios(float red, float yellow, float green) {
+        mRedRatio = red;
+        mYellowRatio = yellow;
+        mGreenRatio = green;
+        notifyChanged();
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        LinearColorBar colors = (LinearColorBar)view.findViewById(
+                R.id.linear_color_bar);
+        colors.setShowIndicator(false);
+        colors.setColors(0xffcc3000, 0xffcccc00, 0xff00cc30);
+        colors.setRatios(mRedRatio, mYellowRatio, mGreenRatio);
+    }
+}
diff --git a/src/com/android/settings/applications/ProcStatsEntry.java b/src/com/android/settings/applications/ProcStatsEntry.java
index dec283f..e180205 100644
--- a/src/com/android/settings/applications/ProcStatsEntry.java
+++ b/src/com/android/settings/applications/ProcStatsEntry.java
@@ -18,7 +18,9 @@
 
 import com.android.internal.app.ProcessStats;
 
-public class ProcStatsEntry {
+import java.util.ArrayList;
+
+public final class ProcStatsEntry {
     final String mPackage;
     final int mUid;
     final String mName;
@@ -27,7 +29,10 @@
     final long mAvgPss;
     final long mWeight;
 
-    ProcStatsEntry(ProcessStats.ProcessState proc, ProcessStats.ProcessDataCollection tmpTotals) {
+    ArrayList<Service> mServices;
+
+    public ProcStatsEntry(ProcessStats.ProcessState proc,
+            ProcessStats.ProcessDataCollection tmpTotals) {
         ProcessStats.computeProcessData(proc, tmpTotals, 0);
         mPackage = proc.mPackage;
         mUid = proc.mUid;
@@ -37,4 +42,35 @@
         mAvgPss = tmpTotals.avgPss;
         mWeight = mDuration * mAvgPss;
     }
+
+    public void addServices(ProcessStats.PackageState pkgState) {
+        for (int isvc=0, NSVC=pkgState.mServices.size(); isvc<NSVC; isvc++) {
+            ProcessStats.ServiceState svc = pkgState.mServices.valueAt(isvc);
+            // XXX can't tell what process it is in!
+            if (mServices == null) {
+                mServices = new ArrayList<Service>();
+            }
+            mServices.add(new Service(svc));
+        }
+    }
+
+    public static final class Service {
+        final String mPackage;
+        final String mName;
+        final long mDuration;
+
+        public Service(ProcessStats.ServiceState service) {
+            mPackage = service.mPackage;
+            mName = service.mName;
+            mDuration = ProcessStats.dumpSingleServiceTime(null, null, service,
+                    ProcessStats.ServiceState.SERVICE_STARTED,
+                    ProcessStats.STATE_NOTHING, 0, 0)
+                + ProcessStats.dumpSingleServiceTime(null, null, service,
+                    ProcessStats.ServiceState.SERVICE_BOUND,
+                    ProcessStats.STATE_NOTHING, 0, 0)
+                + ProcessStats.dumpSingleServiceTime(null, null, service,
+                    ProcessStats.ServiceState.SERVICE_EXEC,
+                    ProcessStats.STATE_NOTHING, 0, 0);
+        }
+    }
 }
diff --git a/src/com/android/settings/applications/ProcessStatsUi.java b/src/com/android/settings/applications/ProcessStatsUi.java
index 3632288..ec49e0d 100644
--- a/src/com/android/settings/applications/ProcessStatsUi.java
+++ b/src/com/android/settings/applications/ProcessStatsUi.java
@@ -33,6 +33,7 @@
 import android.preference.PreferenceScreen;
 import android.text.format.DateFormat;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -60,11 +61,24 @@
 
     static final int MAX_ITEMS_TO_LIST = 20;
 
+    final static Comparator<ProcStatsEntry> sEntryCompare = new Comparator<ProcStatsEntry>() {
+        @Override
+        public int compare(ProcStatsEntry lhs, ProcStatsEntry rhs) {
+            if (lhs.mWeight < rhs.mWeight) {
+                return 1;
+            } else if (lhs.mWeight > rhs.mWeight) {
+                return -1;
+            }
+            return 0;
+        }
+    };
+
     private static ProcessStats sStatsXfer;
 
     IProcessStats mProcessStats;
     UserManager mUm;
     ProcessStats mStats;
+    int mMemState;
 
     private PreferenceGroup mAppListGroup;
     private Preference mMemStatusPref;
@@ -169,8 +183,17 @@
         mAppListGroup.addPreference(mMemStatusPref);
         String durationString = Utils.formatElapsedTime(getActivity(),
                 mStats.mTimePeriodEndRealtime-mStats.mTimePeriodStartRealtime);
+        CharSequence memString;
+        CharSequence[] memStates = getResources().getTextArray(R.array.ram_states);
+        if (mMemState >= 0 && mMemState < memStates.length) {
+            memString = memStates[mMemState];
+        } else {
+            memString = "?";
+        }
         mMemStatusPref.setTitle(getActivity().getString(R.string.process_stats_total_duration,
                 durationString));
+        mMemStatusPref.setSummary(getActivity().getString(R.string.process_stats_memory_status,
+                        memString));
         /*
         mMemStatusPref.setTitle(DateFormat.format(DateFormat.getBestDateTimePattern(
                 getActivity().getResources().getConfiguration().locale,
@@ -188,30 +211,47 @@
 
         long now = SystemClock.uptimeMillis();
 
+        final PackageManager pm = getActivity().getPackageManager();
+
         mTotalTime = ProcessStats.dumpSingleTime(null, null, mStats.mMemFactorDurations,
                 mStats.mMemFactor, mStats.mStartTime, now);
 
+        LinearColorPreference colors = new LinearColorPreference(getActivity());
+        colors.setOrder(-1);
+        mAppListGroup.addPreference(colors);
+
+        long[] memTimes = new long[ProcessStats.ADJ_MEM_FACTOR_COUNT];
+        for (int iscreen=0; iscreen<ProcessStats.ADJ_COUNT; iscreen+=ProcessStats.ADJ_SCREEN_MOD) {
+            for (int imem=0; imem<ProcessStats.ADJ_MEM_FACTOR_COUNT; imem++) {
+                int state = imem+iscreen;
+                memTimes[imem] += mStats.mMemFactorDurations[state];
+            }
+        }
+
+        colors.setRatios(memTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL] / (float)mTotalTime,
+                (memTimes[ProcessStats.ADJ_MEM_FACTOR_LOW]
+                        + memTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE]) / (float)mTotalTime,
+                memTimes[ProcessStats.ADJ_MEM_FACTOR_NORMAL] / (float)mTotalTime);
+
+        ArrayList<ProcStatsEntry> procs = new ArrayList<ProcStatsEntry>();
+
+        /*
         ArrayList<ProcessStats.ProcessState> rawProcs = mStats.collectProcessesLocked(
                 ProcessStats.ALL_SCREEN_ADJ, ProcessStats.ALL_MEM_ADJ,
                 ProcessStats.BACKGROUND_PROC_STATES, now, null);
-
-        final PackageManager pm = getActivity().getPackageManager();
-
-        ArrayList<ProcStatsEntry> procs = new ArrayList<ProcStatsEntry>();
         for (int i=0, N=(rawProcs != null ? rawProcs.size() : 0); i<N; i++) {
             procs.add(new ProcStatsEntry(rawProcs.get(i), totals));
         }
-        Collections.sort(procs, new Comparator<ProcStatsEntry>() {
-            @Override
-            public int compare(ProcStatsEntry lhs, ProcStatsEntry rhs) {
-                if (lhs.mWeight < rhs.mWeight) {
-                    return 1;
-                } else if (lhs.mWeight > rhs.mWeight) {
-                    return -1;
-                }
-                return 0;
+        */
+
+        for (int ip=0, N=mStats.mProcesses.getMap().size(); ip<N; ip++) {
+            SparseArray<ProcessStats.ProcessState> uids = mStats.mProcesses.getMap().valueAt(ip);
+            for (int iu=0; iu<uids.size(); iu++) {
+                procs.add(new ProcStatsEntry(uids.valueAt(iu), totals));
             }
-        });
+        }
+
+        Collections.sort(procs, sEntryCompare);
         while (procs.size() > MAX_ITEMS_TO_LIST) {
             procs.remove(procs.size()-1);
         }
@@ -232,19 +272,56 @@
             ProcessStatsPreference pref = new ProcessStatsPreference(getActivity(), null);
             ApplicationInfo targetApp = null;
             String label = proc.mName;
+            String pkgName = null;
             if (proc.mUnique) {
+                pkgName = proc.mPackage;
+                proc.addServices(mStats.getPackageStateLocked(proc.mPackage, proc.mUid));
+            } else {
+                // See if there is one significant package that was running here.
+                ArrayList<ProcStatsEntry> subProcs = new ArrayList<ProcStatsEntry>();
+                for (int ipkg=0, NPKG=mStats.mPackages.getMap().size(); ipkg<NPKG; ipkg++) {
+                    SparseArray<ProcessStats.PackageState> uids
+                            = mStats.mPackages.getMap().valueAt(ipkg);
+                    for (int iu=0, NU=uids.size(); iu<NU; iu++) {
+                        if (uids.keyAt(iu) != proc.mUid) {
+                            continue;
+                        }
+                        ProcessStats.PackageState pkgState = uids.valueAt(iu);
+                        boolean match = false;
+                        for (int iproc=0, NPROC=pkgState.mProcesses.size(); iproc<NPROC; iproc++) {
+                            ProcessStats.ProcessState subProc =
+                                    pkgState.mProcesses.valueAt(iproc);
+                            if (subProc.mName.equals(proc.mName)) {
+                                match = true;
+                                subProcs.add(new ProcStatsEntry(subProc, totals));
+                            }
+                        }
+                        if (match) {
+                            proc.addServices(mStats.getPackageStateLocked(proc.mPackage,
+                                    proc.mUid));
+                        }
+                    }
+                }
+                if ( subProcs.size() > 1) {
+                    Collections.sort(subProcs, sEntryCompare);
+                    if (subProcs.get(0).mWeight > (subProcs.get(1).mWeight*3)) {
+                        pkgName = subProcs.get(0).mPackage;
+                    }
+                }
+            }
+            if (pkgName != null) {
                 // Only one app associated with this process.
                 try {
-                    targetApp = pm.getApplicationInfo(proc.mPackage,
+                    targetApp = pm.getApplicationInfo(pkgName,
                             PackageManager.GET_DISABLED_COMPONENTS |
                             PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
                             PackageManager.GET_UNINSTALLED_PACKAGES);
                     String name = targetApp.loadLabel(pm).toString();
-                    if (proc.mName.equals(proc.mPackage)) {
+                    if (proc.mName.equals(pkgName)) {
                         label = name;
                     } else {
-                        if (proc.mName.startsWith(proc.mPackage)) {
-                            int off = proc.mPackage.length();
+                        if (proc.mName.startsWith(pkgName)) {
+                            int off = pkgName.length();
                             if (proc.mName.length() > off) {
                                 off++;
                             }
@@ -258,15 +335,15 @@
             }
             if (targetApp == null) {
                 String[] packages = pm.getPackagesForUid(proc.mUid);
-                for (String pkgName : packages) {
+                for (String curPkg : packages) {
                     try {
-                        final PackageInfo pi = pm.getPackageInfo(pkgName,
+                        final PackageInfo pi = pm.getPackageInfo(curPkg,
                                 PackageManager.GET_DISABLED_COMPONENTS |
                                 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
                                 PackageManager.GET_UNINSTALLED_PACKAGES);
                         if (pi.sharedUserLabel != 0) {
                             targetApp = pi.applicationInfo;
-                            final CharSequence nm = pm.getText(pkgName,
+                            final CharSequence nm = pm.getText(curPkg,
                                     pi.sharedUserLabel, pi.applicationInfo);
                             if (nm != null) {
                                 label = nm.toString() + " (" + proc.mName + ")";
@@ -293,6 +370,7 @@
 
     private void load() {
         try {
+            mMemState = mProcessStats.getCurrentMemoryState();
             ArrayList<ParcelFileDescriptor> fds = new ArrayList<ParcelFileDescriptor>();
             byte[] data = mProcessStats.getCurrentStats(fds);
             Parcel parcel = Parcel.obtain();
@@ -300,7 +378,7 @@
             parcel.setDataPosition(0);
             mStats = ProcessStats.CREATOR.createFromParcel(parcel);
             int i = fds.size()-1;
-            while (i > 0 && (mStats.mTimePeriodEndRealtime-mStats.mTimePeriodStartRealtime)
+            while (i >= 0 && (mStats.mTimePeriodEndRealtime-mStats.mTimePeriodStartRealtime)
                     < (24*60*60*1000)) {
                 Log.i(TAG, "Not enough data, loading next file @ " + i);
                 ProcessStats stats = new ProcessStats(false);