A Dynamic Island for Pixel, built in Material 3 Expressive.
Islet puts a small black pill exactly over your phone's camera cutout. At rest it is invisible — it is the same black as the panel around the lens. When something happens it grows: album art and a live equaliser while music plays, a peek when a notification lands, a battery reading when you plug in. Tap it and it opens into a full card with controls.
No ads. No accounts. No in-app purchases. No analytics. No internet permission at
all — check AndroidManifest.xml, there isn't one, so nothing Islet reads can leave
the device.
This is the part that usually goes wrong, so it is worth saying how it is done.
Islet has no per-model lookup table. Pixel cutouts are not consistent — the 4a, 5, 5a and 6a punch in the top left, the 6, 7, 8, 9 and 10 punch in the centre, and the diameter moves by a few pixels between generations. Any hard-coded table is wrong the day a new phone ships.
Instead the geometry is read from the platform at runtime:
val metrics = windowContext.getSystemService(WindowManager::class.java).currentWindowMetrics
val cutout = metrics.windowInsets.displayCutoutDisplayCutout.getBoundingRectTop() is the exact rectangle of the hole in display
pixels. Islet centres its window on that rectangle's centre, not on the centre of the
screen — which is what makes the left-punch models work — and re-measures on every
configuration change, rotation and display change.
The window itself is the other half of the trick:
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
x = cutout.centerX - display.width / 2
layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
flags = FLAG_LAYOUT_NO_LIMITS or FLAG_LAYOUT_IN_SCREEN or …Because the window uses centre gravity with a fixed offset, the island stays pinned to the lens as it grows and shrinks — there is no per-frame repositioning to lag behind an animation.
If you use a screen protector with an oversized cutout, or you just want it a hair lower, Settings has nudge and bleed sliders plus an alignment guide that outlines exactly what the platform reported, so you can see what Islet sees.
| Now playing | Artwork, title, artist, wavy progress bar, tap-to-seek, previous / play / next. Works with any player that publishes a MediaSession, which is all of them. |
| Notifications | A short peek with app, title, body and up to three actions. Anything you have silenced at the channel level stays silent here too — Islet reads the ranking and respects it. |
| Charging | A brief battery peek on plug and unplug. |
Tap a peek to pin it open. Swipe up to send it away. Swipe down to expand.
- Material 3 Expressive throughout —
MaterialExpressiveTheme, the expressive motion scheme for every transition, andLinearWavyProgressIndicatorfor playback. - Material You colour. The settings app takes the wallpaper palette. The island takes its accent from the album art (with a luminance floor, because a brand colour picked to sit on white disappears on a black pill), falling back to the wallpaper palette when there is no art.
- The resting state is nothing. No glow, no border, no "app is running" hint. If it is not telling you something, you should not be able to see it.
- Idle means idle. With nothing to show, the window sets
FLAG_NOT_TOUCHABLE, so swiping down over the camera pulls the shade exactly as it does without Islet installed. (There is a setting if you would rather it were always tappable.) - The equaliser is synthetic. A real spectrum would mean the
RECORD_AUDIOpermission, which is an absurd price for a decoration.
| Permission | Why |
|---|---|
SYSTEM_ALERT_WINDOW |
Draw the one overlay window. |
| Notification access | The single grant that provides both the notification feed and MediaSessionManager active sessions. |
FOREGROUND_SERVICE_SPECIAL_USE |
Keep the overlay resident so it reacts instantly. |
POST_NOTIFICATIONS |
Somewhere to put the minimum-importance "running" notice Android requires. |
RECEIVE_BOOT_COMPLETED |
Come back after a reboot. |
VIBRATE |
Haptics on tap. Optional in Settings. |
There is deliberately no INTERNET, no QUERY_ALL_PACKAGES (a <queries> block covers
app labels) and no analytics SDK of any kind.
Grab the APK from Releases and install it.
- Open Islet.
- Grant Display over other apps and Notification access.
- Flip the switch.
A Quick Settings tile is included if you want to drop the island for a game or a film.
git clone https://github.com/dylanneve1/islet
cd islet
./gradlew assembleDebugRequires JDK 17+ and the Android SDK with API 37 (Android 17). Release builds are signed
with a keystore described in a git-ignored keystore.properties; without one,
assembleRelease falls back to the debug key so the build still works from a clean
clone.
minSdk31 (Android 12 — where Material You starts)targetSdk/compileSdk37 (Android 17)- Kotlin, Jetpack Compose, Material 3
1.5.0-alpha28 - ~2 MB release APK, no third-party runtime dependencies beyond AndroidX
- Expanded cards sit under the status bar.
TYPE_APPLICATION_OVERLAYwindows are z-ordered below the system status bar, so the clock and icons draw on top of the card while it is open. On a light-icon status bar this looks intentional; on a dark-icon one the icons are hard to read for the couple of seconds a peek lasts. The compact island is deliberately narrow enough to stay clear of both. - Landscape hides the island by default, because the cutout is no longer at the top of the screen. You can turn that off.
- No text replies yet. Notification actions that open a remote-input field are shown and will launch the app rather than replying inline.
Apache 2.0. See LICENSE.