Merge "freezer: add internal per-process callbacks" into main
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 83c3bf6..89efa9b 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -55,6 +55,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.BiFunction;
 
 /**
@@ -1268,6 +1269,28 @@
     public abstract boolean shouldDelayHomeLaunch(int userId);
 
     /**
+     * Used to track when a process is frozen or unfrozen.
+     */
+    public interface FrozenProcessListener {
+        /**
+         * Called when a process is frozen.
+         */
+        void onProcessFrozen(int pid);
+
+        /**
+         * Called when a process is unfrozen.
+         */
+        void onProcessUnfrozen(int pid);
+    }
+
+    /**
+     * Register the frozen process event listener callback. The same listener may be reused for
+     * multiple pids. Listeners are dropped when the process dies.
+     */
+    public abstract void addFrozenProcessListener(int pid, @NonNull Executor executor,
+            @NonNull FrozenProcessListener listener);
+
+    /**
      * Add a startup timestamp to the most recent start of the specified process.
      *
      * @param key The {@link ApplicationStartInfo} start timestamp key of the timestamp to add.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 33f33fb..9d8f337 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18444,6 +18444,23 @@
             implements ActivityManagerLocal {
 
         @Override
+        public void addFrozenProcessListener(int pid, @NonNull Executor executor,
+                @NonNull FrozenProcessListener listener) {
+            Objects.requireNonNull(executor);
+            Objects.requireNonNull(listener);
+            synchronized (mProcLock) {
+                final ProcessRecord app;
+                synchronized (mPidsSelfLocked) {
+                    app = mPidsSelfLocked.get(pid);
+                }
+                if (app != null) {
+                    mOomAdjuster.mCachedAppOptimizer.addFrozenProcessListener(app, executor,
+                            listener);
+                }
+            }
+        }
+
+        @Override
         public List<PendingIntentStats> getPendingIntentStats() {
             return mPendingIntentController.dumpPendingIntentStatsForStatsd();
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index bbd4323..0e19347 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1234,8 +1234,7 @@
                 if (freeze) {
                     mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(proc);
                 } else {
-                    mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(proc, 0,
-                            true);
+                    mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppLSP(proc, 0, true);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 11e8353..8f52f67 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -49,6 +49,7 @@
 import android.annotation.IntDef;
 import android.annotation.UptimeMillisLong;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.FrozenProcessListener;
 import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.ApplicationExitInfo;
@@ -99,6 +100,7 @@
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 public final class CachedAppOptimizer {
 
@@ -1406,8 +1408,15 @@
         }
     }
 
+    /**
+     * Returns true if the app was frozen and became unfrozen, otherwise false.
+     *
+     * Do not call this directly.  It will unfreeze a process but it will not send out any
+     * notifications.  Instead call unfreezeAppLSP().
+     */
     @GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
-    void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
+    private boolean unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason,
+            boolean force) {
         final int pid = app.getPid();
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         boolean sticky = opt.isFreezeSticky();
@@ -1418,7 +1427,7 @@
                         "Skip unfreezing because frozen state is sticky pid=" + pid + " "
                                 + app.processName);
             }
-            return;
+            return false;
         }
         boolean processFreezableChangeReported = false;
         if (opt.isPendingFreeze()) {
@@ -1440,7 +1449,7 @@
 
         opt.setFreezerOverride(false);
         if (pid == 0 || !opt.isFrozen()) {
-            return;
+            return false;
         }
 
         // Unfreeze the binder interface first, to avoid transactions triggered by timers fired
@@ -1473,7 +1482,7 @@
         }
 
         if (processKilled) {
-            return;
+            return false;
         }
         if (!processFreezableChangeReported) {
             reportProcessFreezableChangedLocked(app);
@@ -1489,7 +1498,7 @@
             app.killLocked("Unable to unfreeze",
                     ApplicationExitInfo.REASON_FREEZER,
                     ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
-            return;
+            return false;
         }
 
         try {
@@ -1512,14 +1521,25 @@
                         pid,
                         (int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
                         new Pair<ProcessRecord, Integer>(app, reason)));
+            return true;
+        }
+        return false;
+    }
+
+    @GuardedBy({"mAm", "mProcLock"})
+    void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
+        final boolean shouldDispatch;
+        synchronized (mFreezerLock) {
+            shouldDispatch = unfreezeAppInternalLSP(app, reason, force);
+        }
+        if (shouldDispatch) {
+            app.mOptRecord.dispatchUnfrozenEvent();
         }
     }
 
     @GuardedBy({"mAm", "mProcLock"})
     void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
-        synchronized (mFreezerLock) {
-            unfreezeAppInternalLSP(app, reason, false);
-        }
+        unfreezeAppLSP(app, reason, false);
     }
 
     /**
@@ -1530,8 +1550,9 @@
      * @param pid pid of the process to be unfrozen
      */
     void unfreezeProcess(int pid, @OomAdjReason int reason) {
+        final ProcessRecord app;
         synchronized (mFreezerLock) {
-            ProcessRecord app = mFrozenProcesses.get(pid);
+            app = mFrozenProcesses.get(pid);
             if (app == null) {
                 return;
             }
@@ -1550,6 +1571,7 @@
                 Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
             }
         }
