Merge "Rate limit calls to AM.getMemoryInfo()." into main
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b83be6b..b4fb480 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -88,6 +88,7 @@
 import android.view.WindowInsetsController.Appearance;
 import android.window.TaskSnapshot;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.LocalePicker;
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.os.RoSystemProperties;
@@ -238,6 +239,14 @@
     private static final RateLimitingCache<List<ProcessErrorStateInfo>> mErrorProcessesCache =
             new RateLimitingCache<>(10, 2);
 
+    /** Rate-Limiting cache that allows no more than 100 calls to the service per second. */
+    @GuardedBy("mMemoryInfoCache")
+    private static final RateLimitingCache<MemoryInfo> mMemoryInfoCache =
+            new RateLimitingCache<>(10);
+    /** Used to store cached results for rate-limited calls to getMemoryInfo(). */
+    @GuardedBy("mMemoryInfoCache")
+    private static final MemoryInfo mRateLimitedMemInfo = new MemoryInfo();
+
     /**
      * 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.
@@ -3510,6 +3519,19 @@
             foregroundAppThreshold = source.readLong();
         }
 
+        /** @hide */
+        public void copyTo(MemoryInfo other) {
+            other.advertisedMem = advertisedMem;
+            other.availMem = availMem;
+            other.totalMem = totalMem;
+            other.threshold = threshold;
+            other.lowMemory = lowMemory;
+            other.hiddenAppThreshold = hiddenAppThreshold;
+            other.secondaryServerThreshold = secondaryServerThreshold;
+            other.visibleAppThreshold = visibleAppThreshold;
+            other.foregroundAppThreshold = foregroundAppThreshold;
+        }
+
         public static final @android.annotation.NonNull Creator<MemoryInfo> CREATOR
                 = new Creator<MemoryInfo>() {
             public MemoryInfo createFromParcel(Parcel source) {
@@ -3536,6 +3558,20 @@
      * manage its memory.
      */
     public void getMemoryInfo(MemoryInfo outInfo) {
+        if (Flags.rateLimitGetMemoryInfo()) {
+            synchronized (mMemoryInfoCache) {
+                mMemoryInfoCache.get(() -> {
+                    getMemoryInfoInternal(mRateLimitedMemInfo);
+                    return mRateLimitedMemInfo;
+                });
+                mRateLimitedMemInfo.copyTo(outInfo);
+            }
+        } else {
+            getMemoryInfoInternal(outInfo);
+        }
+    }
+
+    private void getMemoryInfoInternal(MemoryInfo outInfo) {
         try {
             getService().getMemoryInfo(outInfo);
         } catch (RemoteException e) {
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 4d61f41..c0c81df 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -125,3 +125,14 @@
          purpose: PURPOSE_BUGFIX
      }
 }
+
+flag {
+     namespace: "backstage_power"
+     name: "rate_limit_get_memory_info"
+     description: "Rate limit calls to getMemoryInfo using a cache"
+     is_fixed_read_only: true
+     bug: "364312431"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}