Improve process tracking.

We now keep track of when each process is running, batched
by the current memory status of the device.  In addition,
the stats are organized by package first, and then processes
associated with each package inside of that.  Finally, we
also keep track of the overall time spent in each memory
status.

This should start to actually get us to some information
we can reach some conclusions about.  The total time spent
in each memory status gives us some indication of how much
we are running while memory is low; the new package organization
batched by memory status lets us see what packages have
what processes running when memory is low.

Change-Id: I389d62d39d115a846126cf354e4c20070d8f1180
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index a5d64b2..912c465 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -1015,7 +1015,7 @@
                 Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app);
             if (app != null && app.thread != null) {
                 try {
-                    app.addPackage(r.appInfo.packageName);
+                    app.addPackage(r.appInfo.packageName, mAm.mProcessTracker);
                     realStartServiceLocked(r, app);
                     return null;
                 } catch (RemoteException e) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 38fe5e6..2124095 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -817,10 +817,10 @@
     int mNumNonCachedProcs = 0;
 
     /**
-     * Keep track of the number of cached procs, to balance oom adj
+     * Keep track of the number of cached hidden procs, to balance oom adj
      * distribution between those and empty procs.
      */
-    int mNumCachedProcs = 0;
+    int mNumCachedHiddenProcs = 0;
 
     /**
      * Keep track of the number of service processes we last found, to
@@ -2151,7 +2151,7 @@
                 // come up (we have a pid but not yet its thread), so keep it.
                 if (DEBUG_PROCESSES) Slog.v(TAG, "App already running: " + app);
                 // If this is a new package in the process, add the package to the list
-                app.addPackage(info.packageName);
+                app.addPackage(info.packageName, mProcessTracker);
                 return app;
             }
 
@@ -2206,7 +2206,7 @@
             }
         } else {
             // If this is a new package in the process, add the package to the list
-            app.addPackage(info.packageName);
+            app.addPackage(info.packageName, mProcessTracker);
         }
 
         // If the system is not ready yet, then hold off on starting this
@@ -3039,10 +3039,8 @@
                         proc = p;
                         break;
                     }
-                    for (String str : p.pkgList) {
-                        if (str.equals(packageName)) {
-                            proc = p;
-                        }
+                    if (p.pkgList.containsKey(packageName)) {
+                        proc = p;
                     }
                 }
             }
@@ -4014,7 +4012,7 @@
                     if (userId != UserHandle.USER_ALL && app.userId != userId) {
                         continue;
                     }
-                    if (!app.pkgList.contains(packageName)) {
+                    if (!app.pkgList.containsKey(packageName)) {
                         continue;
                     }
                 }
@@ -6220,7 +6218,7 @@
                     if (proc.userId != tr.userId) {
                         continue;
                     }
-                    if (!proc.pkgList.contains(pkg)) {
+                    if (!proc.pkgList.containsKey(pkg)) {
                         continue;
                     }
                     procs.add(proc);
@@ -6577,7 +6575,7 @@
                 if (DEBUG_MU)
                     Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid);
                 app.pubProviders.put(cpi.name, cpr);
-                app.addPackage(cpi.applicationInfo.packageName);
+                app.addPackage(cpi.applicationInfo.packageName, mProcessTracker);
                 ensurePackageDexOpt(cpi.applicationInfo.packageName);
             }
         }
@@ -7316,7 +7314,7 @@
             ps = stats.getProcessStatsLocked(info.uid, proc);
         }
         return new ProcessRecord(ps, thread, info, proc, uid,
-                mProcessTracker.getStateLocked(info.processName, info.uid));
+                mProcessTracker.getProcessStateLocked(info.packageName, uid, proc));
     }
 
     final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) {
@@ -8895,7 +8893,8 @@
             int flags = process.info.flags;
             IPackageManager pm = AppGlobals.getPackageManager();
             sb.append("Flags: 0x").append(Integer.toString(flags, 16)).append("\n");
-            for (String pkg : process.pkgList) {
+            for (int ip=0; ip<process.pkgList.size(); ip++) {
+                String pkg = process.pkgList.keyAt(ip);
                 sb.append("Package: ").append(pkg);
                 try {
                     PackageInfo pi = pm.getPackageInfo(pkg, 0, UserHandle.getCallingUserId());
@@ -9630,7 +9629,7 @@
                 final int NA = procs.size();
                 for (int ia=0; ia<NA; ia++) {
                     ProcessRecord r = procs.valueAt(ia);
-                    if (dumpPackage != null && !r.pkgList.contains(dumpPackage)) {
+                    if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
                         continue;
                     }
                     if (!needSep) {
@@ -9653,7 +9652,7 @@
             boolean printed = false;
             for (int i=0; i<mIsolatedProcesses.size(); i++) {
                 ProcessRecord r = mIsolatedProcesses.valueAt(i);
-                if (dumpPackage != null && !r.pkgList.contains(dumpPackage)) {
+                if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
                     continue;
                 }
                 if (!printed) {
@@ -9685,7 +9684,7 @@
                 boolean printed = false;
                 for (int i=0; i<mPidsSelfLocked.size(); i++) {
                     ProcessRecord r = mPidsSelfLocked.valueAt(i);
-                    if (dumpPackage != null && !r.pkgList.contains(dumpPackage)) {
+                    if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
                         continue;
                     }
                     if (!printed) {
@@ -9708,7 +9707,7 @@
                     ProcessRecord r = mPidsSelfLocked.get( 
                             mForegroundProcesses.valueAt(i).pid);
                     if (dumpPackage != null && (r == null
-                            || !r.pkgList.contains(dumpPackage))) {
+                            || !r.pkgList.containsKey(dumpPackage))) {
                         continue;
                     }
                     if (!printed) {
@@ -9766,7 +9765,7 @@
                     int puid = uids.keyAt(i);
                     ProcessRecord r = mProcessNames.get(pname, puid);
                     if (dumpPackage != null && (r == null
-                            || !r.pkgList.contains(dumpPackage))) {
+                            || !r.pkgList.containsKey(dumpPackage))) {
                         continue;
                     }
                     if (!printed) {
@@ -9797,7 +9796,7 @@
                     int puid = uids.keyAt(i);
                     ProcessRecord r = mProcessNames.get(pname, puid);
                     if (dumpPackage != null && (r == null
-                            || !r.pkgList.contains(dumpPackage))) {
+                            || !r.pkgList.containsKey(dumpPackage))) {
                         continue;
                     }
                     if (!printed) {
@@ -9840,7 +9839,7 @@
             }
         }
         if (mHomeProcess != null && (dumpPackage == null
-                || mHomeProcess.pkgList.contains(dumpPackage))) {
+                || mHomeProcess.pkgList.containsKey(dumpPackage))) {
             if (needSep) {
                 pw.println();
                 needSep = false;
@@ -9848,7 +9847,7 @@
             pw.println("  mHomeProcess: " + mHomeProcess);
         }
         if (mPreviousProcess != null && (dumpPackage == null
-                || mPreviousProcess.pkgList.contains(dumpPackage))) {
+                || mPreviousProcess.pkgList.containsKey(dumpPackage))) {
             if (needSep) {
                 pw.println();
                 needSep = false;
@@ -9862,7 +9861,7 @@
             pw.println(sb);
         }
         if (mHeavyWeightProcess != null && (dumpPackage == null
-                || mHeavyWeightProcess.pkgList.contains(dumpPackage))) {
+                || mHeavyWeightProcess.pkgList.containsKey(dumpPackage))) {
             if (needSep) {
                 pw.println();
                 needSep = false;
@@ -9959,7 +9958,8 @@
                 pw.println("  mLaunchingActivity=" + getFocusedStack().mLaunchingActivity);
                 pw.println("  mAdjSeq=" + mAdjSeq + " mLruSeq=" + mLruSeq);
                 pw.println("  mNumNonCachedProcs=" + mNumNonCachedProcs
-                        + " mNumCachedProcs=" + mNumCachedProcs
+                        + " (" + mLruProcesses.size() + " total)"
+                        + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs
                         + " mNumServiceProcs=" + mNumServiceProcs
                         + " mNewNumServiceProcs=" + mNewNumServiceProcs);
             }
@@ -10463,7 +10463,7 @@
                 = new ArrayList<Pair<ProcessRecord, Integer>>(origList.size());
         for (int i=0; i<origList.size(); i++) {
             ProcessRecord r = origList.get(i);
-            if (dumpPackage != null && !r.pkgList.contains(dumpPackage)) {
+            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
                 continue;
             }
             list.add(new Pair<ProcessRecord, Integer>(origList.get(i), i));
@@ -11079,7 +11079,6 @@
 
     final void dumpProcessTracker(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (this) {
-            pw.println("Process Stats:");
             mProcessTracker.dumpLocked(fd, pw, args);
         }
     }
@@ -11787,7 +11786,7 @@
                             + ") when registering receiver " + receiver);
                 }
                 if (callerApp.info.uid != Process.SYSTEM_UID &&
-                        !callerApp.pkgList.contains(callerPackage)) {
+                        !callerApp.pkgList.containsKey(callerPackage)) {
                     throw new SecurityException("Given caller package " + callerPackage
                             + " is not running in process " + callerApp);
                 }
@@ -13899,7 +13898,7 @@
     }
 
     private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
-            int clientCachedAdj, int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) {
+            int clientCachedAdj, int emptyAdj, ProcessRecord TOP_APP, boolean doingAll, long now) {
         app.cachedAdj = cachedAdj;
         app.clientCachedAdj = clientCachedAdj;
         app.emptyAdj = emptyAdj;
@@ -13936,9 +13935,10 @@
                     TAG, "Set " + app.pid + " " + app.processName +
                     " adj " + app.curAdj + ": " + app.adjType);
                 app.setAdj = app.curAdj;
-                if (app.setAdj >= ProcessList.FOREGROUND_APP_ADJ) {
-                    app.tracker.setState(mProcessList.adjToTrackedState(app.setAdj),
-                            SystemClock.uptimeMillis());
+                app.setAdjChanged = true;
+                if (!doingAll) {
+                    app.setProcessTrackerState(TOP_APP, mProcessTracker.getMemFactor(),
+                            now, mProcessList);
                 }
             } else {
                 success = false;
@@ -13997,7 +13997,7 @@
         mAdjSeq++;
 
         boolean success = updateOomAdjLocked(app, app.cachedAdj, app.clientCachedAdj,
-                app.emptyAdj, TOP_APP, false);
+                app.emptyAdj, TOP_APP, false, SystemClock.uptimeMillis());
         final boolean nowCached = app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
             && app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ;
         if (nowCached != wasCached) {
@@ -14011,7 +14011,8 @@
     final void updateOomAdjLocked() {
         final ActivityRecord TOP_ACT = resumedAppLocked();
         final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
-        final long oldTime = SystemClock.uptimeMillis() - ProcessList.MAX_EMPTY_TIME;
+        final long now = SystemClock.uptimeMillis();
+        final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
 
         if (false) {
             RuntimeException e = new RuntimeException();
@@ -14040,7 +14041,7 @@
         // them.
         int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
                 - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2;
-        int numEmptyProcs = mLruProcesses.size()- mNumNonCachedProcs - mNumCachedProcs;
+        int numEmptyProcs = mLruProcesses.size()- mNumNonCachedProcs - mNumCachedHiddenProcs;
         if (numEmptyProcs > cachedProcessLimit) {
             // If there are more empty processes than our limit on cached
             // processes, then use the cached process limit for the factor.
@@ -14052,7 +14053,7 @@
         }
         int emptyFactor = numEmptyProcs/numSlots;
         if (emptyFactor < 1) emptyFactor = 1;
-        int cachedFactor = (mNumCachedProcs > 0 ? mNumCachedProcs : 1)/numSlots;
+        int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots;
         if (cachedFactor < 1) cachedFactor = 1;
         int stepCached = 0;
         int stepEmpty = 0;
@@ -14061,7 +14062,7 @@
         int numTrimming = 0;
 
         mNumNonCachedProcs = 0;
-        mNumCachedProcs = 0;
+        mNumCachedHiddenProcs = 0;
 
         // First update the OOM adjustment for each of the
         // application processes based on their current state.
@@ -14071,16 +14072,20 @@
         int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
         int nextEmptyAdj = curEmptyAdj+2;
         int curClientCachedAdj = curEmptyAdj;
+        boolean changed = false;
         while (i > 0) {
             i--;
             ProcessRecord app = mLruProcesses.get(i);
             //Slog.i(TAG, "OOM " + app + ": cur cached=" + curCachedAdj);
-            updateOomAdjLocked(app, curCachedAdj, curClientCachedAdj, curEmptyAdj, TOP_APP, true);
+            app.setAdjChanged = false;
+            updateOomAdjLocked(app, curCachedAdj, curClientCachedAdj, curEmptyAdj, TOP_APP,
+                    true, now);
+            changed |= app.setAdjChanged;
             if (!app.killedBackground) {
                 if (app.curRawAdj == curCachedAdj && app.hasActivities) {
                     // This process was assigned as a cached process...  step the
                     // cached level.
-                    mNumCachedProcs++;
+                    mNumCachedHiddenProcs++;
                     if (curCachedAdj != nextCachedAdj) {
                         stepCached++;
                         if (stepCached >= cachedFactor) {
@@ -14188,6 +14193,7 @@
         // are managing to keep around is less than half the maximum we desire;
         // if we are keeping a good number around, we'll let them use whatever
         // memory they want.
+        int memFactor = ProcessTracker.STATE_MEM_FACTOR_NORMAL_ADJ;
         if (numCached <= ProcessList.TRIM_CACHED_APPS
                 && numEmpty <= ProcessList.TRIM_EMPTY_APPS) {
             final int numCachedAndEmpty = numCached + numEmpty;
@@ -14201,10 +14207,13 @@
             int fgTrimLevel;
             if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
                 fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
+                memFactor = ProcessTracker.STATE_MEM_FACTOR_CRITIAL_ADJ;
             } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
                 fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
+                memFactor = ProcessTracker.STATE_MEM_FACTOR_LOW_ADJ;
             } else {
                 fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
+                memFactor = ProcessTracker.STATE_MEM_FACTOR_MODERATE_ADJ;
             }
             int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
             for (i=0; i<N; i++) {
@@ -14303,6 +14312,21 @@
             // be in a consistent state at this point.
             mStackSupervisor.scheduleDestroyAllActivities(null, "always-finish");
         }
+
+        boolean allChanged = mProcessTracker.setMemFactor(memFactor, !mSleeping, now);
+        if (changed || allChanged) {
+            memFactor = mProcessTracker.getMemFactor();
+            for (i=mLruProcesses.size()-1; i>=0; i--) {
+                ProcessRecord app = mLruProcesses.get(i);
+                if (allChanged || app.setAdjChanged) {
+                    app.setProcessTrackerState(TOP_APP, memFactor, now, mProcessList);
+                }
+            }
+        }
+
+        if (DEBUG_OOM_ADJ) {
+            Slog.d(TAG, "Did OOM ADJ in " + (SystemClock.uptimeMillis()-now) + "ms");
+        }
     }
 
     final void trimApplications() {
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 6fe28f4..ba8de39 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -987,7 +987,7 @@
 
         if (app != null && app.thread != null) {
             try {
-                app.addPackage(r.info.packageName);
+                app.addPackage(r.info.packageName, mService.mProcessTracker);
                 realStartActivityLocked(r, app, andResume, checkConfig);
                 return;
             } catch (RemoteException e) {
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index c7e6010..0e7513c 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -795,7 +795,7 @@
                     info.activityInfo.applicationInfo.uid);
             if (app != null && app.thread != null) {
                 try {
-                    app.addPackage(info.activityInfo.packageName);
+                    app.addPackage(info.activityInfo.packageName, mService.mProcessTracker);
                     processCurBroadcastLocked(r, app);
                     return;
                 } catch (RemoteException e) {
diff --git a/services/java/com/android/server/am/CompatModePackages.java b/services/java/com/android/server/am/CompatModePackages.java
index f56371c..59e6787 100644
--- a/services/java/com/android/server/am/CompatModePackages.java
+++ b/services/java/com/android/server/am/CompatModePackages.java
@@ -300,7 +300,7 @@
             // Tell all processes that loaded this package about the change.
             for (int i=mService.mLruProcesses.size()-1; i>=0; i--) {
                 ProcessRecord app = mService.mLruProcesses.get(i);
-                if (!app.pkgList.contains(packageName)) {
+                if (!app.pkgList.containsKey(packageName)) {
                     continue;
                 }
                 try {
diff --git a/services/java/com/android/server/am/ProcessList.java b/services/java/com/android/server/am/ProcessList.java
index e937d35..b5d783d 100644
--- a/services/java/com/android/server/am/ProcessList.java
+++ b/services/java/com/android/server/am/ProcessList.java
@@ -244,7 +244,7 @@
     }
 
     int adjToTrackedState(int adj) {
-        return mAdjToTrackedState[adj];
+        return adj >= FOREGROUND_APP_ADJ ? mAdjToTrackedState[adj] : ProcessTracker.STATE_NOTHING;
     }
 
     private void writeFile(String path, String data) {
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 6cae67c..cc0a5a3 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -51,9 +51,10 @@
     final int uid;              // uid of process; may be different from 'info' if isolated
     final int userId;           // user of process.
     final String processName;   // name of the process
-    final ProcessTracker.ProcessState tracker; // tracking execution of process
+    final ProcessTracker.ProcessState baseProcessTracker;
     // List of packages running in the process
-    final HashSet<String> pkgList = new HashSet<String>();
+    final ArrayMap<String, ProcessTracker.ProcessState> pkgList
+            = new ArrayMap<String, ProcessTracker.ProcessState>();
     IApplicationThread thread;  // the actual proc...  may be null only if
                                 // 'persistent' is true (in which case we
                                 // are in the process of launching the app)
@@ -87,6 +88,7 @@
     boolean hasAboveClient;     // Bound using BIND_ABOVE_CLIENT, so want to be lower
     boolean bad;                // True if disabled in the bad process list
     boolean killedBackground;   // True when proc has been killed due to too many bg
+    boolean setAdjChanged;      // Keep track of whether we changed 'setAdj'.
     String waitingToKill;       // Process is waiting to be killed when in the bg; reason
     IBinder forcingToForeground;// Token that is forcing this process to be foreground
     int adjSeq;                 // Sequence id for identifying oom_adj assignment cycles
@@ -327,15 +329,15 @@
     
     ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, IApplicationThread _thread,
             ApplicationInfo _info, String _processName, int _uid,
-            ProcessTracker.ProcessState _tracker) {
+            ProcessTracker.ProcessState tracker) {
         batteryStats = _batteryStats;
         info = _info;
         isolated = _info.uid != _uid;
         uid = _uid;
         userId = UserHandle.getUserId(_uid);
         processName = _processName;
-        tracker = _tracker;
-        pkgList.add(_info.packageName);
+        baseProcessTracker = tracker;
+        pkgList.put(_info.packageName, tracker);
         thread = _thread;
         maxAdj = ProcessList.CACHED_APP_MAX_ADJ;
         cachedAdj = clientCachedAdj = emptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
@@ -437,20 +439,33 @@
     /*
      *  Return true if package has been added false if not
      */
-    public boolean addPackage(String pkg) {
-        if (!pkgList.contains(pkg)) {
-            pkgList.add(pkg);
+    public boolean addPackage(String pkg, ProcessTracker tracker) {
+        if (!pkgList.containsKey(pkg)) {
+            pkgList.put(pkg, tracker.getProcessStateLocked(pkg, info.uid, processName));
             return true;
         }
         return false;
     }
-    
+
+    public void setProcessTrackerState(ProcessRecord TOP_APP, int memFactor, long now,
+            ProcessList plist) {
+        int state = this == TOP_APP ? ProcessTracker.STATE_TOP
+                : plist.adjToTrackedState(setAdj);
+        for (int ip=pkgList.size()-1; ip>=0; ip--) {
+            pkgList.valueAt(ip).setState(state, memFactor, now);
+        }
+    }
+
     /*
      *  Delete all packages from list except the package indicated in info
      */
     public void resetPackageList() {
+        long now = SystemClock.uptimeMillis();
+        for (int i=0; i<pkgList.size(); i++) {
+            pkgList.valueAt(i).setState(ProcessTracker.STATE_NOTHING, 0, now);
+        }
         pkgList.clear();
-        pkgList.add(info.packageName);
+        pkgList.put(info.packageName, baseProcessTracker);
     }
     
     public String[] getPackageList() {
@@ -459,7 +474,9 @@
             return null;
         }
         String list[] = new String[size];
-        pkgList.toArray(list);
+        for (int i=0; i<pkgList.size(); i++) {
+            list[i] = pkgList.keyAt(i);
+        }
         return list;
     }
 }
diff --git a/services/java/com/android/server/am/ProcessTracker.java b/services/java/com/android/server/am/ProcessTracker.java
index 4eb71c7..fb8d09c 100644
--- a/services/java/com/android/server/am/ProcessTracker.java
+++ b/services/java/com/android/server/am/ProcessTracker.java
@@ -36,7 +36,15 @@
     public static final int STATE_HOME = 6;
     public static final int STATE_PREVIOUS = 7;
     public static final int STATE_CACHED = 8;
-    public static final int STATE_SCREEN_ON_MOD = STATE_CACHED+1;
+    public static final int STATE_MEM_FACTOR_MOD = STATE_CACHED+1;
+    public static final int STATE_MEM_FACTOR_NORMAL_ADJ = 0;
+    public static final int STATE_MEM_FACTOR_MODERATE_ADJ = STATE_MEM_FACTOR_MOD;
+    public static final int STATE_MEM_FACTOR_LOW_ADJ = STATE_MEM_FACTOR_MOD*2;
+    public static final int STATE_MEM_FACTOR_CRITIAL_ADJ = STATE_MEM_FACTOR_MOD*3;
+    public static final int STATE_MEM_FACTOR_COUNT = STATE_MEM_FACTOR_MOD*4;
+    public static final int STATE_SCREEN_ON_MOD = STATE_MEM_FACTOR_COUNT;
+    public static final int STATE_SCREEN_OFF_ADJ = 0;
+    public static final int STATE_SCREEN_ON_ADJ = STATE_SCREEN_ON_MOD;
     public static final int STATE_COUNT = STATE_SCREEN_ON_MOD*2;
 
     static String[] STATE_NAMES = new String[] {
@@ -45,21 +53,44 @@
     };
 
     public static final class ProcessState {
-        final long[] mTimes = new long[STATE_COUNT];
+        final long[] mDurations = new long[STATE_COUNT];
         int mCurState = STATE_NOTHING;
         long mStartTime;
 
-        public void setState(int state, long now) {
-            if (mCurState != STATE_NOTHING) {
-                mTimes[mCurState] += now - mStartTime;
+        public void setState(int state, int memFactor, long now) {
+            if (state != STATE_NOTHING) {
+                state += memFactor;
             }
-            mCurState = state;
-            mStartTime = now;
+            if (mCurState != state) {
+                if (mCurState != STATE_NOTHING) {
+                    mDurations[mCurState] += now - mStartTime;
+                }
+                mCurState = state;
+                mStartTime = now;
+            }
+        }
+    }
+
+    public static final class ServiceState {
+        long mStartedDuration;
+        long mStartedTime;
+    }
+
+    public static final class PackageState {
+        final ArrayMap<String, ProcessState> mProcesses = new ArrayMap<String, ProcessState>();
+        final ArrayMap<String, ServiceState> mServices = new ArrayMap<String, ServiceState>();
+        final int mUid;
+
+        public PackageState(int uid) {
+            mUid = uid;
         }
     }
 
     static final class State {
-        final ProcessMap<ProcessState> mProcesses = new ProcessMap<ProcessState>();
+        final ProcessMap<PackageState> mPackages = new ProcessMap<PackageState>();
+        final long[] mMemFactorDurations = new long[STATE_COUNT/STATE_MEM_FACTOR_MOD];
+        int mMemFactor = STATE_NOTHING;
+        long mStartTime;
     }
 
     final State mState = new State();
@@ -67,44 +98,198 @@
     public ProcessTracker() {
     }
 
-    public ProcessState getStateLocked(String name, int uid) {
-        ProcessState ps = mState.mProcesses.get(name, uid);
+    private PackageState getPackageStateLocked(String packageName, int uid) {
+        PackageState as = mState.mPackages.get(packageName, uid);
+        if (as != null) {
+            return as;
+        }
+        as = new PackageState(uid);
+        mState.mPackages.put(packageName, uid, as);
+        return as;
+    }
+
+    public ProcessState getProcessStateLocked(String packageName, int uid, String processName) {
+        final PackageState as = getPackageStateLocked(packageName, uid);
+        ProcessState ps = as.mProcesses.get(processName);
         if (ps != null) {
             return ps;
         }
         ps = new ProcessState();
-        mState.mProcesses.put(name, uid, ps);
+        as.mProcesses.put(processName, ps);
         return ps;
     }
 
+    public ServiceState getServiceStateLocked(String packageName, int uid, String className) {
+        final PackageState as = getPackageStateLocked(packageName, uid);
+        ServiceState ss = as.mServices.get(className);
+        if (ss != null) {
+            return ss;
+        }
+        ss = new ServiceState();
+        as.mServices.put(className, ss);
+        return ss;
+    }
+
+    public boolean setMemFactor(int memFactor, boolean screenOn, long now) {
+        if (screenOn) {
+            memFactor += STATE_SCREEN_ON_MOD;
+        }
+        if (memFactor != mState.mMemFactor) {
+            if (mState.mMemFactor != STATE_NOTHING) {
+                mState.mMemFactorDurations[mState.mMemFactor/STATE_MEM_FACTOR_MOD]
+                        += now - mState.mStartTime;
+            }
+            mState.mMemFactor = memFactor;
+            mState.mStartTime = now;
+            return true;
+        }
+        return false;
+    }
+
+    public int getMemFactor() {
+        return mState.mMemFactor != STATE_NOTHING ? mState.mMemFactor : 0;
+    }
+
+    private void printScreenLabel(PrintWriter pw, int offset) {
+        switch (offset) {
+            case STATE_NOTHING:
+                pw.print("             ");
+                break;
+            case STATE_SCREEN_OFF_ADJ:
+                pw.print("Screen Off / ");
+                break;
+            case STATE_SCREEN_ON_ADJ:
+                pw.print("Screen On  / ");
+                break;
+            default:
+                pw.print("?????????? / ");
+                break;
+        }
+    }
+
+    private void printMemLabel(PrintWriter pw, int offset) {
+        switch (offset) {
+            case STATE_NOTHING:
+                pw.print("       ");
+                break;
+            case STATE_MEM_FACTOR_NORMAL_ADJ:
+                pw.print("Norm / ");
+                break;
+            case STATE_MEM_FACTOR_MODERATE_ADJ:
+                pw.print("Mod  / ");
+                break;
+            case STATE_MEM_FACTOR_LOW_ADJ:
+                pw.print("Low  / ");
+                break;
+            case STATE_MEM_FACTOR_CRITIAL_ADJ:
+                pw.print("Crit / ");
+                break;
+            default:
+                pw.print("???? / ");
+                break;
+        }
+    }
+
     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
         final long now = SystemClock.uptimeMillis();
-        ArrayMap<String, SparseArray<ProcessState>> pmap = mState.mProcesses.getMap();
+        ArrayMap<String, SparseArray<PackageState>> pmap = mState.mPackages.getMap();
+        pw.println("Process Stats:");
         for (int ip=0; ip<pmap.size(); ip++) {
             String procName = pmap.keyAt(ip);
-            SparseArray<ProcessState> procs = pmap.valueAt(ip);
+            SparseArray<PackageState> procs = pmap.valueAt(ip);
             for (int iu=0; iu<procs.size(); iu++) {
                 int uid = procs.keyAt(iu);
-                ProcessState state = procs.valueAt(iu);
+                PackageState state = procs.valueAt(iu);
                 pw.print("  "); pw.print(procName); pw.print(" / "); pw.print(uid); pw.println(":");
-                long totalTime = 0;
-                for (int is=0; is<STATE_NAMES.length; is++) {
-                    long time = state.mTimes[is];
-                    if (state.mCurState == is) {
-                        time += now - state.mStartTime;
+                for (int iproc=0; iproc<state.mProcesses.size(); iproc++) {
+                    pw.print("    Process ");
+                    pw.print(state.mProcesses.keyAt(iproc));
+                    pw.println(":");
+                    long totalTime = 0;
+                    ProcessState proc = state.mProcesses.valueAt(iproc);
+                    int printedScreen = -1;
+                    for (int iscreen=0; iscreen<STATE_COUNT; iscreen+=STATE_SCREEN_ON_MOD) {
+                        int printedMem = -1;
+                        for (int imem=0; imem<STATE_MEM_FACTOR_COUNT; imem+=STATE_MEM_FACTOR_MOD) {
+                            for (int is=0; is<STATE_NAMES.length; is++) {
+                                int bucket = is+imem+iscreen;
+                                long time = proc.mDurations[bucket];
+                                String running = "";
+                                if (proc.mCurState == bucket) {
+                                    time += now - proc.mStartTime;
+                                    running = " (running)";
+                                }
+                                if (time != 0) {
+                                    pw.print("      ");
+                                    printScreenLabel(pw, printedScreen != iscreen
+                                            ? iscreen : STATE_NOTHING);
+                                    printedScreen = iscreen;
+                                    printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING);
+                                    printedMem = imem;
+                                    pw.print(STATE_NAMES[is]); pw.print(": ");
+                                    TimeUtils.formatDuration(time, pw); pw.println(running);
+                                    totalTime += time;
+                                }
+                            }
+                        }
+                    }
+                    if (totalTime != 0) {
+                        pw.print("      ");
+                        printScreenLabel(pw, STATE_NOTHING);
+                        printMemLabel(pw, STATE_NOTHING);
+                        pw.print("TOTAL      : ");
+                        TimeUtils.formatDuration(totalTime, pw);
+                        pw.println();
+                    }
+                }
+                for (int isvc=0; isvc<state.mServices.size(); isvc++) {
+                    pw.print("    Service ");
+                    pw.print(state.mServices.keyAt(isvc));
+                    pw.println(":");
+                    ServiceState svc = state.mServices.valueAt(isvc);
+                    long time = svc.mStartedDuration;
+                    if (svc.mStartedTime >= 0) {
+                        time += now - svc.mStartedTime;
                     }
                     if (time != 0) {
-                        pw.print("    "); pw.print(STATE_NAMES[is]); pw.print(": ");
+                        pw.print("    Started: ");
                         TimeUtils.formatDuration(time, pw); pw.println();
-                        totalTime += time;
                     }
                 }
-                if (totalTime != 0) {
-                    pw.print("    TOTAL      : ");
-                    TimeUtils.formatDuration(totalTime, pw);
-                    pw.println();
+            }
+        }
+        pw.println();
+        pw.println("Run time Stats:");
+        long totalTime = 0;
+        int printedScreen = -1;
+        for (int iscreen=0; iscreen<STATE_COUNT; iscreen+=STATE_SCREEN_ON_MOD) {
+            int printedMem = -1;
+            for (int imem=0; imem<STATE_MEM_FACTOR_COUNT; imem+=STATE_MEM_FACTOR_MOD) {
+                int bucket = imem+iscreen;
+                long time = mState.mMemFactorDurations[bucket/STATE_MEM_FACTOR_MOD];
+                String running = "";
+                if (mState.mMemFactor == bucket) {
+                    time += now - mState.mStartTime;
+                    running = " (running)";
+                }
+                if (time != 0) {
+                    pw.print("  ");
+                    printScreenLabel(pw, printedScreen != iscreen
+                            ? iscreen : STATE_NOTHING);
+                    printedScreen = iscreen;
+                    printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING);
+                    printedMem = imem;
+                    TimeUtils.formatDuration(time, pw); pw.println(running);
+                    totalTime += time;
                 }
             }
         }
+        if (totalTime != 0) {
+            pw.print("  ");
+            printScreenLabel(pw, STATE_NOTHING);
+            pw.print("TOTAL: ");
+            TimeUtils.formatDuration(totalTime, pw);
+            pw.println();
+        }
     }
 }