# Binder Observer
`ActivityManager::open_with_observer()` registers the calling process as an
`IProcessObserver` with Android's ActivityManager. On success it returns
`(Self, OwnedFd)` — an owned duplicate of the observer's wake eventfd that
becomes readable whenever `onForegroundActivitiesChanged` fires. The core
retains the original fd; the caller may close its duplicate without breaking
the wake path.
## Observer variants and query surface
`ActivityManager` offers four open modes and three focused-task queries:
- `open()` — polling mode; no observer, no thread pool. Carries the focused-task
query code (see below).
- `open_with_observer()` — `IProcessObserver`, pure wake-up on
`onForegroundActivitiesChanged` (never parses arguments).
- `open_with_fgproc_observer()` — `IForegroundProcessObserver` with a
pid-first custom-ROM fallback (`resolve_fgproc_codes_fallback`). Parses the
callback argument; in the dual-layout mode it suppresses the eventfd write on
background transitions (`fg == 0`), so the fd only fires on foreground.
- `open_with_uid_observer(watched_uid)` — `IUidObserver` scoped to one uid
(`registerUidObserverForUids`); packs `(uid, kind)` into `last_uid_event()`.
- `get_focused_task()` — `(task_id, top_activity_package)` from one transaction.
- `get_focused_package()` / `get_focused_task_id()` — the same data split.
- `last_foreground_pid()` / `last_uid_event()` — free functions reading the
most recent fgproc/uid callback state.
**Caveat**: only polling `open()` and `open_with_observer()` carry a usable
focused-task query code. Instances created with the fgproc/uid observers
transact code 0 on `get_focused_task*` — do not call them on such an instance.
`TxCodes { observer_code, query_code, api_mode, fg_code }` / `resolve_tx_codes()`
expose the resolved codes; `api_mode` is 1 (RootTaskInfo) or 2 (legacy
StackInfo, pre-Q).
## Usage
Add the returned fd to an epoll reactor. On event:
1. Drain the eventfd (read 8 bytes).
2. Call `get_focused_package()` on the **caller's thread** — the observer
callback itself never queries; it only signals the eventfd. The focused-task
transaction is a separate synchronous query re-issued on demand, so data is
fresh relative to the wake but runs on the caller's thread.
## Transaction code resolution
Codes are resolved at runtime by parsing `TRANSACTION_*` static `int` fields
from `IActivityManager`, `IProcessObserver`, `IForegroundProcessObserver`,
`IUidObserver`, `IWindowManager`/`ITaskFpsCallback`, `IDisplayManager`/
`IPowerManager`, `IContentProvider`, and `IActivityTaskManager` directly from
the ZIP STORED DEX entries in `framework.jar`, with no subprocess or
`app_process` required.
Resolution re-runs on **every** `open*` call — resolved values land in
process-wide statics, but there is no persistent startup cache. The focused-task
query has a dual path: `getFocusedRootTaskInfo` (api_mode 1) with a
`getFocusedStackInfo` fallback (api_mode 2) for pre-Q frameworks.
## Lifetime
The binder thread pool (`ABinderProcess_joinThreadPool`) runs on a background
thread for the process lifetime. It is started by every `open_with_*` /
observer path **except** the polling `ActivityManager::open()` and
`Handoff::open()`. The loaded `libbinder_ndk.so` is intentionally never
unloaded — `dlclose` while the thread pool executes from the library causes
use-after-free. This is safe for daemon processes.
## Accessibility
`open_with_observer` requires:
- `/dev/binder` accessible to the calling process
- SELinux policy permits `registerProcessObserver` on `IActivityManager`
- `activity` service reachable via service manager
On rooted devices this typically succeeds. Failure surfaces as a `CoreError`
from `open_with_observer` so callers can fall back gracefully.
## Acknowledgement
The `binder` subsystem is co-owned with
**[sehan64](https://github.com/sehan64)**.