+        app.mOptRecord.dispatchUnfrozenEvent();
     }
 
     /**
@@ -2390,6 +2412,7 @@
                     }
                 });
             }
+            opt.dispatchFrozenEvent();
         }
 
         private void reportUnfreeze(ProcessRecord app, int pid, int frozenDuration,
@@ -2549,7 +2572,7 @@
                 if (freeze) {
                     forceFreezeAppAsyncLSP(proc);
                 } else {
-                    unfreezeAppInternalLSP(proc, UNFREEZE_REASON_NONE, true);
+                    unfreezeAppLSP(proc, UNFREEZE_REASON_NONE, true);
                 }
             }
         }
@@ -2652,4 +2675,12 @@
                 exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats"));
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
+
+    /**
+     * Register a callback to notify when a process's frozen state changes.
+     */
+    public void addFrozenProcessListener(ProcessRecord app, Executor executor,
+            FrozenProcessListener listener) {
+        app.mOptRecord.addFrozenProcessListener(executor, listener);
+    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 53643b8..57a5e3f 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -19,6 +19,8 @@
 import android.annotation.IntDef;
 import android.annotation.UptimeMillisLong;
 import android.app.ActivityManagerInternal.OomAdjReason;
+import android.app.ActivityManagerInternal.FrozenProcessListener;
+import android.util.Pair;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
@@ -29,6 +31,8 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
 
 /**
  * The state info of app when it's cached, used by the optimizer.
@@ -165,6 +169,12 @@
     @GuardedBy("mProcLock")
     private long mLastUsedTimeout;
 
+    /**
+     * The list of callbacks for this process whenever it is frozen or unfrozen.
+     */
+    final CopyOnWriteArrayList<Pair<Executor, FrozenProcessListener>> mFrozenProcessListeners =
+            new CopyOnWriteArrayList<>();
+
     @GuardedBy("mProcLock")
     long getLastCompactTime() {
         return mLastCompactTime;
@@ -386,6 +396,22 @@
         mFreezeExempt = exempt;
     }
 
+    void addFrozenProcessListener(Executor executor, FrozenProcessListener listener) {
+        mFrozenProcessListeners.add(new Pair<Executor, FrozenProcessListener>(executor, listener));
+    }
+
+    void dispatchFrozenEvent() {
+        mFrozenProcessListeners.forEach((pair) -> {
+            pair.first.execute(() -> pair.second.onProcessFrozen(mApp.mPid));
+        });
+    }
+
+    void dispatchUnfrozenEvent() {
+        mFrozenProcessListeners.forEach((pair) -> {
+            pair.first.execute(() -> pair.second.onProcessUnfrozen(mApp.mPid));
+        });
+    }
+
     ProcessCachedOptimizerRecord(ProcessRecord app) {
         mApp = app;
         mProcLock = app.mService.mProcLock;
@@ -409,6 +435,10 @@
         pw.print(" " + IS_FROZEN + "="); pw.println(mFrozen);
         pw.print(prefix); pw.print("earliestFreezableTimeMs=");
         TimeUtils.formatDuration(mEarliestFreezableTimeMillis, nowUptime, pw);
+        if (!mFrozenProcessListeners.isEmpty()) {
+            pw.print(" mFrozenProcessListeners=");
+            mFrozenProcessListeners.forEach((pair) -> pw.print(pair.second + ", "));
+        }
         pw.println();
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 32ff569..d203de5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -21,6 +21,7 @@
 import static com.android.server.am.ActivityManagerService.Injector;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -33,6 +34,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.FrozenProcessListener;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -1037,6 +1040,53 @@
         assertTrue(mFreezeCounter.await(5, TimeUnit.SECONDS));
     }
 
+    @Test
+    public void testFrozenNotifier() throws Exception {
+        mUseFreezer = true;
+        mProcessDependencies.setRss(new long[] {
+                    0 /*total_rss*/,
+                    0 /*file*/,
+                    0 /*anon*/,
+                    0 /*swap*/,
+                    0 /*shmem*/
+                });
+
+        // Force the system to use the freezer
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+                CachedAppOptimizer.KEY_USE_FREEZER, "true", false);
+        mCachedAppOptimizerUnderTest.init();
+        initActivityManagerService();
+
+        assertTrue(mAms.isAppFreezerSupported());
+        assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isTrue();
+
+        int pid = 10000;
+        int uid = 2;
+        int pkgUid = 3;
+        ProcessRecord app = makeProcessRecord(pid, uid, pkgUid, "p1", "app1");
+        assertNotNull(app.mOptRecord);
+
+        FrozenProcessListener listener = new FrozenProcessListener() {
+                @Override
+                public void onProcessFrozen(int pid) {
+                    mFreezeCounter.countDown();
+                }
+                @Override
+                public void onProcessUnfrozen(int pid) {
+                    mFreezeCounter.countDown();
+                }
+            };
+        mCachedAppOptimizerUnderTest.addFrozenProcessListener(app, directExecutor(), listener);
+
+        mFreezeCounter = new CountDownLatch(2);
+        mCachedAppOptimizerUnderTest.forceFreezeForTest(app, true);
+        assertTrue(mFreezeCounter.await(5, TimeUnit.SECONDS));
+
+        mFreezeCounter = new CountDownLatch(2);
+        mCachedAppOptimizerUnderTest.forceFreezeForTest(app, false);
+        assertTrue(mFreezeCounter.await(5, TimeUnit.SECONDS));
+    }
+
     private void setFlag(String key, String value, boolean defaultValue) throws Exception {
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, key, value, defaultValue);