So that we can use the bounds to set on BackgroundBlurDrawable
Bug: 445467156
Test: manual in the next CL
Flag: EXEMPT BUGFIX
Change-Id: I002d9dfd1d1aabc90c5cd6097772103575d91de9
- Folder pages should not swipe out of bounds while opening
- Folder pages should be revealed if swiped in bounds while opening
- Closing the folder should not clip the preview icons
Bug: 458072199
Test: opening/closing/swiping a large folder
Flag: com.android.launcher3.enable_expressive_folder_expansion
Change-Id: I3773a9c79ec0e5efc24c0ac185a516353f955cec
Bug: 449923444
Flag: com.android.launcher3.enable_expressive_folder_expansion
Test: open folder and immediately swipe to next page
Change-Id: Iafa46f3748fdf0c1982b5de6df539b8c8609204f
Bug: 456164513
Test: manual - close app and quickly open folder
Flag: com.android.launcher3.enable_expressive_folder_expansion
Change-Id: I47886b034402549176f99618699ae90bd0356086
Bug: 452119282
Flag: com.android.launcher3.enable_expressive_folder_expansion
Test: use longpress on folder item and tap remove shortcut
Change-Id: Ia6e60189a74d731ed7a23ab74c144a228118a1d9
Bitmap.createBitmap(Picture) was only added in API 28, but Lawnchair
runs down to minSdk 26. Creating a folder drag preview on Android 8.x
therefore throws NoSuchMethodError and crashes the launcher.
Reuse the existing BitmapRenderer fallback on versions below P to
rasterize the recorded foreground Picture into a software bitmap.
Co-authored-by: caizhixing <17589399+caizhixing03@user.noreply.gitee.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>
Infinite scrolling kept a wrap state (target page, saved scroll bounds,
a cached feed flag) that every path out of a swipe had to tear down.
Paths that missed it caused the bugs: a short or backward swipe on the
last page wrapped anyway, the first page flashed when a layout pass
reset the bounds mid-wrap, touching during a wrap jumped to the target,
two-panel layouts got stuck between pages, and the page indicator lost
its active dot.
The wrap now follows from the scroll position alone: on an end that
wraps, the scroll may run one screen further, and the screen from the
other end is translated into that gap. Workspace decides which ends
wrap; other paged views behave as in AOSP.
Co-authored-by: Claude Opus 5.5 <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>
Home screen > Icon size (homeIconSizeFactor) had no effect on responsive
grids such as "practical": DeviceProfileOverrides scales the IDP's
iconSize, but responsive grids take their icon size from their cell
specs (mResponsiveWorkspaceCellSpec.getIconSize()) and never read
inv.iconSize.
getResponsiveIconSizePx() now applies the factor to the cell specs' icon
size, for the workspace, the hotseat and folder content, the same three
places the IDP-sized grids use inv.iconSize. The responsive code still
shrinks an icon that doesn't fit its cell (getIconSmallerThan). The icon
bitmap size already followed the factor, as the IDP computes it from the
scaled iconSize.
Visible change: on responsive grids the setting now works; at 100% (the
default) nothing changes.
(cherry picked from commit 6345b71df7dbd77be6c4da03ddcc76ebcadd2e90)
Co-authored-by: Claude <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>
* fix(fold): keep dock items past the hotseat size instead of deleting them
LoaderCursor#checkItemPlacement rejects hotseat items whose rank is not
below InvariantDeviceProfile#numDatabaseHotseatIcons, and checkAndAddItem
then marks them deleted like overlapping items, so they are removed from
the database. That size can shrink only for a while: on a foldable the
folded (phone) profile has a smaller hotseat than the unfolded one, and
folding once deleted the dock icons at ranks 4 and 5 for good.
Skip such items for binding but keep them in the database, so they show
up again once the hotseat is large enough. Why this is safe:
- Overlapping items (in bounds) are still deleted as before.
- Nothing can be placed at a rank past the current hotseat size, so a
kept item can't collide with a new one while it is hidden. If it ever
does, the regular overlap check deletes it when it comes back.
- Grid migrations are unchanged and run before the loader.
- Only top-level hotseat items are bound, so the contents of a kept
folder stay unbound with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d556864ee08d0e2494f21a5fc3a9f00676f1269a)
* fix(migration): skip dock entries past the hotseat size (pre-existing crash)
Both solveHotseatPlacement versions (GridSizeMigrationDBController and
GridSizeMigrationLogic) mark the target's remaining dock items in an
array sized to the new dock: occupied[entry.screenId] = true. When the
target grid's database was last used with a larger dock, an entry's
rank can be past that size, and moving back to that grid with anything
to add to the dock throws ArrayIndexOutOfBoundsException. The Kotlin
path then aborts the migration; the Java path resets the target
database.
Such an entry takes no slot in the new dock, so it is skipped when
marking. Nothing else changes: the placement loop and every other entry
are as before.
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit f9bdf9e9eebd710a11281c066c5293b76d71d18c)
* fix(migration): also skip negative dock ranks in the hotseat guard
A corrupt database could hold a negative hotseat rank, which would still
index outside the array. Such an entry takes no slot either.
Suggested in review by CodeRabbit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
InvariantDeviceProfile.initGrid took closestProfile's folder arrays as
they were, and DeviceProfileOverrides then wrote the folder settings
into them. The folder sliders take their default from closestProfile
(numFolderRows/numFolderColumns[INDEX_DEFAULT]), so the default they
compared against was the value currently applied, not the grid
option's. Setting the value that was already current, e.g. releasing
the slider thumb without moving it, removed the key and the folder grid
fell back to the grid option's on that reload.
Copy the arrays instead. Nothing else reads closestProfile's folder
arrays: the profiles read the IDP's own arrays, and every initGrid
parses fresh grid options.
(cherry picked from commit 198724e42446611b204d0700e6630a82d0c10502)
Co-authored-by: Claude Opus 5.5 <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>
The "Use multiple lines" setting of the app drawer shows in every
language, but AOSP only honoured it in English, so it did nothing for
other languages. Labels break at spaces, or between words in camel case
names, which works for other scripts too.
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>