Update async API guidelines for cached+frozen app processes

Add guidance on pausing notifications to apps when cached vs when frozen.
Add more examples, with code.

Per comment from timmurray:

PiperOrigin-RevId: 606251376
Change-Id: I89f233f23d39a0fbb1893ae7d05c033046c3f518
diff --git a/api-guidelines/async.md b/api-guidelines/async.md
index 90ffd1a..4ca1c16 100644
--- a/api-guidelines/async.md
+++ b/api-guidelines/async.md
@@ -147,55 +147,123 @@
 longer needed permit stopping that work before it can consume further system
-### Interaction with the Cached Apps Freezer
+### Special considerations for Cached or Frozen apps
 When designing asynchronous APIs where callbacks originate in a system process
-and are delivered to apps, consider the effect of the
-[Cached Apps Freezer](https://source.android.com/docs/core/perf/cached-apps-freezer).
+and are delivered to apps, consider the following:
-Application processes that are in the cached state may be frozen at any time.
-Threads in frozen app processes will not receive any CPU time until the app is
-brought out of the cached state, for instance if the user interacts with the app
-again. App processes may spend hours or even days at a time in the freezer.
+1.  [Processes and app lifecycle](https://developer.android.com/guide/components/activities/process-lifecycle):
+    the recipient app process may be in the cached state.
+2.  [Cached Apps Freezer](https://source.android.com/docs/core/perf/cached-apps-freezer):
+    the recipient app process may be frozen.
-App developers are encouraged to unregister callbacks when they exit lifecycle
-states (such that they may enter the cached state), but they often neglect to do
-so. Do not assume that the app registers and unregisters your callback according
-to its lifecycle and state. When notifying app callbacks from system server,
-follow these guidelines:
+When an app process enters the cached state, this means that it's not currently
+hosting any user-visible components such as Activities and Services. The app is
+kept in memory in case it becomes user-visible again, but in the meantime should
+not be doing work. In most cases, you should pause dispatching app callbacks
+when that app enters the cached state and resume when the app exits the cached
+state, so as to not induce work in cached app processes.
-1.  Register to be notified when client apps are frozen and unfrozen.
+A cached app may also be frozen. When an app is frozen, it receives zero CPU
+time and is not able to do any work at all. Any calls to that app's registered
+callbacks will be buffered and delivered when the app is unfrozen.
+Buffered transactions to app callbacks may be stale by the time that the app is
+unfrozen and processes them. The buffer is finite, and if overflown would cause
+the recipient app to crash. To avoid overwhelming apps with stale events or
+overflowing their buffers, don't dispatch app callbacks while their process is
+In review:
+*   You should *consider* pausing dispatching app callbacks while the app's
+    process is cached.
+*   You *MUST* pause dispatching app callbacks while the app's process is
+    frozen.
+#### Registering for all states
+To track when apps enters or exit the cached state:
+    new UidImportanceListener() { ... },
+For example, see
+[ag/20754479 Defer sending display events to cached apps](https://googleplex-android-review.git.corp.google.com/c/platform/frameworks/base/+/20754479).
+To track when apps are frozen or unfrozen:
 ActivityManager.registerUidFrozenStateChangedCallback(executor, callback);
-1.  Do not notify app callbacks while the app is frozen.
-2.  When an app is unfrozen, resume notifying the app.
-3.  Consider immediately sending the app "catch up" events when it's unfrozen.
+<!-- TODO(shayba): add an example change once such a change exists. -->
+<!-- TODO(shayba): replace with per-pid APIs after we've added them. -->
-When sending "catch up" events to an unfrozen app, you should send the fewest
-number of events to avoid overwhelming the app. You should not notify the app of
-events that began and ended while it was frozen. When notifying the app of state
-changes that happened while it was frozen, it's acceptable to deliver those
-events outside of their chronological order.
+#### Strategies for resuming dispatching app callbacks
-*   If the state is expressed as a single value, immediately notify the app of
-    the latest value. For instance, if the app is registered to listen to screen
-    brightness changes, notify the app of the current screen brightness upon the
-    app being unfrozen, rather than wait for the next actual screen brightness
-    value change.
+Whether you pause dispatching app callbacks when the app enters the cached state
+or the frozen state, when the app exits the respective state you should resume
+dispatching the app's registered callbacks once the app exits the respective
+state until the app has unregistered its callback or the app process dies.
-*   If the state is expressed as multiple stateful objects, notify the app of
-    objects removed, objects added, and objects changed (in this order). For
-    instance, consider an API to listen to wifi networks available. When
-    catching up the app on the current wifi networks available upon the app
-    exiting the cached state, notify the app of previously-available networks
-    that are now unavailable, then notify of still-available networks whose
-    state had changed, then notify of newly-available networks. If there were
-    networks that became available then unavailable while the app was in the
-    cached state, don't notify the app of those - it's the intended outcome for
-    the app to have missed state transitions that happened while it was cached.
+Apps often save updates they received via callbacks as a snapshot of the latest
+state. Consider a hypothetical API for apps to monitor the remaining battery
+interface BatteryListener {
+    void onBatteryPercentageChanged(int newPercentage);
+Apps may cache the last value seen as the current battery percentage remaining.
+For this reason, resuming dispatching is not enough; you should also immediately
+notify the app of the current remaining battery percentage so that it can "catch
+In some cases, you may track the last value delivered to the app so the app
+doesn't need to be notified of the same value once it is unfrozen.
+State may be expressed as more complex data. Consider a hypothetical API for
+apps to be notified of network interfaces:
+interface NetworkListener {
+    void onAvailable(Network network);
+    void onLost(Network network);
+    void onChanged(Network network);
+When pausing notifications to an app, you should remember the set of networks
+and states that the app had last seen. Upon resuming, it's recommended to notify
+the app of old networks that were lost, of new networks that became available,
+and of existing networks whose state had changed - in this order.
+Do not notify the app of networks that were made available and then lost while
+callbacks were paused. Apps should not receive a full account of events that
+happened while they were frozen, and API documentation should not promise to
+deliver event streams uninterrupted outside of explicit lifecycle states. In
+this example, if the app needs to continuously monitor network availability then
+it must remain in a lifecycle state that keeps it from becoming cached or
+In review, you should coalesce events that had happened after pausing and before
+resuming notifications and deliver the latest state to the registered app
+callbacks succinctly.
+#### Considerations for developer documentation
+Delivery of async events may be delayed, either because the sender paused
+delivery for a period of time as shown above or because the recipient app did
+not receive enough device resources to process the event in a timely way.
+Discourage developers from making assumptions on the time between when their app
+is notified of an event and the time that the event actually happened.
 ## Developer expectations for suspending APIs