Add new API to performing grouping and ordering of bindings.

This will allow apps to better manage many isolated processes,
telling the system how they are grouped together (so should be
managed as one related entity within the LRU cache) and fine-tune
the ordering within that group.

The API is not yet tested...  though in theory it is implemented.
But the implementation done for that also fixes a *lot* of problems
with activity LRU management that, even without groups, should
make the ordering of processes in the LRU list much more consistently
match how recently the user has interacted with it.

Also clean up some of the new dumpsys output in the activity manager:
move the new sections to before the process output (so it is still
easy to see the process state at the end of the output), and add and
document the command line options for controlling them.  And add a
new "lru" section that gives a clear view of what is going on with
the raw LRU list.

An upcoming change will add tests for the new grouping functionality,
and probably some fixes resulting from that.

Test: atest CtsAppTestCases:ServiceTest
Bug: 111434506
Change-Id: I1f6b6b9de66ccde1573e1a0e9615e8c5f8e6c0d7
diff --git a/api/current.txt b/api/current.txt
index ff78d48..4c32d04 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -9598,6 +9598,7 @@
     method public abstract void unbindService(android.content.ServiceConnection);
     method public void unregisterComponentCallbacks(android.content.ComponentCallbacks);
     method public abstract void unregisterReceiver(android.content.BroadcastReceiver);
+    method public abstract void updateServiceGroup(android.content.ServiceConnection, int, int);
     field public static final java.lang.String ACCESSIBILITY_SERVICE = "accessibility";
     field public static final java.lang.String ACCOUNT_SERVICE = "account";
     field public static final java.lang.String ACTIVITY_SERVICE = "activity";
@@ -9796,6 +9797,7 @@
     method public boolean stopService(android.content.Intent);
     method public void unbindService(android.content.ServiceConnection);
     method public void unregisterReceiver(android.content.BroadcastReceiver);
+    method public void updateServiceGroup(android.content.ServiceConnection, int, int);
   }
 
   public deprecated class CursorLoader extends android.content.AsyncTaskLoader {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 9837deb..28ecb27 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1724,6 +1724,24 @@
     }
 
     @Override
