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);