From 19eb97e4051f7f3a09405d7deb0ca3a650b48bf2 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sun, 23 Nov 2025 00:34:02 +0700 Subject: [PATCH] feat: Foldable support --- GITHUB_CHANGELOG.md | 1 + .../util/LawnchairWindowManagerProxy.kt | 38 +++++++++++-------- .../launcher3/util/DisplayController.java | 19 ++++++++-- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/GITHUB_CHANGELOG.md b/GITHUB_CHANGELOG.md index 8492b95b29..0f1dc540ad 100644 --- a/GITHUB_CHANGELOG.md +++ b/GITHUB_CHANGELOG.md @@ -21,6 +21,7 @@ This is a developer-focused change log: * Big word that means Lawnchair will take system default hint bright/dark theme and fallback to luminosity detection for bright/dark mode detection in At a Glance. * Variable font for Launcher3 (????????) * Dynamically get app widget popup icon +* Foldable support (for real) ### 🥞 Snapshot 9 (Development 4 Release 1) diff --git a/lawnchair/src/app/lawnchair/util/LawnchairWindowManagerProxy.kt b/lawnchair/src/app/lawnchair/util/LawnchairWindowManagerProxy.kt index 4108e21169..d6b9965da2 100644 --- a/lawnchair/src/app/lawnchair/util/LawnchairWindowManagerProxy.kt +++ b/lawnchair/src/app/lawnchair/util/LawnchairWindowManagerProxy.kt @@ -34,22 +34,28 @@ class LawnchairWindowManagerProxy @Inject constructor() : WindowManagerProxy(Uti val TAG = "LC-WindowManagerProxy" override fun estimateInternalDisplayBounds(displayInfoContext: Context): ArrayMap> { - // Lawnchair-TODO(Foldable): See estimateInternalDisplayBounds in [SystemWindowManagerProxy] - // Wait... If SWMP fallback to getMaximumWindowMetrics from [WindowManager] public APIs - // That means that we can combine getMaximumWindowMetrics with getCurrentWindowMetrics! - // Wait... but doesn't that only account for two screens? What if there were more than two - // Until then *this* is a good enough solutions - // Wait... Launcher calls eIDB every time you interact, what happened if you switched screen - // to the screen with maximum window metrics screen? won't that cause duplicate with - // getCurrentWindowMetrics? - // - // True... we duplicate it, and cache it? Is there a better way to do this? - // We can't just call [getPossibleMaximumWindowMetrics] because it's an internal apis, - // reflection won't work. Thanks @SystemApi - val info = getDisplayInfo(displayInfoContext).normalize(this) - val bounds = estimateWindowBounds(displayInfoContext, info) - val result = ArrayMap>().apply { put(info, bounds) } - Log.d(TAG, "estimateInternalDisplayBounds: $result") + val result = ArrayMap>() + val displayManager = displayInfoContext.getSystemService(DisplayManager::class.java) ?: return result + + val displays = displayManager.displays + + for (display in displays) { + try { + val contextForDisplay = displayInfoContext.createDisplayContext(display) + val wm = contextForDisplay.getSystemService(WindowManager::class.java) + val metrics = if (Utilities.ATLEAST_R) wm.maximumWindowMetrics else null + + if (metrics != null) { + val info = getDisplayInfo(metrics, display.rotation).normalize(this) + val bounds = estimateWindowBounds(contextForDisplay, info) + + result[info] = bounds + } + } catch (e: Exception) { + Log.e(TAG, "Error estimating bounds for display ${display.displayId}", e) + } + } + return result } diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java index 5f3c63e202..19afc719da 100644 --- a/src/com/android/launcher3/util/DisplayController.java +++ b/src/com/android/launcher3/util/DisplayController.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.util; +import static android.content.pm.PackageManager.FEATURE_SENSOR_HINGE_ANGLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -547,6 +548,8 @@ public class DisplayController implements DesktopVisibilityListener { private final boolean mIsNightModeActive; + private boolean mIsFoldable; + public Info(Context displayInfoContext) { /* don't need system overrides for external displays */ this(displayInfoContext, enableScalabilityForDesktopExperience() @@ -575,6 +578,10 @@ public class DisplayController implements DesktopVisibilityListener { mScreenSizeDp = new PortraitSize(config.screenHeightDp, config.screenWidthDp); navigationMode = wmProxy.getNavigationMode(displayInfoContext); mIsNightModeActive = config.isNightModeActive(); + + // LC: Hacky stuff but it work! + mIsFoldable = Utilities.ATLEAST_R && displayInfoContext.getPackageManager() + .hasSystemFeature(FEATURE_SENSOR_HINGE_ANGLE); mPerDisplayBounds.putAll(perDisplayBoundsCache); List cachedValue = getCurrentBounds(); @@ -726,12 +733,16 @@ public class DisplayController implements DesktopVisibilityListener { .mapToInt(bounds -> isTablet(bounds) ? flagTablet : flagPhone) .reduce(0, (a, b) -> a | b); - //type = flagTablet; - // pE-TODO(n/a): Testing DC! if (type == (flagPhone | flagTablet)) { - Log.d("LC-DisplayController", "Device has multiple display (Phone|Tablet)"); + Log.d("LC-DisplayController", "Device has multiple display (Phone|Tablet) based on bounds"); return TYPE_MULTI_DISPLAY; - } else if (type == flagTablet) { + } + if (type == flagTablet) { + // LC: Hacky stuff but it work! + if (mIsFoldable) { + Log.d("LC-DisplayController", "Device is Foldable (Tablet && Hinge Detected)"); + return TYPE_MULTI_DISPLAY; + } Log.d("LC-DisplayController", "Device has tablet profile (Tablet)"); return TYPE_TABLET; } else {