Rate limit calls to AM.getMyMemoryState().

Apps do not need to call getMyMemoryState any more than 200 times per
second. Rate limit this API to 2 calls every 10ms which will allow
apps to keep getting updated info at a reasonable interval.

Flag: android.app.rate_limit_get_my_memory_state
Bug: 365182205
Test: atest ActivityManagerTest
Test: manually check logs for cache hits/misses
Change-Id: I0ab53c8f477dc5a82c812d1fe541491e04367cc9
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b4fb480..7273e64 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -247,6 +247,14 @@
     @GuardedBy("mMemoryInfoCache")
     private static final MemoryInfo mRateLimitedMemInfo = new MemoryInfo();
 
+    /** Rate-Limiting cache that allows no more than 200 calls to the service per second. */
+    @GuardedBy("mMyMemoryStateCache")
+    private static final RateLimitingCache<RunningAppProcessInfo> mMyMemoryStateCache =
+            new RateLimitingCache<>(10, 2);
+    /** Used to store cached results for rate-limited calls to getMyMemoryState(). */
+    @GuardedBy("mMyMemoryStateCache")
+    private static final RunningAppProcessInfo mRateLimitedMemState = new RunningAppProcessInfo();
+
     /**
      * Query handler for mGetCurrentUserIdCache - returns a cached value of the current foreground
      * user id if the backstage_power/android.app.cache_get_current_user_id flag is enabled.
@@ -4223,6 +4231,23 @@
             lastActivityTime = source.readLong();
         }
 
+        /**
+         * Note: only fields that are updated in ProcessList.fillInProcMemInfoLOSP() are copied.
+         * @hide
+         */
+        public void copyTo(RunningAppProcessInfo other) {
+            other.pid = pid;
+            other.uid = uid;
+            other.flags = flags;
+            other.lastTrimLevel = lastTrimLevel;
+            other.importance = importance;
+            other.lru = lru;
+            other.importanceReasonCode = importanceReasonCode;
+            other.processState = processState;
+            other.isFocused = isFocused;
+            other.lastActivityTime = lastActivityTime;
+        }
+
         public static final @android.annotation.NonNull Creator<RunningAppProcessInfo> CREATOR =
             new Creator<RunningAppProcessInfo>() {
             public RunningAppProcessInfo createFromParcel(Parcel source) {
@@ -4854,7 +4879,21 @@
      * {@link RunningAppProcessInfo#lru}, and
      * {@link RunningAppProcessInfo#importanceReasonCode}.
      */
-    static public void getMyMemoryState(RunningAppProcessInfo outState) {
+    public static void getMyMemoryState(RunningAppProcessInfo outState) {
+        if (Flags.rateLimitGetMyMemoryState()) {
+            synchronized (mMyMemoryStateCache) {
+                mMyMemoryStateCache.get(() -> {
+                    getMyMemoryStateInternal(mRateLimitedMemState);
+                    return mRateLimitedMemState;
+                });
+                mRateLimitedMemState.copyTo(outState);
+            }
+        } else {
+            getMyMemoryStateInternal(outState);
+        }
+    }
+
+    private static void getMyMemoryStateInternal(RunningAppProcessInfo outState) {
         try {
             getService().getMyMemoryState(outState);
         } catch (RemoteException e) {
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index c0c81df..38bd576 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -136,3 +136,14 @@
          purpose: PURPOSE_BUGFIX
      }
 }
+
+flag {
+     namespace: "backstage_power"
+     name: "rate_limit_get_my_memory_state"
+     description: "Rate limit calls to getMyMemoryState using a cache"
+     is_fixed_read_only: true
+     bug: "365182205"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5b4e573..e54e430 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4121,19 +4121,6 @@
         return false;
     }
 
-    private static int procStateToImportance(int procState, int memAdj,
-            ActivityManager.RunningAppProcessInfo currApp,
-            int clientTargetSdk) {
-        int imp = ActivityManager.RunningAppProcessInfo.procStateToImportanceForTargetSdk(
-                procState, clientTargetSdk);
-        if (imp == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
-            currApp.lru = memAdj;
-        } else {
-            currApp.lru = 0;
-        }
-        return imp;
-    }
-
     @GuardedBy(anyOf = {"mService", "mProcLock"})
     void fillInProcMemInfoLOSP(ProcessRecord app,
             ActivityManager.RunningAppProcessInfo outInfo,
@@ -4151,14 +4138,20 @@
         }
         outInfo.lastTrimLevel = app.mProfile.getTrimMemoryLevel();
         final ProcessStateRecord state = app.mState;
-        int adj = state.getCurAdj();
-        int procState = state.getCurProcState();
-        outInfo.importance = procStateToImportance(procState, adj, outInfo,
-                clientTargetSdk);
+        final int procState = state.getCurProcState();
+        outInfo.importance = ActivityManager.RunningAppProcessInfo
+                                .procStateToImportanceForTargetSdk(procState, clientTargetSdk);
+        if (outInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
+            outInfo.lru = state.getCurAdj();
+        } else {
+            outInfo.lru = 0;
+        }
         outInfo.importanceReasonCode = state.getAdjTypeCode();
         outInfo.processState = procState;
         outInfo.isFocused = (app == mService.getTopApp());
         outInfo.lastActivityTime = app.getLastActivityTime();
+        // Note: ActivityManager$RunningAppProcessInfo.copyTo() must be updated if what gets
+        // "filled into" outInfo in this method changes.
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})