Android permissions
Every permission Jenny’s manifest declares, why it’s there, and what happens if you refuse or revoke it.
Jenny asks for a short, deliberately narrow list. Two permissions are requested at runtime (notifications, location) with an Android system prompt; the rest are either automatic (granted at install, no prompt) or “special” permissions that route through a system settings screen instead of a normal dialog.
Requested permissions
Section titled “Requested permissions”| Permission | Type | Why Jenny wants it | If you deny it |
|---|---|---|---|
INTERNET |
Normal (install-time, no prompt) | Talking to your configured LLM provider, web search/fetch, Telegram, downloads — essentially everything Jenny does over the network. | Can’t be denied on Android; without it the app cannot function at all. |
ACCESS_NETWORK_STATE |
Normal (install-time) | Lets the app check network connectivity state. | Can’t be denied; no user-visible effect either way. |
FOREGROUND_SERVICE |
Normal (install-time) | Lets the gateway run as a foreground service so it keeps working with the screen off or the app backgrounded. | Can’t be denied; required for the agent to run at all. |
FOREGROUND_SERVICE_SPECIAL_USE |
Normal (install-time) | Declares the foreground service’s category as “special use” (running a local agent runtime doesn’t fit Android’s other foreground-service categories like media playback or navigation). | Can’t be denied; part of how the persistent service is allowed to exist on modern Android. |
FOREGROUND_SERVICE_LOCATION |
Normal (install-time) | Lets the foreground service keep reading location while running in the background (screen off, Telegram-only turns, scheduled/cron turns), without needing the much broader ACCESS_BACKGROUND_LOCATION permission. Access is tied to the service’s lifetime, not a standing background grant. |
Can’t be denied by itself; it only matters if you’ve also granted fine/coarse location below. |
POST_NOTIFICATIONS |
Runtime (Android 13+ only; asked at first run) | Shows the persistent “Jenny ✦ online” foreground-service notification, and any proactive notifications (reminders, heartbeat messages, Telegram activity) when Jenny isn’t in the foreground. | The persistent service notification silently fails to show, and proactive alerts never appear — Jenny keeps running, but you get no visual sign of it and no pings for reminders or proactive messages. No error is shown; you can grant it later from Android’s app-notification settings and Jenny picks it up on the next service start. |
RECEIVE_BOOT_COMPLETED |
Normal (install-time) | Lets a boot receiver relaunch the gateway automatically after the phone restarts — the “server in a drawer” behavior. The same receiver also handles the “quick boot” broadcasts some ROMs send instead of the standard one, and the “my package was replaced” broadcast, so the gateway comes back by itself after an app update rather than staying down until you next open the app by hand. | Can’t be denied; if you don’t want auto-restart after reboot you’d need to disable it at the OS level (e.g. via app battery/autostart settings on some OEM skins) or simply not rely on it. After a reboot Jenny only comes back at the first unlock, by design. She deliberately does not handle the earlier LOCKED_BOOT_COMPLETED broadcast: everything she needs — your workspace, config.json, the Python runtime itself — lives in credential-encrypted storage, which isn’t merely empty before the first unlock, it’s unreachable. Starting there would mean moving your API keys and memory out of the encryption tied to your PIN, which isn’t a trade worth making. A phone that reboots overnight and sits locked until morning is a phone where nothing scheduled runs until you unlock it. |
ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION |
Runtime (asked at first run, since the in-app location toggle defaults to on) | Lets Jenny read device location: a free last-known reading injected into agent context, plus an on-demand fresh GPS fix when explicitly requested. No background location permission is used — location is only read while the foreground service is alive. | Denying it doesn’t break anything or show an error: the location feature simply returns nothing, silently. Jenny keeps working normally without location context. You can grant it later from Android’s per-app permission settings; see Location. |
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS |
Special (no manifest-time prompt; triggered by an in-app button) | Lets Jenny launch the system’s “exempt from battery optimization” screen. Exemption keeps all background activity — reminders, cron jobs, Dream, Heartbeat, and Telegram’s long-poll — from being throttled when the phone isn’t charging and Doze would otherwise clamp down. | If you don’t grant the exemption, Android’s Doze/App Standby can delay background work — reminders, proactive checks and Telegram replies may lag or stall while the screen is off, especially unplugged. Nothing fails outright; things just get slower or less reliable in the background. The button is offered during first-run setup and lives in Settings → Background activity; the Telegram card still offers it too, with its own wording, but it was never Telegram-specific. |
WAKE_LOCK |
Normal (install-time) | Lets Jenny hold a PARTIAL_WAKE_LOCK so the CPU stays powered while she is actually working. A foreground service keeps the process alive, not the processor: with the screen off the device suspends and the agent’s timers stop advancing mid-turn. With the default power.keepAwake: "turns" the lock is taken around real work — an agent turn, a cron/Dream/heartbeat job, an SSH command — and released immediately after; "always" holds it for the whole life of the gateway service. Each per-turn lock carries an OS-side expiry, so a gateway that dies mid-turn cannot leave one held forever; the always lock has no expiry by design and is released when the service is destroyed. |
Can’t be denied; it’s granted at install. What you can change is how much it’s used, with power.keepAwake in Settings → Background activity — off restores the pre-0.6.6 behaviour of never taking one. |
SCHEDULE_EXACT_ALARM |
Special (user-controllable from Android 12; “Alarms & reminders” in Android’s per-app special access) | Lets Jenny ask the OS to wake the phone at a precise moment. This is what makes a scheduled job fire on time after the CPU has been suspended for hours, and what keeps the restart watchdog’s alarm chain punctual. Jenny does not declare USE_EXACT_ALARM, the non-revocable variant reserved for alarm-clock apps. |
Jenny checks the permission at the moment she schedules, and falls back to an inexact alarm that still fires during Doze but may slip — later, not never. Revoking it also makes Android silently drop the exact alarms already queued, with no callback. Jenny does listen for the permission-change broadcast, but does not depend on it: the system only sends it on the granting edge, never on revocation, and on at least one ROM it proved unreliable even there. So the state is also re-checked opportunistically — when you open the app, and when the network comes back — and the alarm chains are re-armed or stood down accordingly. The 8-hourly alarmClockFallback net does not escape this: measured on-device, setAlarmClock throws the same SecurityException as any other exact alarm when the permission is missing, and degrades to the same inexact fallback. Its higher delivery priority is therefore something you only get once the permission is granted. Settings → Background activity reports whether it’s held and, while it isn’t, offers a button that opens Android’s own “Alarms & reminders” screen for Jenny. Granting it is measurable: on the test device it moved the alarm slip windows from as much as an hour down to zero. |
REQUEST_DELETE_PACKAGES |
Normal (install-time) | Lets the Apps tab hand an uninstall to Android’s own uninstaller (ACTION_DELETE). From API 26 the system uninstaller checks this permission on the caller and, if it’s missing, closes immediately without showing anything. It opens no prompt of its own and cannot uninstall anything without your confirmation — that stays in the system dialog. |
Can’t be denied; it’s granted at install. Without it the uninstall entry in the Apps tab would do nothing visible. |
REQUEST_INSTALL_PACKAGES |
Normal (install-time), gated by a separate user switch | The ticket to install anything at all, used by the in-app updater. It is not sufficient on its own: the per-app “Install unknown apps” switch is still yours to grant by hand, and canRequestPackageInstalls() reports whether you have. Without the permission, though, PackageInstaller refuses the session before Android ever gets to ask you. |
Can’t be denied at install time, but the update stays blocked until you also allow “Install unknown apps” for Jenny. Revoking that switch is the effective off switch for in-app installs; updates.enabled: false stops the check itself. |
UPDATE_PACKAGES_WITHOUT_USER_ACTION |
Normal (install-time, Android 12+) | Lets Jenny request an update without a confirmation dialog. It grants nothing broad: it only covers packages you are already the installer of — here, Jenny updating herself (installer uid == package uid). Asking is not getting: the system can still answer STATUS_PENDING_USER_ACTION, and on Android 14+ update ownership made that the norm, so the updater treats the prompt as the main road rather than the exception. Jenny declares neither QUERY_ALL_PACKAGES (not needed — she only updates herself) nor INSTALL_PACKAGES (reserved for system apps and ignored anyway). |
Can’t be denied. In practice you will usually see Android’s install prompt regardless, which is the intended path. |
About the update path
Section titled “About the update path”These three exist for one feature: noticing a new release and installing it. The noticing half is a plain GET of the latest.json published with each release — no identifier, no version, no headers of Jenny’s own, no query string — fetched every 24h and compared on the device. It is the one outbound connection you did not switch on; updates.enabled: false in config.json stops it, and updates.checkIntervalH changes the cadence. See Configuration.
What Jenny deliberately does not ask for
Section titled “What Jenny deliberately does not ask for”| Not requested | What happens instead |
|---|---|
| Camera | Attaching a photo taken on the spot goes through Android’s standard system camera app via an image-capture intent (ACTION_IMAGE_CAPTURE), triggered from the chat attachment picker. Jenny never touches the camera hardware directly and never needs the CAMERA permission. |
Any storage permission (READ_EXTERNAL_STORAGE, MANAGE_EXTERNAL_STORAGE, etc.) |
File attachments, backup export/import, and “save to Downloads” all go through Android’s Storage Access Framework and MediaStore — system pickers and content-resolver APIs that don’t require a storage permission grant at all. Saving a file to the public Downloads folder does require Android 10 (API 29) or newer on the device; Jenny’s manifest supports back to Android 8.0 for everything else. |
| Contacts, SMS, phone state, other apps’ data | Never requested, never used. The sandbox around what Jenny can touch is Android’s own per-app isolation — see Security model. |
App visibility (<queries>)
Section titled “App visibility (<queries>)”Separately from permissions, Jenny’s manifest declares two narrow <queries> entries so PackageManager will tell it about specific other apps, without the broad QUERY_ALL_PACKAGES permission:
- Apps that declare a launcher entry point (
MAIN/LAUNCHER) — this is what powers the “Android apps” launcher tab inside Jenny. See Phone app launcher. - Any camera app that can handle
IMAGE_CAPTURE— this is what lets the attachment chooser offer “take a photo” without Jenny needing the camera permission itself.
Where this fits
Section titled “Where this fits”This table only covers what Android permission system involves. For the layers of the actual sandbox — what Jenny’s own file-access policy allows inside its workspace, what the network/SSRF filter does, and why python_exec isn’t a security boundary by itself — see Security model. For what data ever leaves the device, and to whom, see Privacy.
See also
Section titled “See also”- Install the APK — the build these permissions come from, and the
allowBackupcaveat - First run — what happens the moment the app opens for the first time
- Location — the location toggle and permission interaction in detail
- Configuration reference — the
power.*keys behind the wake lock, the watchdog and the scheduled wake-ups - Scheduling and proactivity — what the wake lock and the alarms actually buy a reminder, and what they don’t
- Telegram bridge — why an inbound Telegram message can still wait for the phone to wake up
- Security model
- Privacy
