Widgets of other apps used to fill their cells minus Launcher3's widget
padding, which is a few pixels wider than the icons' edges and reaches down
into the room kept for icon labels; on the owner's phone they hung about
13 px past the icons sideways and 40 px below. Now the room a widget keeps in
its cells is the distance from the cell edges to the icon edges, worked out
as IconFrame does for the launcher's own widgets, so every widget's edges
meet the edges of the icons around it.
The one function (FlexeraWidgets.appWidgetPadding) is used for the layout,
for the size the app is told, for the drop animation and through
WidgetSizes for previews. The cells a widget needs are still worked out
with Launcher3's padding, so no widget changes its cell count. A new switch
under Settings, Home screen, Widgets ("Line up with icons", on by default)
turns it off; Lawnchair's Widget padding slider shows only then.
Checked on the emulator against lines drawn at the icon edges: Google
Calendar's widgets meet them within 2 px; Google Clock's sits about 20 px
inside, because it draws an empty margin of its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hide app: a new entry in the menu a long press on an app opens in the app
list. It adds the app to Lawnchair's hidden list (hidden_apps), which the
list and its search already leave out and which Settings > App drawer >
Hidden apps already shows, so that page is where an app is brought back.
A message with Undo follows. The entry is offered in the app list only
(not for home screen icons, which hiding does not touch) and not while
the home screen is locked.
Preference collectors: five collectors in PreferenceManager2 (icon shape,
dock mode, dock on/off, dock search provider, labels in the dock) had
drop(1) before distinctUntilChanged. The first emission after the dropped
one then always passed, so the first write of any preference after the
launcher started reloaded the model, rebuilt the grid three times and
recreated the launcher. Hiding an app is such a write, made while the
launcher is on screen: the list went blank for a moment and the Undo
message was lost with the old activity. The operators are swapped, so
only a changed value passes.
Checked on the emulator: both menu layouts (labelled rows and the icon
row), hiding right after a fresh start (no relaunch in the log, message
stays), Undo, and unticking the apps under Hidden apps.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Folder blur: while a folder is open on the home screen, what is behind it
is blurred, icons and widgets through a RenderEffect and the wallpaper
through the launcher surface's background blur. It is a new input of the
depth controller (folderBlur, 0 to 1), because that controller already
owns both kinds of blur and cleared anything set past it on every frame
of the folder's wallpaper zoom. The folder's spring animation drives it in
step with the dimming, and the places that reset the dimming reset it too.
It works with the wallpaper depth effect switched off, without bringing
the folder's wallpaper zoom along. Setting: Folders > Blur behind open
folders, on by default (flexera_folder_blur). Folders in the app list are
left as they were.
Dock: with the dock switched off the hotseat has no height, but its icons
were still drawn just below the screen edge, and the 97.5% zoom-out behind
an open folder brought their tops into view. One more alpha channel on
the icons hides them.
Checked on the emulator (Android 17 image, window blur available): open,
close by tap outside, Back and Home, an app started from the folder, sleep
and wake, the setting off, and the depth effect off.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Swiping on from the first home screen page, towards the page that is not
there, now does what the owner chose under Settings, Gestures, Quick launch:
open an app, or any other of the launcher's gesture actions. The last page has
a choice of its own.
The stretch the pages already show at either end is an edge effect; quick
launch replaces it with one that also counts how far the finger has pulled.
Lifting the finger after a pull of about a seventh of the screen, or after a
flick, does the action; a short pull or one that is taken back does nothing.
An app opens out of the screen edge the finger pulled from.
From Android 12, where the stretch is Android's own. Infinite scrolling takes
those swipes for itself, so the two do not combine.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Opening the launcher like an app (the installer's "Open") and then making it
the home app leaves two launcher activities in one process. A changed setting
recreated whichever one LawnchairApp.launcher returned, which could be the one
in the background, so the home screen kept its old look until a restart.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Row size (Settings, App drawer, Style): Compact is the list as it was, Comfy
has taller rows, larger icons and slightly larger names and letter headers.
In the open drawer the alphabet no longer reserves the screen edge. Its letters
rest 30dp from the edge, clear of the strip where Android starts Back. A touch
that starts on that strip becomes a scrub once it moves up or down or rests for
a moment; a sideways swipe is left to Android and goes back. The band that
starts a scrub from the home screen is narrower than the one in the drawer.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Asked for by the owner on 12 November 2026, with one of their own backups to work from.
Import (Settings > Backup and restore > Import from Smart Launcher)
- A .slbk file is a zip of Smart Launcher's files. The importer reads its database
(HomeItem, Launchable, Action, Widget) and the grid from its preferences, and writes the
home screens as a Launcher3 database that Launcher3's restore code takes in, the way
Lawnchair's Nova import does. The format is not documented; this follows one real
backup of data version 64 and refuses a file without those tables.
- One to one, by the owner's decision: every icon, folder and widget in the cell it had, on
a grid of the same size, and the dock switched off because Smart Launcher has none.
Home screen labels follow the backup.
- Widgets arrive as "tap to set up". Android has each launcher ask for its own widgets and
keeps a widget's settings with the launcher that made it.
- Left out, and said so on the screen before the owner agrees: Smart Launcher's own widgets,
items of other profiles, anything without a usable target or free cells. Icons of apps
that are not installed are dropped by Launcher3 when it loads the layout.
- Settings are changed only after Launcher3 has accepted the layout, so a failed import
changes nothing.
- Entry names in the zip are not trusted: only the database and the preference files are
unpacked, each under a fixed name, with a size limit.
Fix in Launcher3 (WidgetSizeHandler)
- A widget waiting for "tap to set up" has an id Android has reserved but not bound.
Android 17 answers a size update for such an id with a NullPointerException, which ended
the launcher whenever the grid reloaded with such a widget on a home screen. Found
because the import first changed a setting that reloads the grid while the previous
layout still had unset widgets. The answer is now logged and dropped.
Checked on the Android 17 emulator: the owner's backup itself (two apps of it exist there
and land in their cells), a copy of its layout pointed at apps the emulator has (all 7
icons, 6 folders and 4 widgets in place, one widget set up through Android's dialog), and
two grid reloads with unset widgets on screen.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Requests from the owner on 12 November 2026, after using 0.3.1 on the Pixel 9a, with a
photo of Niagara Launcher's alphabet as the reference.
Drawer list
- Rows are 50dp with 34dp icons and 15sp names (were 64dp, 46dp, 17sp); letter headers are
34dp. A row grows with a large system font so a notification preview still fits.
Alphabet
- Letters sit 20dp apart at most (was 30dp). The strip keeps a stretch of edge as tall as
26 letters, low on the edge with its end 40dp above the navigation bar, and centres the
letters that exist in it. It used to spread over the whole edge, which put "A" out of a
thumb's reach.
- The wave follows Niagara's: letters keep their size, the wave is wide (104dp) so about
five letters on each side of the finger curve around the thumb, and the selected letter
is shown large in a light bubble just beyond the wave's peak. This replaces the enlarged
letter on a dark spot from 0.3.1, which collided with its neighbours at this spacing.
- The list dims a little during any scrub, more where the wave lies over it.
Backdrop
- The drawer's scrim is black at 78% in every theme (AllAppsState). Lawnchair's grey at 40%
left a light grey behind white names over a light wallpaper. LawnchairScrimView judges the
status bar icons by the scrim's own colour again, since that colour is now always dark.
Scrub from the home screen (PRD AD-10)
- On by default, the owner's decision; this closes the PRD's open question.
- It opens the drawer in 120 ms instead of the state manager's 600 ms, like swipe up and hold.
- A touch that lifts within 200 ms was a flick and opens the plain list.
Fix: undoing a scrub that was not meant
- 0.3.1 put the list back to "where it was", read from the list's rows. A closed drawer has
been told to return to the top but lays its rows out only when it opens, so the rows
still reported the place the drawer was last left at, and a quick swipe reopened it
there. The list now goes where a plainly opened drawer stands.
About
- A QR code for the release page, generated on the phone with ZXing core (new dependency),
black on white in every theme, with the address under it.
Checked on the Android 17 emulator with timed touches, a thumb-sized shape over the
screenshots, a 1.6 font scale, and by decoding the QR code from a screenshot.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
PRD AD-11, the last buildable row of M4.
- FlexeraNotificationPreview keeps the text of the notifications that currently show a
dot, fed by Launcher3's NotificationListener on its worker thread, and sets a second,
smaller and dimmer line on a drawer row whenever the row's dot state is applied.
- It uses what the dots already use: the same notification access and the same filter
for which notifications count. Without notification access nothing changes.
- PopupDataProvider now refreshes an app's icons on every posted notification, so a
notification updated in place brings its new text to the row.
- Only rows of the drawer list take the second line (marked with a view tag); search
result icons share the "all apps" display type but stay as they were.
- New setting under Settings > App drawer: Notification previews, on by default.
Checked on the Android 17 emulator (debug build): a test SMS showed under Messages,
a second one replaced it while the drawer was open, removing the notification put the
plain name back, and the setting switches the line off and on.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
First run (PRD SI-1)
- The request to become the default home app never appeared. Started from onResume, it
was still pending when the launcher finished resuming, and Quickstep cancels any
pending request at that point. It now starts from onDeferredResumed, after that
check, through Launcher3's own ApiWrapper.assignDefaultHomeRole.
Updater
- An automatic check that fails (phone offline, say) no longer leaves "couldn't reach
the update server" on the About screen. The screen keeps what it showed before, and
the check runs again in an hour. A check started by hand still reports the failure.
Checked on the Android 17 emulator with the 0.2.0 release build made before this commit:
swipe up and hold in gesture and in three-button navigation, the list darkening under
a wave that reaches in from the right edge, and the work profile tabs. The two changes
in this commit are not yet checked.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Release script
- The first 0.2.0 build crashed at start with NoSuchFieldError: $stable. Its Kotlin was
compiled incrementally on top of the 0.1.0 output after a preference class had changed,
the same fault seen earlier in debug builds. It was caught by hand before publishing.
- flexera/release.ps1 now deletes the release variant's Kotlin output and builds without
the build cache, so a release never reuses earlier compiled code.
- New step: install the APK on the running emulator, start it, and stop without
publishing if it crashes or is not running after 25 seconds. Installed copies update
themselves, so a release that crashes at start must not get out. -NoSmokeTest skips
the step on a machine without an emulator.
First run (PRD SI-1)
- The first time the launcher comes to the front while another app is the home app, it
asks Android to offer it as the default home app (FlexeraFirstRun). Asked once; if
declined, Launcher3's own hint and the reminder in settings remain.
Not yet checked on a device: the first-run prompt.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Scrub from home (PRD AD-10)
- New setting, off by default: a vertical slide along the alphabet's edge of the home
screen opens the drawer and scrubs it in the same touch (FlexeraEdgeScrubController).
It claims a touch only once it has clearly moved up or down, and never during an item
drag. Nothing is drawn on the home screen.
Work profile (PRD AD-14)
- The Personal and Work tabs and the work info card were one icon wide, because their
width came from grid maths that assumes several columns. In the one-column list they
now line up with the search bar.
- The info card's own scroller section no longer shows up as a letter in the alphabet.
Navigation modes (PRD SI-2, AD-17)
- With three-button navigation, the controller that carries swipe up and hold stood aside
completely, so a hold did nothing there. It now takes the ordinary swipe up from the
home screen in that mode too.
Wave alphabet
- The list darkens under the wave where the letters cross icons and names: always from
the left edge, and from the right edge in proportion to how far the wave reaches in.
Release script
- -BuildOnly builds and checks the APK without publishing, so a version can be tried
first and then published with -SkipBuild.
Checked on the Android 17 emulator (debug build): work tabs at full width, scrub from the
home screen's edge, the search field keeping its background while typing. The
three-button change and the right-edge darkening were written after that build and are
not yet checked.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
PRD AD-15 and SI-10, plus polish found while checking the M4 rows on the emulator.
Wave alphabet
- New setting "Alphabet on the left edge" for left-handed use. Rows keep clear of the
alphabet on whichever edge it sits; a scrub from the left darkens the list under the
wave, because there the letters cross the icons and names.
- Screen readers see the strip as one slider: it reads the letter the list is at, and the
adjust gesture moves one letter at a time.
- Letters shrink to fit their slot when a large system font would make them overlap.
Settings
- FlexeraDrawerSettings replaces Lawnchair's App drawer screen, whose grid options
(columns, row height, icon and label size, drawer folders, scroll bar, panel colour and
opacity) do nothing in the list drawer or break its white-on-transparent look.
- Settings home and search hint no longer mention column counts or web results.
Search
- The search field keeps its pill while typing; without a drawer panel behind it, the
dark text sat directly on the wallpaper.
Checked on the Android 17 emulator: drawer search, hiding and restoring an app,
notification dots, swipe down for notifications, the left-edge alphabet and its scrub,
and the slider node in the accessibility tree.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The app is distributed privately, outside any store. Every version is published as a
release with the APK attached in https://git.mariosem.es/msemes/Flexera-Launcher-Releases,
and installed copies update themselves from there.
Versioning
- flexera/version.properties holds the version name (now 0.1.0); build.gradle derives the
version code from it. This replaces Lawnchair's 16_00_02_01_00 / "16.Dev.(hash)".
- Debug builds always use the debug key, so the release key signs releases only.
Updater (app.flexera.update.FlexeraUpdater)
- Asks the release repository for the latest release at most once a day, when the home
screen or settings come to the front, and on demand from Settings > About > Updates.
- Downloads the APK into the cache, checks that it is a newer build of this app, and
hands it to Android's installer. Android enforces the signature.
- Posts a notification when an automatic check finds a newer version.
- Off in debug builds, whose app ID differs from the released one.
Network policy (reverses PRD SI-5, at the owner's request for in-app updates)
- The INTERNET permission is back, plus REQUEST_INSTALL_PACKAGES.
- The update check is the only network use. Lawnchair's online features stay off through
FlexeraNetwork.onlineFeaturesAllowed: web suggestions, the announcement feed, and the
crash-report upload button.
- The About screen's privacy text says what the app now does online.
Releasing
- flexera/release.ps1 raises the version, builds the signed APK, checks its signature,
creates the release and uploads the APK.
Also carries the strings for the next commit (alphabet side and drawer settings).
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
PRD M3 (AD-4 to AD-6, AD-17), plus three look changes the owner asked for.
Wave alphabet
- WaveAlphabetView (flexera/src): a strip of letters on the drawer's end edge showing only
letters that have apps. Touching it jumps the list to that letter with a haptic tick; the
letters near the finger bulge away from the edge and reach further the further in the
finger moves; the selected letter is enlarged.
- The strip reserves the edge from the system back gesture and tells the drawer not to
drag closed under a scrub.
- Launcher3's scroll bar is hidden by default and no longer takes touches while hidden.
Swipe up and hold
- NoButtonNavbarToOverviewTouchController reuses its motion-pause detector on an ordinary
swipe up from the home screen: a pause finishes opening the drawer and hands the same
touch to the alphabet. Swipes that start on the navigation bar keep their stock meaning.
Look
- The drawer fades in over the home screen instead of sliding up from the bottom.
- No background panel or drag handle (drawer opacity default 0); the wallpaper keeps
Lawnchair's blur and scrim.
- App names, letter headers and alphabet letters are white with a soft shadow.
Tested on the Android 17 emulator: quick swipe opens the drawer, edge scrub jumps the
list, swipe up and hold opens the drawer and scrubs from mid-screen, Back closes it.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
PRD M2 (AD-1 to AD-3, AD-7, AD-8, HS-7). The drawer keeps Launcher3's adapter, sorting,
live updates, click, long-press menu and drag; only what differs from the grid is new.
- FlexeraDrawer (flexera/src): column count, row styling, and a letter-header adapter item
- flexera/res/layout/all_apps_icon.xml overrides Launcher3's grid cell with a horizontal
row (46dp icon, name beside it, 64dp tall)
- AlphabeticalAppsList adds a header item at the start of each letter group and points
the fast-scroll section at it; Private Space keeps its own framing
- BaseAllAppsAdapter creates and binds the header view type and styles rows
- DeviceProfileOverrides forces one drawer column
- DeviceProfile.getAllAppsIconStartMargin returns the row padding for a one-column
drawer, which keeps the search bar full width
- Suggested apps row above the list is off by default
Tested on the Android 17 emulator: scrolling, tap to launch, long-press menu, drag to the
home screen, and live removal and return of an app all work. The right-edge fast
scroller did not respond to scripted drags and still needs a hand test.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Lawnchair's screen showed its logo, team and community links and loaded contributor
data from the network. FlexeraAbout (flexera/src) shows the app's own icon, name and
version, credits Lawnchair as the project it is built on, states the privacy position,
and keeps the open-source licence list. It makes no network requests.
The About route in PreferenceNavigation now points at it; Lawnchair's About.kt is untouched.
Tested on the Android 17 emulator: the screen renders, the licence list opens, and
no network request or guard message is logged.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
PRD SI-5: nothing leaves the device.
- flexera/AndroidManifest.xml removes android.permission.INTERNET; it is attached to the
debug and release build types so it outranks flavor and library manifests
- FlexeraNetwork (new, flexera/src): isAvailable() plus a crash guard, because OkHttp
rethrows Android's SecurityException on its dispatcher thread and that killed the launcher
- Drawer search skips web suggestions when the permission is missing
- LawnchairApp installs the guard; the bug reporter ignores the same exception
Tested on the Android 17 emulator: typing in the drawer search and opening the About
screen no longer crash; the guard logged and absorbed six refused requests.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
restartLauncher()'s fallback for when Lawnchair isn't the current default home restarts it via PackageManager.getLaunchIntentForPackage(), which builds a CATEGORY_LAUNCHER (standard-typed) intent to the home activity. Some Android 8-10 OEM ROMs react to that launch by trying to re-type the home task and killing the app with RemoteServiceException("Can't change activity type once set: ... activityType=standard") delivered via SCHEDULE_CRASH, which cannot be caught at runtime. Launch the same activity with a home-category intent pinned to our own package instead; the resolved target and flags are unchanged (NEW_TASK was implicit in the launch intent, CLEAR_TOP stays).
Co-authored-by: caizhixing <caizhixing@users.noreply.github.com>
* feat and fix : Add Tilt to swipe pages setting
Replace the multi-level Motion controls with a single Home screen toggle named “Tilt to swipe pages,” positioned below Infinite scrolling. Update the description to explain that tilting the phone moves between pages while rearranging apps or widgets, and make the pan controller use the toggle directly while retaining the existing preference key for compatibility. Fix for "[BUG] Large empty gap between app drawer search bar and app grid when app suggestions are disabled
#7490"
* EDITS : Coderabbit suggested edits for PR #7500
* Coderabbit suggested changes (2) #7500
* Update gradle-daemon-jvm.properties
* Remove unrelated Gradle toolchain changes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add docstrings for pan-to-move and all apps header changes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add a tilt sensitivity control
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LawnchairFloatingSurfaceView.removeViewImmediate() skipped the removal when
isAttachedToWindow was false. onNewIntent is delivered inside
performResumeActivity, while the window can still be re-attaching: the view is
already a child of the drag layer but not attached yet. The removal was
skipped, so the following show() crashed on addView() with
IllegalStateException: The specified child already has a parent.
You must call removeView() on the child's parent first.
FloatingSurfaceView removes the view unconditionally, and removeViewInLayout()
is a no-op when the view has no parent, so match it.
Co-authored-by: caizhixing <caizhixing@users.noreply.github.com>
lawnchair/res/layout/widgets_two_pane_sheet_paged_view.xml is a leftover copy
of the 2021 two-pane layout that shadows the current one in res/. It is
inflated by WidgetsTwoPaneSheet when a work profile is present, but the AOSP
code has moved on:
- WidgetsFullSheet.setupViews() looks up R.id.search_widgets_list_view, which
only exists in the current layout. The old copy has no search results list,
so the lookup returns null and AdapterHolder.setup() crashes on
setOutlineProvider() while the sheet is being inflated.
- It is also missing R.id.suggestions_header and repeats the "Widgets" title
that the outer widgets_two_pane_sheet.xml already provides.
Deleting the overlay lets the AOSP layout in res/ win, matching the
recyclerview variant which was already realigned with AOSP in #7188.
Co-authored-by: caizhixing <caizhixing@users.noreply.github.com>
* fix(startup): install the shipped baseline profile on sideloaded builds
Removing androidx.startup's InitializationProvider (AOSP b/197780098)
also removed ProfileInstallerInitializer, so GitHub and Nightly APKs
never wrote the bundled baseline profile and ran unoptimised until
background dexopt built a profile of its own.
Do the initializer's job from LawnchairApp instead: main process only,
5 s after start, on the background-priority ordered executor. The
provider stays removed, so no other library initializes eagerly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(startup): only install the profile from the main process on API 26-27
Below API 28 the version check let every process through, including
:bugReport. Look up this pid's process name with ActivityManager there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The bug report file name used the locale-dependent
SimpleDateFormat.getDateTimeInstance(), which yields '/' in some
locales (e.g. ja_JP: "2026/09/28 12:34:56"). The resulting path had
non-existent parent directories, so createNewFile() threw inside the
uncaught exception handler and the default handler was never invoked,
leaving the process frozen instead of restarting.
- Use a fixed, locale-independent format (yyyy-MM-dd_HH-mm-ss, Locale.US)
- Always delegate to the default handler via try/finally so the process
is killed even if generating the report fails
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On Android 11 and below, LawnchairActivityCachingLogic#loadIcon resolves
ActivityInfo through PackageManager#getActivityInfo because
LauncherActivityInfo#getActivityInfo is only public since Android 12. When a
package is updated while the launcher refreshes its icon cache, one of its
launcher activities can vanish between LauncherApps handing out the activity
list and this lookup. The resulting NameNotFoundException is uncaught and
kills the model thread:
Fatal Exception: android.content.pm.PackageManager$NameNotFoundException
at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:471)
at app.lawnchair.LawnchairActivityCachingLogic.loadIcon(LawnchairActivityCachingLogic.kt:60)
at com.android.launcher3.icons.cache.BaseIconCache.addIconToDBAndMemCache(BaseIconCache.kt:235)
at com.android.launcher3.icons.IconCache.updateIconsForPkg(IconCache.java:206)
at com.android.launcher3.model.PackageUpdatedTask.execute(PackageUpdatedTask.java:160)
at com.android.launcher3.LauncherModel.enqueueModelUpdateTask(LauncherModel.kt:412)
Catch the exception and return BaseIconCache#getDefaultIcon instead:
addIconToDBAndMemCache already treats the default icon as "could not be
loaded" and skips the DB/memory cache entry, matching how other icon load
failures are handled. The two other PackageManager#getActivityInfo call
sites (ThemedIconCompat, SearchResultIcon) already catch this exception; the
CachingLogic was the only unguarded one.
Android 12+ is unaffected: it reads the ActivityInfo snapshot from
LauncherActivityInfo directly.
Co-authored-by: caizhixing <17589399+caizhixing03@user.noreply.gitee.com>
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
* fix(prefs): reload the grid with the value just set
Most grid preferences reload the grid in their onSet. onSet runs right
after the DataStore write, but the device profiles read the preferences
through PreferenceManager2's cache (firstCached), which is updated by its
own collector on the main thread. That collector may not have run yet
when the reload runs inline, so the grid could be rebuilt from the
previous value: the change shows up only on the next reload, for example
after the next change of any grid preference.
ReloadHelper.reloadGrid now refreshes the cache from the DataStore, which
already has the new value, before reloading the grid. This fixes every
preference whose onSet calls reloadGrid, and the other callers
(restart(), the folder settings), with no change at the call sites. The
reload now always runs on the main thread, through PreferenceManager2's
Main.immediate scope.
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit cbe549ade0bac9d655a965ed8f802c8c19573ef5)
* fix(prefs): keep a newer cached value when the fresh read finishes late
With two quick grid changes, the first refresh could finish after the
collector had already stored the second value and put the older snapshot
back. Store the read result only if the cache hasn't changed meanwhile.
Suggested in review by CodeRabbit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(home): live glass blur behind icons, folders and widgets
Add an optional "Glass blur" setting that blurs the wallpaper behind home
screen icons, folder icons, open folders and widgets, like CSS
backdrop-filter. SurfaceFlinger renders the blur through blur regions
(a copy of the framework BackgroundBlurDrawable), so it stays live and
follows the items during page scrolls and animations.
SurfaceFlinger ignores all the blur of an app layer once there are more
than 10 blur requests on screen (kMaxBlurRequests, b/399120953), unless
the layer was created by a privileged process. With root, the regions go
to an effect layer created by the root helper and placed below the
launcher window content. Without root, they go to the window layer and
are subject to that limit.
Also fix RootHelperManager, unused until now: it reported root as not
available until a root shell had been created, and bound the root
service off the main thread.
* fix(home): redraw when a glass blur region is removed
Without a frame the pre-draw listener never sends the regions left, so a
removed blur, the last one in particular, could stay on screen until the
next redraw.
AllAppsSearchInput sets its horizontal padding from the device profile
only when it is inflated and when the IDP changes. A DeviceProfile change
without an IDP change (rotation, or folding and unfolding once the
device type is stable) left the padding of the old profile. When it did
run, setupPadding() also reset the full padding even while a search was
active, where the bar uses half of it, and the focus and text listeners
kept animating to the paddings captured at inflation.
- Listen to DeviceProfile changes and update the padding then too.
- Keep the half padding while the search box is focused or holds a
query, and cancel a running padding animation first.
- Animate to the current paddings rather than the inflation ones.
The search text and results are untouched. The result views already
follow the new profile: ActivityAllAppsContainerView#onDeviceProfileChanged
swaps the adapters and clears the view pool of every list, the search
list included, so all result views are inflated again with it.
(cherry picked from commit 090ce597ed7f6e35ee7fecb8235ea52d7be155c7)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(display): keep the device type stable across folds
LawnchairWindowManagerProxy estimated the supported bounds only from
DisplayManager#getDisplays, which reports the panel in use. On a foldable
the DisplayController cache then held a single state, so every fold and
unfold invalidated it ("Unexpected normalizedDisplayInfo found"), the
device type flipped between PHONE (folded) and MULTI_DISPLAY (unfolded,
through the hinge check), and CHANGE_SUPPORTED_BOUNDS reloaded the grid
and the model. The smaller dock of the phone profile also made the
loader delete the dock items past its size.
On devices with FEATURE_SENSOR_HINGE_ANGLE, the estimate now also holds
the other states of the internal display:
- WindowManager#getPossibleMaximumWindowMetrics, like
SystemWindowManagerProxy, when the system answers (it only does for the
recents provider) and the current state is one of its states.
- Otherwise each state of the default display seen on the device is
remembered. A state with the same aspect ratio replaces an older one,
so a resolution change leaves no stale bounds.
The extra states are built the way DisplayController.Info builds the key
it looks up (getDisplayInfo(context).normalize(), no-cutout default), so
the lookup finds them and folding no longer changes the supported
bounds. Once both panels are known they hold a phone and a tablet size,
and the device type is MULTI_DISPLAY in both states, across restarts too.
A hinge device never seen unfolded still reports PHONE: flip phones also
have the hinge sensor, so it can't be assumed.
The states are kept in their own device-protected file,
lc_display_states, not in boot_aware_prefs: DisplayController can run
before unlock, the values must not be backed up or restored on another
device, and the write happens while DisplayController is being
constructed. AOSP's taskbar-pinning listener on boot_aware_prefs calls
getInfo() for every change, which is still null at that point, so a write
to that file crashed the launcher when it started on the other screen
than last time.
The hinge check now lives in one place, LawnchairWindowManagerProxy
.isFoldable(), which DisplayController.Info uses as well.
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 14e6051ac83cba92cb371eec1c408966cb41ba9d)
* chore(display): log the device type once per info
getDeviceType() is called many times for each display change (once per
DeviceProfile and more), and logged its result every time. Log it once
per DisplayController.Info, that is once per display change, with the
current size, so the device type of each state can be read in logcat.
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 9fa57ec7e3d678c3e19d6ce1309ae11b893e18fc)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Retrofit already percent-encodes @Query parameters, so encoding the query with
Uri.encode first sent the literal "%D8%A7..." text to the suggestion APIs.
Non-ASCII queries (Arabic, Cyrillic, accented Latin, ...) got suggestions for
that escaped text, shown as hex bytes. Pass the raw query instead. The search
URLs built by hand in getSearchUrl still encode, as they should.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(about): count the changes' relative times as plurals
"5m", "3h" and the other relative times in the changes dialog were plain
strings, so languages that change the noun with the number, like
Arabic ("دقيقة", "دقيقتين", "5 دقائق"), had to pick one form that is
wrong for most numbers. Make them plurals; English stays the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(folders): translatable "Manage apps" in the drawer folder editor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(settings): translatable empty pane text on large screens
The pane next to the settings menu on tablets and unfolded phones said
"Tap a preference in the left hand menu" in English only, and the menu
is on the right in right-to-left languages. Use a string that names no
side.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit improves Lawnchair's icon system by reorganizing the preference screens, while making icon behavior more deterministic in `LawnchairIconProvider` and cleaning up old and unused preferences.
VerticalSwipeTouchController uses BothAxesSwipeDetector, which has no angle
check. Once the touch drifts vertically by more than the touch slop it takes
over the stream and the dock pager receives ACTION_CANCEL, so a normal thumb
swipe (e.g. 450 px horizontal, 90 px vertical) leaves the dock half scrolled
or snaps back to the starting page.
When the touch starts on a paged dock, stop offering it to the vertical swipe
detector as soon as the motion is mostly horizontal (|dy| < 2 * |dx|).
- Closes: #7149
Co-authored-by: Diego Pérez Ruanova <25953869+dperezruanova@users.noreply.github.com>