From 28cc72b7ea3c00ee84d812b6979ba5e5e2c16767 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Wed, 17 Jun 2026 19:46:35 +0000 Subject: [PATCH] Fix foldable camera cutout overrides --- .../render/EffectiveCutoutSupport.java | 85 +++++++++++++++++++ .../UnlockedIconSnapshotRenderController.java | 16 ++-- .../runtime/render/UnlockedLayoutPlanner.java | 11 +-- .../render/UnlockedStockSourceCollector.java | 1 + .../settings/ClockCameraTypeSupport.java | 37 ++++++-- .../shell/settings/SbtSettings.java | 23 +++++ .../shell/ui/IconsDebugFragment.java | 6 +- app/src/main/res/values/strings.xml | 4 +- 8 files changed, 161 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/se/ajpanton/statusbartweak/runtime/render/EffectiveCutoutSupport.java diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/EffectiveCutoutSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/EffectiveCutoutSupport.java new file mode 100644 index 0000000..9608f37 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/EffectiveCutoutSupport.java @@ -0,0 +1,85 @@ +package se.ajpanton.statusbartweak.runtime.render; + +import android.graphics.Rect; +import android.view.DisplayCutout; +import android.view.View; +import android.view.WindowInsets; + +import java.util.Objects; +import java.util.WeakHashMap; + +import se.ajpanton.statusbartweak.shell.settings.ClockCameraTypeSupport; +import se.ajpanton.statusbartweak.shell.settings.SbtSettings; + +final class EffectiveCutoutSupport { + private static final WeakHashMap CAMERA_TYPE_BY_ROOT = new WeakHashMap<>(); + + private EffectiveCutoutSupport() { + } + + static Rect middleCutout(View root, SbtSettings settings, boolean ignoreUnderDisplayCamera) { + Rect cutout = ViewGeometry.middleCutout(root); + if (cutout == null || !ignoreUnderDisplayCamera) { + return cutout; + } + String effectiveType = effectiveCameraType(root, cutout, settings); + return ClockCameraTypeSupport.TYPE_UDC.equals(effectiveType) ? null : cutout; + } + + static Rect layoutMiddleCutout(View root, SbtSettings settings) { + return middleCutout( + root, + settings, + settings != null && settings.layoutIgnoreUnderDisplayCameras); + } + + static Rect clockMiddleCutout(View root, SbtSettings settings) { + return middleCutout( + root, + settings, + settings != null && settings.clockIgnoreUnderDisplayCameras); + } + + private static String effectiveCameraType(View root, Rect cutout, SbtSettings settings) { + int signature = cameraTypeSignature(root, cutout, settings); + CameraTypeCache cached = CAMERA_TYPE_BY_ROOT.get(root); + if (cached != null && cached.signature == signature) { + return cached.effectiveType; + } + WindowInsets insets = root != null ? root.getRootWindowInsets() : null; + DisplayCutout displayCutout = insets != null ? insets.getDisplayCutout() : null; + ClockCameraTypeSupport.CameraInfo cameraInfo = + ClockCameraTypeSupport.resolveCameraInfo(root, displayCutout, settings); + String effectiveType = cameraInfo.effectiveType; + if (root != null) { + SbtSettings.setClockCameraDebugState( + root.getContext(), + cameraInfo.cameraId, + cameraInfo.automaticType, + cameraInfo.effectiveType); + } + CAMERA_TYPE_BY_ROOT.put(root, new CameraTypeCache(signature, effectiveType)); + return effectiveType; + } + + private static int cameraTypeSignature(View root, Rect cutout, SbtSettings settings) { + return Objects.hash( + root != null ? root.getWidth() : 0, + root != null ? root.getHeight() : 0, + cutout != null ? cutout.left : 0, + cutout != null ? cutout.top : 0, + cutout != null ? cutout.right : 0, + cutout != null ? cutout.bottom : 0, + settings != null ? settings.clockCameraTypeOverrides : null); + } + + private static final class CameraTypeCache { + final int signature; + final String effectiveType; + + CameraTypeCache(int signature, String effectiveType) { + this.signature = signature; + this.effectiveType = effectiveType; + } + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java index 914327f..a2fa3a4 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java @@ -96,7 +96,7 @@ public final class UnlockedIconSnapshotRenderController { root, settings, scene, - ViewGeometry.middleCutout(root), + EffectiveCutoutSupport.layoutMiddleCutout(root, settings), anchors, previousIcons); LayoutInputSignature previousSignature = lastLayoutSignatureByRoot.get(root); @@ -154,12 +154,12 @@ public final class UnlockedIconSnapshotRenderController { root, currentClockLayout, settings, - UnlockedLayoutPlanner.cutoutBounds(ViewGeometry.middleCutout(root), settings.clockMiddleCutoutGapPx)); + clockCutoutBounds(root, settings)); ClockPlacement textPlacement = clockTextPlacementForPrevious( root, currentClockLayout, settings, - UnlockedLayoutPlanner.cutoutBounds(ViewGeometry.middleCutout(root), settings.clockMiddleCutoutGapPx), + clockCutoutBounds(root, settings), previousPlan != null ? previousPlan.clockPlacement : null); ClockPlacement updatedClock = clockPlacementWithUpdatedText( previousPlan != null ? previousPlan.clockPlacement : null, @@ -193,7 +193,7 @@ public final class UnlockedIconSnapshotRenderController { root, currentClockLayout, settings, - UnlockedLayoutPlanner.cutoutBounds(ViewGeometry.middleCutout(root), settings.clockMiddleCutoutGapPx)); + clockCutoutBounds(root, settings)); } if (scene.isLockscreen() && clockEnabledForScene(settings, scene) && currentClockLayout == null) { clearLockscreenHosts(clockHost, carrierHost, chipHost, statusHost, notificationHost); @@ -724,7 +724,13 @@ public final class UnlockedIconSnapshotRenderController { anchors, settings, scene, - UnlockedLayoutPlanner.cutoutBounds(ViewGeometry.middleCutout(root), settings != null ? settings.clockMiddleCutoutGapPx : 0)); + clockCutoutBounds(root, settings)); + } + + private Bounds clockCutoutBounds(View root, SbtSettings settings) { + return UnlockedLayoutPlanner.cutoutBounds( + EffectiveCutoutSupport.clockMiddleCutout(root, settings), + settings != null ? settings.clockMiddleCutoutGapPx : 0); } private ClockLayout buildClockLayout( diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java index 282e2f6..24bb0ab 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java @@ -68,13 +68,14 @@ final class UnlockedLayoutPlanner { aodInitialStatusbarHeightByRoot.remove(root); } ArrayList bands = new ArrayList<>(); - Rect cutout = ViewGeometry.middleCutout(root); + Rect layoutCutout = EffectiveCutoutSupport.layoutMiddleCutout(root, settings); + Rect clockCutout = EffectiveCutoutSupport.clockMiddleCutout(root, settings); Bounds layoutCutoutBounds = scene != null && scene.isAod() - ? aodCutoutBounds(root, cutout, settings.layoutPaddingCutoutPx) - : cutoutBounds(cutout, settings.layoutPaddingCutoutPx); + ? aodCutoutBounds(root, layoutCutout, settings.layoutPaddingCutoutPx) + : cutoutBounds(layoutCutout, settings.layoutPaddingCutoutPx); Bounds clockCutoutBounds = scene != null && scene.isAod() - ? aodCutoutBounds(root, cutout, settings.clockMiddleCutoutGapPx) - : cutoutBounds(cutout, settings.clockMiddleCutoutGapPx); + ? aodCutoutBounds(root, clockCutout, settings.clockMiddleCutoutGapPx) + : cutoutBounds(clockCutout, settings.clockMiddleCutoutGapPx); Bounds aodStatusAnchorBounds = scene != null && scene.isAod() ? aodStatusAnchorBounds(root, anchors, scene) : null; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java index 21be8c4..483611c 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java @@ -1626,6 +1626,7 @@ final class UnlockedStockSourceCollector { settings.clockCustomFormatEnabled, settings.clockCustomFormat, settings.clockIgnoreUnderDisplayCameras, + settings.clockCameraTypeOverrides, settings.layoutPaddingCutoutPx, settings.layoutPaddingLeftPx, settings.layoutPaddingRightPx, diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/ClockCameraTypeSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/ClockCameraTypeSupport.java index be67b63..4e0201b 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/ClockCameraTypeSupport.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/ClockCameraTypeSupport.java @@ -29,6 +29,9 @@ public final class ClockCameraTypeSupport { public static final class CameraInfo { public int semDisplayDeviceType = Integer.MIN_VALUE; public int fillUdcDisplayCutout = Integer.MIN_VALUE; + public int rootWidth = Integer.MIN_VALUE; + public int rootHeight = Integer.MIN_VALUE; + public int rotation = Integer.MIN_VALUE; public boolean isUdcMainDisplay; public String cutoutType; public String cutoutString; @@ -59,8 +62,15 @@ public final class ClockCameraTypeSupport { : "blocking cutout"; } - public static String buildCameraId(int semDisplayDeviceType, String cutoutType, String cutoutString) { + private static String buildCameraId(int semDisplayDeviceType, + int rootWidth, + int rootHeight, + int rotation, + String cutoutType, + String cutoutString) { String descriptor = semDisplayDeviceType + "|" + + rootWidth + "x" + rootHeight + "|" + + rotation + "|" + safe(cutoutType) + "|" + safe(cutoutString); return "cam_" + shortSha1(descriptor); @@ -78,6 +88,20 @@ public final class ClockCameraTypeSupport { finalizeCameraInfo(info, settings); return info; } + Display display = root.getDisplay(); + if (display != null) { + info.rotation = display.getRotation(); + } + int width = root.getWidth(); + if (width <= 0 && root.getRootView() != null) { + width = root.getRootView().getWidth(); + } + info.rootWidth = width; + int height = root.getHeight(); + if (height <= 0 && root.getRootView() != null) { + height = root.getRootView().getHeight(); + } + info.rootHeight = height; info.semDisplayDeviceType = readSemDisplayDeviceType(context); info.fillUdcDisplayCutout = readGlobalInt(context, "fill_udc_display_cutout"); Object displayLifecycle = resolveDisplayLifecycle(context); @@ -93,15 +117,9 @@ public final class ClockCameraTypeSupport { finalizeCameraInfo(info, settings); return info; } - Object input = newInstance(inputClass, context); - Display display = root.getDisplay(); if (display != null) { - setIntField(input, "rotation", display.getRotation()); - } - int width = root.getWidth(); - if (width <= 0 && root.getRootView() != null) { - width = root.getRootView().getWidth(); + setIntField(input, "rotation", info.rotation); } if (width > 0) { setIntField(input, "statusBarWidth", width); @@ -196,6 +214,9 @@ public final class ClockCameraTypeSupport { } info.cameraId = buildCameraId( info.semDisplayDeviceType, + info.rootWidth, + info.rootHeight, + info.rotation, info.cutoutType, info.cutoutString); boolean explicitUdc = info.isUdcMainDisplay || "UDC".equals(info.cutoutType); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java index 89d9864..2c8c849 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java @@ -1129,6 +1129,29 @@ public final class SbtSettings { } } + public static void setClockCameraDebugState(Context context, + String cameraId, + String autoType, + String effectiveType) { + if (context == null || MODULE_PACKAGE.equals(context.getPackageName()) || !hasSettingsProvider(context)) { + return; + } + try { + Bundle extras = new Bundle(); + extras.putString(KEY_DEBUG_CURRENT_CAMERA_ID, cameraId != null ? cameraId : ""); + extras.putString(KEY_DEBUG_CURRENT_CAMERA_AUTO_TYPE, autoType != null ? autoType : ""); + extras.putString(KEY_DEBUG_CURRENT_CAMERA_EFFECTIVE_TYPE, + effectiveType != null ? effectiveType : ""); + context.getContentResolver() + .call(settingsProviderUri(), + SbtSettingsProvider.METHOD_SET_CLOCK_CAMERA_DEBUG_STATE, + null, + extras); + } catch (Throwable ignored) { + // Debug camera metadata is advisory UI state only. + } + } + private static boolean hasSettingsProvider(Context context) { if (context == null) { return false; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/IconsDebugFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/IconsDebugFragment.java index 7c3fc8c..b55d33d 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/IconsDebugFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/IconsDebugFragment.java @@ -338,7 +338,8 @@ public class IconsDebugFragment extends Fragment { if (TextUtils.isEmpty(cameraId)) { identifiedView.setText(getString(R.string.debug_camera_identified_as, - getString(R.string.debug_camera_unknown))); + getString(R.string.debug_camera_unknown), + "")); if (overrideRow != null) { overrideRow.setVisibility(View.GONE); } @@ -352,7 +353,8 @@ public class IconsDebugFragment extends Fragment { : ClockCameraTypeSupport.normalizeType(effectiveTypeFromReport)); identifiedView.setText(getString( R.string.debug_camera_identified_as, - readableType(currentAutoType))); + readableType(currentAutoType), + cameraId)); if (overrideRow != null && overrideView != null && actionButton != null) { overrideRow.setVisibility(View.VISIBLE); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 84948fa..edbec59 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,7 +44,7 @@ Notification permission is required for debug icons. Current camera Other overridden cameras - Identified as %1$s + Identified as %1$s\n%2$s Overridden as %1$s This is wrong @@ -131,7 +131,7 @@ Hide media chip (unlocked) Hide navigation chip (unlocked) Hide call chip (unlocked) - Hide charging battery chip + Hide charging battery chip (unlocked) Misc Battery bar Master toggle