An Android framework service accepts requests from clients and applies platform policy before delegating work. This source walkthrough uses Android 14 VibratorManagerService; it is a reading exercise, not a recipe for adding a privileged service.
Before you start#
Required earlier lessons: Binder: app to system service, HAL, Treble, and the vendor boundary, and Android’s security model. The HAL lesson names the lower-level contract first; this lesson traces the framework-side implementation toward it. Basic Java syntax is helpful. If synchronized blocks or callbacks are unfamiliar, use the concurrency explanation below and do not infer behavior from a method name alone.
Learning goals#
By the end, you can draw a request path, identify a trust-boundary check and its identity, explain why cleared Binder identity must be restored, and state what a lock protects without claiming it proves good performance or correctness.
Practice#
Follow the call path
The conceptual route is:
app API → Binder interface → VibratorManagerService
→ policy and arbitration → framework HAL client
→ vendor implementation → driver / actuator
In Android 14 frameworks/base/services/core/java/com/android/server/vibrator/VibratorManagerService.java, the Binder implementation’s vibrate(...) entry point delegates to vibrateInternal(...) (VibratorManagerService.java). The internal method checks VIBRATE, validates a token and effect, and authorizes the supplied UID for app-ops/statistics purposes before the request is scheduled. Those checks answer different questions: may this caller request vibration, is there a usable request lifetime token, may this caller act for that UID, and is the input supported?
The service also considers user settings and ringer-mode policy (VibrationSettings.java) and competing requests. A syntactically valid Binder call may therefore be intentionally suppressed before hardware dispatch. App-side checks alone are not sufficient: a service must enforce policy at its own trust boundary because clients can reach Binder through paths other than a particular SDK wrapper.
Why this belongs in a framework service
Several apps can request one shared actuator at once. User settings, foreground/background behavior, attribution, cancellation, and competing effects require system-wide decisions. Putting those rules in each app would let clients disagree or omit them; putting all Android user policy in each vendor HAL would duplicate framework behavior across hardware vendors. The framework service arbitrates intent and policy; the HAL implements its supported lower-level operations. Binder provides transport between identities, not that arbitration.
The request also carries a lifetime token. A token lets service code associate work/cancellation or death tracking with a client-owned object according to the implementation. It is not a secret permission certificate. Distinguish who may request, which request is being tracked, and how long it remains valid; UID, token, and effect parameters answer different questions.
Synthetic decision trace:
| Request facts | Owning decision | What not to conclude |
|---|---|---|
| Caller lacks required permission | Entry authorization rejects | Not a motor/driver diagnosis |
| Caller supplies another UID | Validate whether this caller may attribute/act for that UID | The UID argument is not authenticated identity |
| Effect malformed or token unusable | Input/lifetime validation rejects | Not necessarily failed Binder transport |
| Valid request conflicts with settings or another vibration | Policy/arbitration may ignore, replace, queue, or cancel under the actual rules | API return is not proof of physical output |
| Request reaches HAL but backend fails | Lower-level implementation/status investigation | Do not weaken framework permission to fix hardware |
The exact chosen policy result must come from the pinned branch; this table classifies decisions rather than inventing universal scheduling behavior.
Identity and shared state
- Incoming Binder identity
Retain caller identity for permission, ownership, and input validation.
- Policy and shared-state decisions
User settings and arbitration can suppress a valid request; coordinated state needs a lock protocol.
- Scoped service-owned work
If clearing calling identity is necessary, do so only after the relevant authorization.
- Restore in finally
Restore the saved identity even when an exception occurs. Clearing identity is not granting permission.
Binder supplies the caller identity during a transaction. The service needs that identity while checking authorization. When it later performs service-owned internal work, it may clear the external calling identity and restore it in a finally block (Binder API reference). Clearing identity does not authorize a request; it changes the identity seen by subsequent operations. Checks first, then clear, and always restore.
The service uses a lock (mLock) to protect shared vibration state. Without coordination, two callers could both observe an idle state and race to become current, or cancellation could overlap a new request. A lock alone does not prove the code is correct. Long-held locks can create contention; nested calls and callbacks can introduce reentrancy or lock-order hazards. To assess performance, collect measurements and trace the actual critical section; this lesson does not claim that the implementation was benchmarked.
Clearing calling identity is not a Linux credential change. The thread's Binder-aware local checks and service-owned work must be interpreted in the right scope; it does not magically delegate the original app's UID through every downstream remote call. Posting work to another thread also does not carry transaction identity automatically. Binder lifetime and deadlocks works through that deferred-identity failure and a synchronous callback lock cycle.
Close the Java-to-HAL gap
After framework scheduling, the Java controller uses native plumbing. The Android 14 VibratorController JNI implementation holds a native HAL controller and delegates operations to it. In the native vibrator service library, connectHal() tries an AIDL VINTF service and has HIDL alternatives; AidlHalWrapper::on invokes the interface and translates its status. This is a real set of implementation boundaries, not “JNI is another IPC.” JNI stays in the hosting process; a remote HAL call then crosses Binder.
The HAL service chapter traces the other endpoint and demonstrates why a working example callback need not move a physical motor. Record each hop separately: Java framework state → JNI/native controller → version-selected wrapper → HAL Binder endpoint → implementation. Product routing and the physical backend remain separate evidence questions.
Build a source evidence record
Open the pinned source URL in Sources. Find vibrate(...), vibrateInternal(...), the permission check, a validation branch, the synchronized state section, and the clearCallingIdentity() / restore pair. Record exact method names and the Android tag. Do not copy a line number from a moving branch.
| Claim | Pinned source evidence | Meaning | Limitation |
|---|---|---|---|
| Binder entry delegates to internal method | |||
| Permission/input checks precede scheduling | |||
| Identity is restored | |||
| Shared state is synchronized |
Source-only completion is acceptable. On a disposable target, adb -s SERIAL shell dumpsys vibrator_manager may add a runtime observation, but a dump is not proof that all request paths work or that a physical actuator exists. Do not issue repeated hardware requests as a lab test.
Knowledge check#
- Why does the service enforce permission if an SDK API has already checked it?
- Does
clearCallingIdentity()let an unauthorized caller make a request? - What race does shared-state locking prevent? Name one risk a lock can introduce.
- What additional evidence is needed before concluding that a slow request is caused by the lock?
- Why keep framework arbitration separate from the HAL, and where does JNI fit?
Answers#
- Binder is a trust boundary; callers need not use the SDK path, so the service checks its own operations.
- No. Authorization must happen using the original caller identity before clearing it; clearing changes identity context, not permission.
- It prevents unsynchronized updates such as two simultaneous requests both winning an idle-state check. Long-held locks can cause contention; callbacks/reentrant paths can create deadlock risks.
- A reproducible workload, timing/trace evidence around the relevant critical section, and comparison against other plausible sources of delay. The source alone does not establish runtime performance.
- Shared Android policy and competing-client decisions belong in the framework; hardware operations belong behind the supported HAL contract. JNI connects Java/native code within the host process before a remote HAL Binder call crosses processes.
Checkpoint#
Pass when your record cites the pinned source methods, puts authorization before identity clearing, explains the lock’s protected state, and lists a limitation. Revise if you treat UID arguments as self-authenticating or identity clearing as an authorization shortcut. Not yet evidenced is the right label for runtime latency or hardware behavior you did not measure.
For IPC mechanics, revisit Binder: app to system service. For the contract boundary, revisit HAL, Treble, and the vendor boundary. For access-control layers, revisit Android’s security model.
Sources#
- AOSP
VibratorManagerService.java, Android 14.0.0_r1 — Binder entry point and request policy. - Binder overview and Binder threading — IPC identity and threading context.
- Binder API reference —
clearCallingIdentity/ restore contract. - Vibrator HAL AIDL, Android 14.0.0_r1 — lower-level interface, distinct from this service.
VibrationSettings.java, Android 14 tag — user settings and ringer-mode state consulted by the service (no Do Not Disturb handling found in this file).