+    public void updateServiceGroup(@NonNull ServiceConnection conn, int group, int importance) {
+        if (conn == null) {
+            throw new IllegalArgumentException("connection is null");
+        }
+        if (mPackageInfo != null) {
+            IServiceConnection sd = mPackageInfo.forgetServiceDispatcher(
+                    getOuterContext(), conn);
+            try {
+                ActivityManager.getService().updateServiceGroup(sd, group, importance);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            throw new RuntimeException("Not supported in system context");
+        }
+    }
+
+    @Override
     public void unbindService(ServiceConnection conn) {
         if (conn == null) {
             throw new IllegalArgumentException("connection is null");
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f27c667..e83bcd0 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -140,6 +140,7 @@
     int bindIsolatedService(in IApplicationThread caller, in IBinder token, in Intent service,
             in String resolvedType, in IServiceConnection connection, int flags,
             in String instanceName, in String callingPackage, int userId);
+    void updateServiceGroup(in IServiceConnection connection, int group, int importance);
     boolean unbindService(in IServiceConnection connection);
     void publishService(in IBinder token, in Intent intent, in IBinder service);
     void setDebugApp(in String packageName, boolean waitForDebugger, boolean persistent);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 004417b..cec8ef5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -425,6 +425,15 @@
      */
     public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
 
+    /**
+     * These bind flags reduce the strength of the binding such that we shouldn't
+     * consider it as pulling the process up to the level of the one that is bound to it.
+     * @hide
+     */
+    public static final int BIND_REDUCTION_FLAGS =
+            Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_WAIVE_PRIORITY
+                    | Context.BIND_ADJUST_BELOW_PERCEPTIBLE | Context.BIND_NOT_VISIBLE;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "RECEIVER_VISIBLE_" }, value = {
             RECEIVER_VISIBLE_TO_INSTANT_APPS
@@ -2982,6 +2991,31 @@
     }
 
     /**
+     * For a service previously bound with {@link #bindService} or a related method, change
+     * how the system manages that service's process in relation to other processes.  This
+     * doesn't modify the original bind flags that were passed in when binding, but adjusts
+     * how the process will be managed in some cases based on those flags.  Currently only
+     * works on isolated processes (will be ignored for non-isolated processes).
+     *
+     * @param conn The connection interface previously supplied to bindService().  This
+     *             parameter must not be null.
+     * @param group A group to put this connection's process in.  Upon calling here, this
+     *              will override any previous group that was set for that process.  The group
+     *              tells the system about processes that are logically grouped together, so
+     *              should be managed as one unit of importance (such as when being considered
+     *              a recently used app).  All processes in the same app with the same group
+     *              are considered to be related.  Supplying 0 reverts to the default behavior
+     *              of not grouping.
+     * @param importance Additional importance of the processes within a group.  Upon calling
+     *                   here, this will override any previous group that was set for that
+     *                   process.  This fine-tunes process killing of all processes within
+     *                   a related groups -- higher importance values will be killed before
+     *                   lower ones.
+     */
+    public abstract void updateServiceGroup(@NonNull ServiceConnection conn, int group,
+            int importance);
+
+    /**
      * Disconnect from an application service.  You will no longer receive
      * calls as the service is restarted, and the service is now allowed to
      * stop at any time.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 88696b0..2db44b4 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -726,6 +726,11 @@
     }
 
     @Override
+    public void updateServiceGroup(ServiceConnection conn, int group, int importance) {
+        mBase.updateServiceGroup(conn, group, importance);
+    }
+
+    @Override
     public void unbindService(ServiceConnection conn) {
         mBase.unbindService(conn);
     }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c660cc6..a19e928 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1702,8 +1702,11 @@
                     s.app.whitelistManager = true;
                 }
                 // This could have made the service more important.
-                mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities()
-                        || s.app.treatLikeActivity, b.client);
+                mAm.updateLruProcessLocked(s.app,
+                        (callerApp.hasActivitiesOrRecentTasks() && s.app.hasClientActivities())
+                                || (callerApp.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP
+                                        && (flags & Context.BIND_TREAT_LIKE_ACTIVITY) != 0),
+                        b.client);
                 mAm.updateOomAdjLocked(s.app, true);
             }
 
@@ -1787,6 +1790,32 @@
         }
     }
 
+    void updateServiceGroupLocked(IServiceConnection connection, int group, int importance) {
+        final IBinder binder = connection.asBinder();
+        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "updateServiceGroup: conn=" + binder);
+        final ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
+        if (clist == null) {
+            throw new IllegalArgumentException("Could not find connection for "
+                    + connection.asBinder());
+        }
+        for (int i = clist.size() - 1; i >= 0; i--) {
+            final ConnectionRecord crec = clist.get(i);
+            final ServiceRecord srec = crec.binding.service;
+            if (srec != null && srec.app != null
+                    && (srec.serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
+                if (group > 0) {
+                    srec.app.connectionService = srec;
+                    srec.app.connectionGroup = group;
+                    srec.app.connectionImportance = importance;
+                } else {
+                    srec.app.connectionService = null;
+                    srec.app.connectionGroup = 0;
+                    srec.app.connectionImportance = 0;
+                }
+            }
+        }
+    }
+
     boolean unbindServiceLocked(IServiceConnection connection) {
         IBinder binder = connection.asBinder();
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "unbindService: conn=" + binder);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4e417ba..7e9e83c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9207,26 +9207,33 @@
                 }
                 dumpAssociationsLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
             }
+            if (dumpPackage == null) {
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
+                mOomAdjProfiler.dump(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
+                dumpBinderProxies(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
+                dumpLmkLocked(pw);
+            }
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpLruLocked(pw, dumpPackage);
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
             dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
-            pw.println();
-            if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
-            }
-            mOomAdjProfiler.dump(pw);
-            pw.println();
-            if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
-            }
-            dumpBinderProxies(pw);
-            pw.println();
-            if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
-            }
-            dumpLmkLocked(pw);
         }
     }
 
@@ -9421,6 +9428,10 @@
                 synchronized (this) {
                     dumpLmkLocked(pw);
                 }
+            } else if ("lru".equals(cmd)) {
+                synchronized (this) {
+                    dumpLruLocked(pw, null);
+                }
             } else if ("permissions".equals(cmd) || "perm".equals(cmd)) {
                 synchronized (this) {
                     dumpPermissionsLocked(fd, pw, args, opti, true, null);
@@ -9698,17 +9709,102 @@
                 }
                 pw.println();
             }
-            pw.println();
             return true;
         }
         return false;
     }
 
     void dumpBinderProxies(PrintWriter pw) {
+        pw.println("ACTIVITY MANAGER BINDER PROXY STATE (dumpsys activity binder-proxies)");
         dumpBinderProxyInterfaceCounts(pw,
-                "Top proxy interface names held by SYSTEM");
+                "  Top proxy interface names held by SYSTEM");
         dumpBinderProxiesCounts(pw,
-                "Counts of Binder Proxies held by SYSTEM");
+                "  Counts of Binder Proxies held by SYSTEM");
+    }
+
+    void dumpLruEntryLocked(PrintWriter pw, int index, ProcessRecord proc) {
+        pw.print("    #");
+        pw.print(index);
+        pw.print(": ");
+        pw.print(ProcessList.makeOomAdjString(proc.setAdj));
+        pw.print(" ");
+        pw.print(ProcessList.makeProcStateString(proc.getCurProcState()));
+        pw.print(" ");
+        pw.print(proc.toShortString());
+        pw.print(" ");
+        if (proc.hasActivitiesOrRecentTasks() || proc.hasClientActivities()
+                || proc.treatLikeActivity) {
+            pw.print(" activity=");
+            boolean printed = false;
+            if (proc.hasActivities()) {
+                pw.print("activities");
+                printed = true;
+            }
+            if (proc.hasRecentTasks()) {
+                if (printed) {
+                    pw.print("|");
+                }
+                pw.print("recents");
+                printed = true;
+            }
+            if (proc.hasClientActivities()) {
+                if (printed) {
+                    pw.print("|");
+                }
+                pw.print("client");
+                printed = true;
+            }
+            if (proc.treatLikeActivity) {
+                if (printed) {
+                    pw.print("|");
+                }
+                pw.print("treated");
+            }
+        }
+        pw.println();
+    }
+
+    // TODO: Move to ProcessList?
+    void dumpLruLocked(PrintWriter pw, String dumpPackage) {
+        pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity lru)");
+        final int N = mProcessList.mLruProcesses.size();
+        int i;
+        boolean first = true;
+        for (i = N - 1; i >= mProcessList.mLruProcessActivityStart; i--) {
+            final ProcessRecord r = mProcessList.mLruProcesses.get(i);
+            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                continue;
+            }
+            if (first) {
+                pw.println("  Activities:");
+                first = false;
+            }
+            dumpLruEntryLocked(pw, i, r);
+        }
+        first = true;
+        for (; i >= mProcessList.mLruProcessServiceStart; i--) {
+            final ProcessRecord r = mProcessList.mLruProcesses.get(i);
+            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                continue;
+            }
+            if (first) {
+                pw.println("  Services:");
+                first = false;
+            }
+            dumpLruEntryLocked(pw, i, r);
+        }
+        first = true;
+        for (; i >= 0; i--) {
+            final ProcessRecord r = mProcessList.mLruProcesses.get(i);
+            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                continue;
+            }
+            if (first) {
+                pw.println("  Other:");
+                first = false;
+            }
+            dumpLruEntryLocked(pw, i, r);
+        }
     }
 
     // TODO: Move to ProcessList?
@@ -13214,6 +13310,12 @@
         }
     }
 
+    public void updateServiceGroup(IServiceConnection connection, int group, int importance) {
+        synchronized (this) {
+            mServices.updateServiceGroupLocked(connection, group, importance);
+        }
+    }
+
     public boolean unbindService(IServiceConnection connection) {
         synchronized (this) {
             return mServices.unbindServiceLocked(connection);
@@ -17398,8 +17500,11 @@
         int stepCached = 0;
         int stepEmpty = 0;
         int numCached = 0;
+        int numCachedExtraGroup = 0;
         int numEmpty = 0;
         int numTrimming = 0;
+        int lastCachedGroup = 0;
+        int lastCachedGroupUid = 0;
 
         mNumNonCachedProcs = 0;
         mNumCachedHiddenProcs = 0;
@@ -17523,7 +17628,21 @@
                     case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                         mNumCachedHiddenProcs++;
                         numCached++;
-                        if (numCached > cachedProcessLimit) {
+                        if (app.connectionGroup != 0) {
+                            if (lastCachedGroupUid == app.uid
+                                    && lastCachedGroup == app.connectionGroup) {
+                                // If this process is the next in the same group, we don't
+                                // want it to count against our limit of the number of cached
+                                // processes, so bump up the group count to account for it.
+                                numCachedExtraGroup++;
+                            } else {
+                                lastCachedGroupUid = app.uid;
+                                lastCachedGroup = app.connectionGroup;
+                            }
+                        } else {
+                            lastCachedGroupUid = lastCachedGroup = 0;
+                        }
+                        if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
                             app.kill("cached #" + numCached, true);
                         }
                         break;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 8f8d5ab..67a4d14 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2853,6 +2853,9 @@
             pw.println("    provider [COMP_SPEC]: provider client-side state");
             pw.println("    s[ervices] [COMP_SPEC ...]: service state");
             pw.println("    as[sociations]: tracked app associations");
+            pw.println("    lmk: stats on low memory killer");
+            pw.println("    lru: raw LRU process list");
+            pw.println("    binder-proxies: stats on binder objects and IPCs");
             pw.println("    settings: currently applied config settings");
             pw.println("    service [COMP_SPEC]: service client-side state");
             pw.println("    package [PACKAGE_NAME]: all state related to given package");
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index bfa3f66..aa76b3d 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -48,7 +48,7 @@
     boolean serviceDead;            // Well is it?
 
     // Please keep the following two enum list synced.
-    private static int[] BIND_ORIG_ENUMS = new int[] {
+    private static final int[] BIND_ORIG_ENUMS = new int[] {
             Context.BIND_AUTO_CREATE,
             Context.BIND_DEBUG_UNBIND,
             Context.BIND_NOT_FOREGROUND,
@@ -65,7 +65,7 @@
             Context.BIND_SHOWING_UI,
             Context.BIND_NOT_VISIBLE,
     };
-    private static int[] BIND_PROTO_ENUMS = new int[] {
+    private static final int[] BIND_PROTO_ENUMS = new int[] {
             ConnectionRecordProto.AUTO_CREATE,
             ConnectionRecordProto.DEBUG_UNBIND,
             ConnectionRecordProto.NOT_FG,
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 84b364b..4b19398 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -50,6 +50,7 @@
 import android.app.AppProtoEnums;
 import android.app.IApplicationThread;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
@@ -2204,7 +2205,7 @@
 
     @GuardedBy("mService")
     int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
-            String what, Object obj, ProcessRecord srcApp) {
+            int lruSeq, String what, Object obj, ProcessRecord srcApp) {
         app.lastActivityTime = now;
 
         if (app.hasActivitiesOrRecentTasks()) {
@@ -2225,7 +2226,7 @@
             return index;
         }
 
-        if (lrui >= mLruProcessActivityStart) {
+        if (lrui >= mLruProcessActivityStart && index < mLruProcessActivityStart) {
             // Don't want to touch dependent processes that are hosting activities.
             return index;
         }
@@ -2237,6 +2238,7 @@
         if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
                 + " in LRU list: " + app);
         mLruProcesses.add(index, app);
+        app.lruSeq = lruSeq;
         return index;
     }
 
@@ -2345,9 +2347,11 @@
         */
 
         int nextIndex;
+        int nextActivityIndex = -1;
         if (hasActivity) {
             final int N = mLruProcesses.size();
-            if ((!app.hasActivities() || app.hasRecentTasks())
+            nextIndex = mLruProcessServiceStart;
+            if (!app.hasActivitiesOrRecentTasks() && !app.treatLikeActivity
                     && mLruProcessActivityStart < (N - 1)) {
                 // Process doesn't have activities, but has clients with
                 // activities...  move it up, but one below the top (the top
@@ -2355,36 +2359,92 @@
                 if (DEBUG_LRU) Slog.d(TAG_LRU,
                         "Adding to second-top of LRU activity list: " + app);
                 mLruProcesses.add(N - 1, app);
-                // To keep it from spamming the LRU list (by making a bunch of clients),
-                // we will push down any other entries owned by the app.
+                // If this process is part of a group, need to pull up any other processes
+                // in that group to be with it.
                 final int uid = app.info.uid;
-                for (int i = N - 2; i > mLruProcessActivityStart; i--) {
-                    ProcessRecord subProc = mLruProcesses.get(i);
-                    if (subProc.info.uid == uid) {
-                        // We want to push this one down the list.  If the process after
-                        // it is for the same uid, however, don't do so, because we don't
-                        // want them internally to be re-ordered.
-                        if (mLruProcesses.get(i - 1).info.uid != uid) {
-                            if (DEBUG_LRU) Slog.d(TAG_LRU,
-                                    "Pushing uid " + uid + " swapping at " + i + ": "
-                                            + mLruProcesses.get(i) + " : "
-                                            + mLruProcesses.get(i - 1));
-                            ProcessRecord tmp = mLruProcesses.get(i);
-                            mLruProcesses.set(i, mLruProcesses.get(i - 1));
-                            mLruProcesses.set(i - 1, tmp);
-                            i--;
+                int endIndex = N - 2;
+                nextActivityIndex = N - 2;
+                if (app.connectionGroup > 0) {
+                    int endImportance = app.connectionImportance;
+                    for (int i = endIndex; i >= mLruProcessActivityStart; i--) {
+                        final ProcessRecord subProc = mLruProcesses.get(i);
+                        if (subProc.info.uid == uid
+                                && subProc.connectionGroup == subProc.connectionGroup) {
+                            if (i == endIndex && subProc.connectionImportance >= endImportance) {
+                                // This process is already in the group, and its importance
+                                // is not as strong as the process before it, so it keep it
+                                // correctly positioned in the group.
+                                endIndex--;
+                                endImportance = subProc.connectionImportance;
+                            } else {
+                                // We want to pull this up to be with the rest of the group,
+                                // and order within the group by importance.
+                                boolean moved = false;
+                                for (int pos = N - 1; pos > endIndex; pos--) {
+                                    final ProcessRecord posProc = mLruProcesses.get(pos);
+                                    if (subProc.connectionImportance
+                                            <= posProc.connectionImportance) {
+                                        mLruProcesses.remove(i);
+                                        mLruProcesses.add(pos, subProc);
+                                        moved = true;
+                                        endIndex--;
+                                        break;
+                                    }
+                                }
+                                if (!moved) {
+                                    // Goes to the end of the group.
+                                    mLruProcesses.remove(i);
+                                    mLruProcesses.add(endIndex - 1, subProc);
+                                    endIndex--;
+                                    endImportance = subProc.connectionImportance;
+                                }
+                            }
                         }
-                    } else {
-                        // A gap, we can stop here.
-                        break;
+                    }
+
+                }
+                // To keep it from spamming the LRU list (by making a bunch of clients),
+                // we will distribute other entries owned by it to be in-between other apps.
+                for (int i = endIndex; i >= mLruProcessActivityStart; i--) {
+                    final ProcessRecord subProc = mLruProcesses.get(i);
+                    if (subProc.info.uid != uid) {
+                        // This is a different app...  if we have gone through some of the
+                        // target app, pull this up to be before them.
+                        if (i < endIndex) {
+                            mLruProcesses.remove(i);
+                            mLruProcesses.add(endIndex, subProc);
+                        }
+                        // Find the end of the next group of processes for target app.  This
+                        // is after any entries of different apps (so we don't change the existing
+                        // relative order of apps) and then after the next last group of processes
+                        // of the target app.
+                        for (endIndex--; endIndex >= mLruProcessActivityStart; endIndex--) {
+                            final ProcessRecord endProc = mLruProcesses.get(endIndex);
+                            if (endProc.info.uid == uid) {
+                                break;
+                            }
+                        }
+                        if (endIndex >= mLruProcessActivityStart) {
+                            final ProcessRecord endProc = mLruProcesses.get(endIndex);
+                            for (endIndex--; endIndex >= mLruProcessActivityStart; endIndex--) {
+                                final ProcessRecord nextEndProc = mLruProcesses.get(endIndex);
+                                if (nextEndProc.info.uid != uid
+                                        || nextEndProc.connectionGroup != endProc.connectionGroup) {
+                                    break;
+                                }
+                            }
+                        }
+                        if (i > endIndex) {
+                            i = endIndex;
+                        }
                     }
                 }
             } else {
                 // Process has activities, put it at the very tipsy-top.
                 if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU activity list: " + app);
                 mLruProcesses.add(app);
+                nextActivityIndex = mLruProcesses.size() - 1;
             }
-            nextIndex = mLruProcessServiceStart;
         } else if (hasService) {
             // Process has services, put it at the top of the service list.
             if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU service list: " + app);
@@ -2416,6 +2476,8 @@
             mLruProcessServiceStart++;
         }
 
+        app.lruSeq = mLruSeq;
+
         // If the app is currently using a content provider or service,
         // bump those processes as well.
         for (int j = app.connections.size() - 1; j >= 0; j--) {
@@ -2423,17 +2485,27 @@
             if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
                     && cr.binding.service.app != null
                     && cr.binding.service.app.lruSeq != mLruSeq
+                    && (cr.flags & Context.BIND_REDUCTION_FLAGS) == 0
                     && !cr.binding.service.app.isPersistent()) {
-                nextIndex = updateLruProcessInternalLocked(cr.binding.service.app,
-                        now,
-                        nextIndex,
-                        "service connection", cr, app);
+                if (cr.binding.service.app.hasClientActivities()) {
+                    if (nextActivityIndex >= 0) {
+                        nextActivityIndex = updateLruProcessInternalLocked(cr.binding.service.app,
+                                now,
+                                nextActivityIndex, mLruSeq,
+                                "service connection", cr, app);
+                    }
+                } else {
+                    nextIndex = updateLruProcessInternalLocked(cr.binding.service.app,
+                            now,
+                            nextIndex, mLruSeq,
+                            "service connection", cr, app);
+                }
             }
         }
         for (int j = app.conProviders.size() - 1; j >= 0; j--) {
             ContentProviderRecord cpr = app.conProviders.get(j).provider;
             if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.isPersistent()) {
-                nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex,
+                nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex, mLruSeq,
                         "provider reference", cpr, app);
             }
         }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index faf8561..013de93 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -155,6 +155,9 @@
     int pssStatType;            // The type of stat collection that we are currently requesting
     int savedPriority;          // Previous priority value if we're switching to non-SCHED_OTHER
     int renderThreadTid;        // TID for RenderThread
+    ServiceRecord connectionService; // Service that applied current connectionGroup/Importance
+    int connectionGroup;        // Last group set by a connection
+    int connectionImportance;   // Last importance set by a connection
     boolean serviceb;           // Process currently is on the service B list
     boolean serviceHighRam;     // We are forcing to service B list due to its RAM use
     boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle?
@@ -396,6 +399,11 @@
                     pw.print(" hasAboveClient="); pw.print(hasAboveClient);
                     pw.print(" treatLikeActivity="); pw.println(treatLikeActivity);
         }
+        if (connectionService != null || connectionGroup != 0) {
+            pw.print(prefix); pw.print("connectionGroup="); pw.print(connectionGroup);
+            pw.print(" Importance="); pw.print(connectionImportance);
+            pw.print(" Service="); pw.println(connectionService);
+        }
         if (hasTopUi() || hasOverlayUi() || runningRemoteAnimation) {
             pw.print(prefix); pw.print("hasTopUi="); pw.print(hasTopUi());
                     pw.print(" hasOverlayUi="); pw.print(hasOverlayUi());
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index f91d74a..7842a1c 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -135,6 +135,7 @@
     method public boolean stopService(android.content.Intent);
     method public void unbindService(android.content.ServiceConnection);
     method public void unregisterReceiver(android.content.BroadcastReceiver);
+    method public void updateServiceGroup(android.content.ServiceConnection, int, int);
   }
 
   public deprecated class MockCursor implements android.database.Cursor {
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 66be6d9..ae6cd29 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -591,6 +591,11 @@
     }
 
     @Override
+    public void updateServiceGroup(ServiceConnection conn, int group, int importance) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void unbindService(ServiceConnection conn) {
         throw new UnsupportedOperationException();
     }