A floating HUD for sideloaded Meta Portal devices. Draws widgets, banners, mirrored notifications, a live status strip, floating navigation, and a fullscreen Now Playing view on top of any app — no Google Play Services.
- Push banners from ntfy.sh — or your own self-hosted ntfy server (with optional access token) to keep messages private — no Firebase / no FCM
- Mirrored notifications from other apps as overlay banners
- Draggable widgets: clock, weather, battery, sticky note
- Ticker overlay from a real RSS, Atom, or JSON feed, shown along the top or bottom edge, with built-in live source presets (BBC, AP, NPR) plus live finance sources (crypto via CoinGecko, stocks via Stooq)
- Status strip / bottom bar with time, date, foreground app / Portal UI label, weather, battery, ntfy state, live network speed, ISO week, rain-in-next-hour, sunrise/sunset countdown, streaming, VPN, and Wi-Fi indicators, plus optional wider Back / Home / Recents buttons on the right
- Pinned app dock on the strip — up to 8 app icons on the right side of the bar; tap to launch, long-press for App info / Unpin / Move. Reorder in Bottom bar → Pinned apps (drag ≡ or ↑ ↓).
- Strip tap actions — assign opens (Immortal screens, alarms, any app) per segment
- Accent follows foreground app — optional icon-colour tint on the strip background
- Bottom bar hide / restore — eye-off icon collapses the strip (and the ticker that rides with it) to a small expand pill; Show bar if hidden in settings restores it
- Floating nav cluster: Back, Home, Recents, Control Center swipe, Screenshot, Lock
- Portal Mini app switcher fallback when the Portal system has no Recents/Overview UI
- Eight nav styles: Pill segments, Underline indicator, Ghost pill, Floating squares, Dark glass, Icon + label, Colour-coded, and Dot indicator
- Now Playing dock shapes: pick how the docked widget looks — a small cover-art Bubble (four styles: Rounded, Circle, Square, Minimal; three sizes), a floating Strip (cover art, title, artist, slim progress bar, play/pause), or a full-width Edge bar pinned to the top or bottom (source-app logo, mini equaliser, prev / play-pause / next, and elapsed / total time). All of them auto-hide when nothing is playing and reappear the moment audio starts, and tapping any opens the full card
- Fullscreen Now Playing with multiple background visualizers, multiple full-card layouts, transport controls, and a compact/expanded start preference — a live progress bar with elapsed time and track length, the album name, the source-app logo (e.g. Spotify), and a screen-off button next to the close control
- Screensaver that survives the screen saver: a built-in
DreamServicethat re-hosts the Now Playing card (cover art, track / artist, the bouncing-bars equaliser), a large clock / date, and battery on the idle screen — so they stay on-screen while the screen saver is showing, which a floating overlay cannot (Android draws the saver on top of every app overlay). Choose the background — Black, a Photo you pick, or a Web page URL such as an Immich Kiosk feed (keeping your photo source behind the card) — on the new Screensaver tab, then set it as the device screensaver. See Screensaver below - Agenda / calendar widget: a draggable card showing the next few events from a public iCalendar
(
.ics/ webcal) feed, plus an optional next-event line on the status strip - Status strip styles: 19 selectable looks for the strip (Dense Dark, Accented, Three Zones, Segments, Minimal Mono, Two Rows, Frosted Glass, Tinted Chips, Aurora, Daylight, HUD Tactical, Sunset, Ocean, Mono Graphite, OLED Black, E-ink Paper, Iconic, High Contrast, and a dynamic Sky), picked from a "Style" list on the Status strip page — each restyles the bar fill, text and accent colours, separators, and font
- Status strip hide / restore: an eye-off icon collapses the strip (and the ticker that rides with it) to a tiny expand handle so you can read what's underneath, then tap the handle or Show bar if hidden in the app to bring it back
- Overlays auto re-arm: reopening the app restores the overlays automatically — no toggling "running" off and on after the Portal kills the background service
- Screenshot button that saves to the gallery (Pictures/Screenshots) via MediaStore, with an app-storage fallback, instead of crashing on the Portal's Android 9
- Dedicated control tabs for Widgets, Now Playing, Status strip, Ticker, Settings, Notifications, Navigation, Appearance, and About
- Settings page for shared app-wide options like weather location and weather units, so they can be changed without enabling the Weather widget first
- Status strip defaults that start with the strip enabled (top position on fresh installs) and the requested live items on: clock, date, foreground app, weather, network speed, Wi-Fi, week number, rain, sunset / sunrise, and optional strip-mounted Back / Home / Recents buttons
- Startup defaults with Clock off and Now Playing on, so a fresh install opens with the most useful media and strip overlays active
- Customisation: accent colour, opacity, corner radius, text scale, strip position, alert sounds, weather location, and weather units
- Narrow update paths so ticker changes restart only the ticker, while simple widget toggles do not tear down the entire overlay stack
- In-app updates — automatic popup on launch when a new GitHub release is published (toggle in
About → Updates), plus Check for updates any time. Compares
versionCodeagainstversion.jsonand installs over the top (same pattern as Immortal). Immortal's store usesreleases/latest/download/PortalOverlays.apkplus a catalogversionCodebump on each release for update detection.
Open Portal Overlays on the Portal to configure everything. Ten tabs in the left rail:
| Tab | What it controls |
|---|---|
| Widgets | Clock, weather, battery, note, agenda |
| Now Playing | Dock shape, visualizer, full card, history |
| Screensaver | Dream background, cover layout, gestures |
| Bottom bar | Style, pinned apps, tap actions, hide/restore |
| Ticker | RSS/finance feeds, scroll speed |
| Settings | Weather city/units, Labs |
| Notifications | ntfy, mirror, breaking news, sounds |
| Navigation | Floating nav, styles, lock position |
| Appearance | Accent, opacity, corners, text scale |
| About | Permissions, updates |
Search: use Search settings… at the top of the rail (or focus it to browse all sections). Results open in the main panel with descriptions and keyword tags — useful with ten tabs.
Fullscreen Now Playing — album art, source-app logo, album name, live progress / track length, transport controls, plus screen-off and close:
| Widgets | Now Playing | Status strip | Ticker | Settings |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
| Notifications | Navigation | Appearance | About |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
- Meta Portal with ADB enabled
- Node.js 20+ (for
metavr) - JDK 17 + Android SDK (for building from source)
npx -y metavr device list # confirm the Portal is connected$env:JAVA_HOME='C:\Program Files\Android\Android Studio\jbr'
.\gradlew.bat assembleDebug
npx -y metavr app install -r app\build\outputs\apk\debug\app-debug.apk
npx -y metavr app launch com.portal.overlaysThe Portal does not expose these through a normal settings UI, so grant them over ADB. Easiest path on Windows:
.\enable_portal_permissions.batOr one by one:
# Draw over other apps
npx -y metavr adb shell appops set com.portal.overlays SYSTEM_ALERT_WINDOW allow
# Floating nav (Back/Home/Recents)
npx -y metavr adb shell settings put secure enabled_accessibility_services \
com.portal.overlays/com.portal.overlays.NavAccessibilityService
npx -y metavr adb shell settings put secure accessibility_enabled 1
# Notification mirroring + media-session access
npx -y metavr adb shell cmd notification allow_listener \
com.portal.overlays/com.portal.overlays.NotifyListenerServicePortal's
AccessibilityServiceManageronly picks upenabled_accessibility_servicesafter it has been initialised, and it wipes the setting on every boot. Ifdumpsys accessibilitystill showsservices:{}after running the script above, reboot the Portal and re-run the script once:npx -y metavr adb reboot # wait for boot_completed=1, then: .\enable_portal_permissions.batAfter that the service label
Overlayswill appear indumpsys accessibilityand the floating Back/Home/Recents cluster will start working.
Then open the app and turn on Overlays running.
The floating overlays use TYPE_APPLICATION_OVERLAY, the highest window type a sideloaded app may
use — and Android composites a running screen saver / Daydream on top of it. So the now-playing
widget and status strip are hidden the instant the screen saver starts; nothing a floating overlay
does can paint over a screen saver. The only surface that survives one is the screen saver itself.
Portal Overlays therefore ships its own screen saver — a DreamService that re-hosts the content
people most want on the idle screen:
- the Now Playing card (cover art, track / artist, the bouncing-bars equaliser),
- a large clock / date, and a battery readout,
drawn over a background you choose on the Screensaver tab: Black, a Photo you pick, or a Web page URL (e.g. an Immich Kiosk feed, so your photo source stays behind the card). The card reads media sessions through the same notification-listener access the overlays use and only appears while real audio is actually playing.
Gestures on the idle screen (when now playing is shown): tap album art to skip, swipe left/right for prev/next, double-tap the clock to wake. The first time the real screen saver runs, a one-time toast reminds you: "Swipe art · double-tap clock" (the in-app Preview does not consume this hint).
The easiest way is the bundled helper — plug the Portal in (ADB enabled) and run, from a PC:
set_screensaver.batIt auto-detects the Portal, registers this screen saver, allows the notification listener the now-playing card needs, and (if the Immortal launcher is installed) stops Immortal from reclaiming the slot. Switches:
set_screensaver.bat -Revert— hand the screen saver back (re-enables Immortal if present)set_screensaver.bat -KeepImmortal— register without touching Immortal
You can also set it from the app's Screensaver tab ("Open screensaver settings"), or by hand:
adb shell settings put secure screensaver_components com.portal.overlays/.NowPlayingDreamService
adb shell settings put secure screensaver_enabled 1The Immortal launcher re-asserts its own screen saver on boot and on every return to its home screen,
which evicts any other screen saver. That self-healing is a no-op without WRITE_SECURE_SETTINGS, so
set_screensaver.bat revokes that from Immortal for you (undo with -Revert). By hand:
adb shell pm revoke com.immortal.launcher android.permission.WRITE_SECURE_SETTINGS
adb shell settings put secure screensaver_components com.portal.overlays/.NowPlayingDreamService
adb shell settings put secure screensaver_enabled 1To keep your Immich Kiosk feed, set the Screensaver background to Web page and paste the same Kiosk URL — you get the photo feed and the bouncing-bars now-playing on top, both surviving the screen saver.
The visualizer animates whenever music is playing. A React to live audio option still exists under
Settings → Labs for tinkerers, but it is off by default and usually makes the visualizer look
worse on Portal — true audio reaction isn't achievable for a sideloaded app (the output mix is
permission-locked and the mic is preempted by the always-on assistant). See
docs/portal-audio-capture.md.
Pick a long, unguessable topic name, then subscribe to it in the app's Notifications tab. Anything POSTed to https://ntfy.sh/<topic> becomes a banner:
curl -d "Kitchen timer done" https://ntfy.sh/your-topic
curl -H "Title: Doorbell" -H "Priority: high" \
-d "Someone is at the door" https://ntfy.sh/your-topicTo keep messages off the public server, run your own ntfy instance and set the Server URL on the Notifications tab (e.g. https://ntfy.example.com, or a plain http:// LAN address). For a read-protected/private topic, generate an access token (ntfy token add <user>) and paste it into the Access token field — it's sent as a Bearer token. Then publish to your own server:
curl -H "Authorization: Bearer tk_yourtoken" \
-d "Private message" https://ntfy.example.com/your-topicWeather by Open-Meteo. Push by ntfy.sh. Made for the Portal sideloading community.
MIT.










