# Current launcher behavior and implementation map This document describes **implemented** behavior, not the wishlist. Paths below are relative to `app/src/main/java/app/indexlauncher/` unless shown from the repository root. Start there when changing behavior; [TODO](../TODO.md) lists active implementation/validation work and [CHANGELOG](../CHANGELOG.md) records completed work. ## Screen and data flow - `MainActivity.kt` is the exported HOME/LAUNCHER entry point. It owns `LauncherViewModel`, repositories, app launching, widget host/gallery/binding/configuration, and the Compose `HomePager`. `SettingsActivity.kt` owns the separate, non-exported settings screen and launches the system static/live wallpaper picker. `MainActivity` sets `FLAG_SHOW_WALLPAPER` rather than reading wallpaper pixels; no storage permission is needed. The Home screen has **no Wallpaper button**. - `ui/HomePager.kt` and `model/HomePages.kt`: physical order `left-N … left-2, left-1, home` (`N` from 0 to 5). Widget 1 is nearest apps; new pages grow left without wrapping. Selection is saved by logical ordinal, so count changes preserve the current page or clamp to the outermost remaining widget. Wide windows independently page widgets on the left and pin the current app mode on the right. Folding returns to apps and remembers the left widget selection. A permanent app composition preserves mode, query, and scroll state without duplicating widget hosts. Compact paging uses an ancestor scrollable and a placeholder app slot, placing the app sibling at its pager position. - `model/HomePaneLayout.kt` chooses two panes when each has at least 320 dp of usable width (increased for large fonts) and the window is tall enough. `MainActivity` collects Jetpack WindowManager fold features; vertical hinge space is excluded, while separating horizontal/tabletop folds use one pane. Keyboard visibility does not determine pane mode. - `data/InstalledAppsRepository.kt` uses `LauncherApps` to query visible launchable activities in the current user and accessible managed profiles, excludes Index Launcher, and skips paused and other profile types (including private space). It derives an in-memory Latin index label with Android ICU `Any-Latin; Latin-ASCII`. If the **same launcher activity** has a resource-based label, it also requests that label with an English resource configuration (falling back to the application label resource only if the activity has none). Android may return the app's default text instead of an actual English translation; a distinct result is treated only as an optional search alias, never proof of a particular language. Primary indexing and sorting still use ICU's key unless it has no Latin initial and the alternate label does. Sort uses the device locale's collator, breaking ties by profile-aware component ID. `model/AppIndex.kt` groups by the chosen key's Latin initial (e.g. Hebrew ב under B), keeping numeric and untransliterated initials under `#`. Search matches the displayed label, chosen index key (normally the ICU romanization), **or alternate label**, without changing the displayed name or saved app IDs. Romanization is character-based, not translation: vowel-less Hebrew or other ambiguous spellings may differ from English words. Missing/inaccessible label resources are logged without hiding the app; cross-profile alternate-label loading still needs validation on a managed-profile device. The text itself uses Android's bidirectional rendering; the index still does not show script-specific letters. `ui/LauncherViewModel.kt` refreshes on `MainActivity.onResume()` and profile/package availability changes, incrementing the icon catalog version; `data/AppIconRepository.kt` loads profile-specific activity icons off-main-thread into an 8 MiB LRU bitmap cache and badges work icons. `data/IconShapeRepository.kt` persists the adaptive-icon shape used only within Index Launcher; device default uses Android's mask, while custom shapes render adaptive foreground/background layers and leave legacy artwork unmasked. Shape is part of the cache and row-loading keys so returning from Settings updates favorites, all apps, and search. Current-user app IDs retain their original format, while other-profile IDs include the profile serial number. - `data/FavoritesRepository.kt` saves **ordered app IDs**, newline-separated in `favorites` SharedPreferences, appending newly starred apps. `model/FavoriteApps.kt` resolves saved IDs against currently available apps; missing or paused-profile apps stay saved but invisible until available again. Long-press drag reordering is implemented by `ui/FavoriteReorderState.kt`: it previews visible IDs locally and persists only on a completed drop. Reordering replaces the visible IDs in their saved slots without dropping unavailable apps. - `data/PageSettingsRepository.kt` saves count in `workspace_left` (`left_pages`, default 1). `data/WidgetPlacementRepository.kt` saves ordered widget placements (ID, width preference, optional chosen height in dp) by stable page key in `widget_placements`: `home` for widgets above favorites, and `left-N` for widget pages. Home placements work even with zero widget pages; wide windows then show an invitation to enable pages in the left pane. Legacy ID-only entries load as full-width with provider-default height, without re-binding their IDs. Old right-side workspace counts and placements are intentionally not migrated. Clear app data to start entirely fresh; existing `home` placements remain independent. Decreasing count hides pages without deleting their widgets. Deletion *does* release the Android widget ID. `data/WidgetPlacementFormat.kt` encodes/decodes records and reports malformed entries without discarding other valid placements; `model/WidgetLayout.kt` handles ordered row packing, movement, and resize thresholds/ranges. - `data/ItemSpacingRepository.kt` saves `item_padding_dp` in `appearance` SharedPreferences (default 12 dp, clamped to 4–28 dp). `model/ItemSpacing.kt` derives row height from a fixed 40 dp icon plus padding on both sides; the minimum height is 48 dp. `ui/AppRow.kt` applies the same spacing to favorites, all apps, and search, including swipe action panels. Returning from Settings refreshes this setting without resetting the current app mode. - `data/WidgetSpacingRepository.kt` saves independent outside padding, column gap, and row gap in `appearance` SharedPreferences (`widget_outer_padding_dp`, `widget_column_gap_dp`, `widget_row_gap_dp`). `model/WidgetSpacing.kt` defaults to 20/12/16 dp and clamps stored values to 0–40 dp. Both home and left-side widget layouts use these settings; half-width suitability and reported host size update with the available width. Provider-internal padding is not changed. - `ui/SettingsScreen.kt` exposes the system wallpaper picker, adaptive-icon shape, app-item and widget spacing, and left-side page count. `ui/theme/IndexLauncherTheme.kt` defines Home/settings appearance; `app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml` and its drawable layers define the launcher's adaptive icon, including its monochrome variant. Debug app labeling is in `app/src/debug/res/values/strings.xml`. ## App-page interaction contract `ui/LauncherScreen.kt` controls three modes. Do not regress vertical scroll, rightward paging from apps, row reveal, or rail scrubbing when changing one of them. 1. **Favorites** is the default. `LazyColumn(verticalArrangement = Arrangement.Bottom)` with 16 dp bottom padding makes short lists grow **upward**; when the content is taller than the viewport it scrolls normally from the top. Home widgets, when present, are an uncapped, non-scrolling header in that same list. Widgets and favorites scroll together, and short combined layouts remain bottom-aligned. Without home widgets, favorites use the full height. The settings tile is **not** shown here. App labels alone have a subtle black shadow (50% alpha, 1 dp down, 2 dp blur) for light wallpapers. 2. **All apps** replaces favorites *in the same app page*. Its section headers are transparent apart from small dark letter pills. A magnifying-glass floating button at the bottom of the list opens search; the list has enough bottom padding to scroll its last item above the button. **Index Launcher settings** and **Homescreen Widgets** are the last two tiles; only the Settings tile opens settings, while Homescreen Widgets returns to favorites in home-widget Edit mode. Back returns to favorites; it closes a revealed app row first, if one is open. 3. **Search** opens only from the all-apps floating button. Swipe-up-to-search has been removed: upward swipes and overscroll on favorites or widgets only scroll the shared list. `ui/SearchScreen.kt` requests input focus and opens the keyboard on entry, filters the available personal and work app catalog as the user types (empty query shows all apps), and shows an empty-results message. On the tested emulator, Back first hides the IME; Back again or the close arrow returns to the view search was opened from. There is **no permanent search box on Home**. MainActivity uses `adjustResize`; the search layout uses IME padding. No private apps are included yet. Android’s Home button or navigation swipe delivers `ACTION_MAIN`/`CATEGORY_HOME` to `MainActivity.onNewIntent()`. A saved request counter tells `ui/LauncherScreen.kt` to return directly to favorites, closing search, revealed rows, and home-widget editing, and resetting the shared widgets/favorites list to item 0 with zero scroll offset. Selecting `★` via touch scrubbing or accessibility also resets that list, even when favorites is already visible. In compact windows `ui/HomePager.kt` also returns from widget pages to the app page; wide windows retain their selected widget page. Each consumer saves the handled counter so recreation does not replay an old Home action. Ordinary resumes and LAUNCHER intents preserve the current mode. `ui/AppRow.kt` claims leftward drags from a closed row **only** after horizontal touch slop (and can close an opened row with a rightward drag), exposing a small filled/outline Android star action to remove/add a favorite. The action is also a custom accessibility action. Rightward drags remain for the widget pager. These physical directions are consistent in RTL layouts. Search results reuse these rows. In favorites, a long press lifts a row with haptic feedback and claims subsequent movement for reordering, with edge auto-scroll; ordinary swipes still scroll/reveal/page as before. Back, Home, or gesture cancellation discards the preview. Accessible Move favorite up/down actions provide an alternative to dragging. Reordering is disabled during home-widget editing and is not offered in all apps/search. Outside an active reorder, avoid pointer handlers that consume rightward or vertical movement; avoid overlaying the star's hit target with the clickable row. `app/src/main/AndroidManifest.xml` queries launchable activities and app widget providers (`APPWIDGET_UPDATE`) so hosted `RemoteViews` can resolve provider resources without `QUERY_ALL_PACKAGES`. ## Alphabet rail, wallpaper, and visual timing `ui/AlphabetRailIndex.kt` defines the rail sequence `★ → # → populated A–Z initials → ⚙`. Empty Latin letters are omitted, including after package/profile changes; `#` remains available even when its section is empty and otherwise navigates to numeric or labels without a Latin index key. Transliterated Hebrew/Arabic names appear under their Latin initial instead. The section mapping accounts for headers **and** rows, including a preceding `#` section. Slot height stays the same as letters appear or disappear, so the compact rail grows upward from its bottom, which sits 54 dp above the safe-area bottom to keep its lowest entries clear of the phone's bottom-right corner. The inset is capped at one-third of the available height in short windows. The 48 dp-wide **full-height touch area** remains active even while invisible, including over the right edge of home widgets. The finger-following 72 dp bubble sits directly **left** of the touch position, vertically centered. Touch scrubbing requests a subtle `SegmentFrequentTick` haptic on the initial entry and each target change (including `★`, `#`, and `⚙`), respecting system haptic settings. Movement within the same target does not repeat it, and finger-position/bubble updates continue normally. Tap/scrub the star to show favorites; tap/scrub populated letters to show all apps; the gear jumps to the two launcher tiles at the end of all apps and **never opens settings directly**, including via accessibility click. `app/src/test/java/app/indexlauncher/ui/AlphabetRailIndexTest.kt` tests target geometry, mapping, and section jumps. `app/src/androidTest/java/app/indexlauncher/ui/AlphabetRailHapticsTest.kt` verifies requested haptics for target changes, same-slot jitter, repeated touches, and accessibility clicks; actual vibration still needs device validation. `ui/LauncherScreen.kt` separately animates rail and wallpaper-scrim opacity with a common **300 ms `FastOutSlowInEasing`** spec. On all apps, the wallpaper scrim stays at 60% black; after **3 seconds idle** the rail settles to **50%** opacity, returning to full on touch. Back or a completed star scrub closes all apps and fades its scrim away. Favorites never dim the wallpaper, even when the rail is initially expanded or being scrubbed. Search has the 60% scrim but **no rail**. On initial entry the rail starts expanded and fades after its idle delay. In compact mode, `ui/HomePager.kt` suppresses the rail immediately during a page swipe and remembers a visit to widget pages; after returning to the apps page, the rail remains hidden until a new tap/scrub on its full-height touch area (or an accessibility click). Widget pages have **no** wallpaper scrim. The scrim is drawn in fixed screen space by `HomePager`, fading with swipe progress over the first third of a page rather than sliding with the app page. In two-pane mode the scrim covers only the right pane and paging widgets does not suppress the app rail. Offscreen pages do not handle Back; an offscreen search hides its keyboard. `ui/theme/IndexLauncherTheme.kt` uses a transparent-background dark Home color scheme and Expressive-style shapes/typography; settings uses system dynamic light/dark colors. ## Widgets on home and left-side pages `ui/WidgetPageScreen.kt` packs full-width widgets and pairs of half-width widgets in an ordered flow shared by home and left-side pages. Newly added widgets start half-width if their declared default width fits, otherwise full-width; legacy widgets remain full-width. Left-side pages scroll over their full height; home widgets scroll together with favorites in a single list and use the safe-area width minus page padding, including behind the rail. The overlaid rail takes touches on the extreme right edge; favorites and all apps still reserve its 48 dp width. Widgets have no Index Launcher-provided background or clipping outside Edit mode, and left-side pages have no visible title or top toolbar. On **left-side pages**, long-press on empty space opens the gallery (an empty page also shows Add widget); long-press on a widget enters page-local Edit mode, even when its child views consume touch (`ui/LongPressWidgetFrame.kt` observes without stealing ordinary taps/scrolls). On **home**, widget long-press entry points are disabled: use the Homescreen Widgets tile below Index Launcher settings in all apps to open home-widget Edit mode, even with no existing widgets. Home widgets disappear temporarily in all-apps/search and reappear on return. The shared list has no search-overscroll gesture. Edit mode shows the bounds, a bottom Add/Done bar, bottom/side drag handles for height and half/full width, reorder arrows, and a trash control with removal confirmation. Accessible actions also offer Add/Edit and handle sizing without gestures. Back or Done locks the layout; the reported provider size comes from the measured `AppWidgetHostView`. `ui/WidgetGalleryScreen.kt` and `data/WidgetCatalogRepository.kt` group home-screen providers by app and accessible profile, render previews when available, and exclude paused profiles. `MainActivity.kt` handles system binding consent, cross-profile configuration, cancellation, and widget-ID cleanup; a work administrator controls which work providers can be hosted. The fuller design and explicit remaining gaps live in [widgets-design.md](widgets-design.md); in particular, full-width incompatibility and saved-height clamping do not yet have dedicated feedback. ## Change checklist - After gesture/UI changes, manually check: Home button/gesture from all apps and search; spacing at both extremes; short and overflowing favorites; Home/`★` resetting scroll including offsets inside oversized widgets; upward overscroll never opening search; rail touches vs row swipes; rightward widget paging from apps; fold/unfold continuity; Back with keyboard visible, then search, then all apps; star removal/persistence; rail opacity/scrim timing over both light and dark wallpaper. - Unit tests are in `app/src/test/java/app/indexlauncher/` (favorites, home pages, app index/search aliases, rail, item/widget-spacing settings, icon-shape settings, widget packing/resizing, and placement serialization). UI regression tests in `app/src/androidTest/` cover favorite drag/drop, cancellation, edge auto-scroll, accessible movement, order persistence, shared widget/favorites scrolling, oversized widgets, short layouts, Home/`★` scroll reset, disabled search overscroll, widget spacing geometry/persistence, the empty home-widget editor, row/pager arbitration (including RTL), rail-haptic requests, pane resizing, saved-state restoration, and logical-page clamping. `FoldableSearchTest.kt` specifically checks that search queries and result scroll positions survive pane changes and saved-state restoration. Run the build/test/lint and UI-test commands in the [project README](../README.md#build-and-run). Emulator checks so far are not a substitute for API 35/36 device testing, accessibility, real-device folding, or the remaining widget-hosting integration checks.