Fix foldable camera cutout overrides

This commit is contained in:
ajp_anton
2026-06-17 19:46:35 +00:00
parent f16e1a8ad5
commit 28cc72b7ea
8 changed files with 161 additions and 22 deletions
@@ -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<View, CameraTypeCache> 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;
}
}
}
@@ -96,7 +96,7 @@ public final class UnlockedIconSnapshotRenderController {
root, root,
settings, settings,
scene, scene,
ViewGeometry.middleCutout(root), EffectiveCutoutSupport.layoutMiddleCutout(root, settings),
anchors, anchors,
previousIcons); previousIcons);
LayoutInputSignature previousSignature = lastLayoutSignatureByRoot.get(root); LayoutInputSignature previousSignature = lastLayoutSignatureByRoot.get(root);
@@ -154,12 +154,12 @@ public final class UnlockedIconSnapshotRenderController {
root, root,
currentClockLayout, currentClockLayout,
settings, settings,
UnlockedLayoutPlanner.cutoutBounds(ViewGeometry.middleCutout(root), settings.clockMiddleCutoutGapPx)); clockCutoutBounds(root, settings));
ClockPlacement textPlacement = clockTextPlacementForPrevious( ClockPlacement textPlacement = clockTextPlacementForPrevious(
root, root,
currentClockLayout, currentClockLayout,
settings, settings,
UnlockedLayoutPlanner.cutoutBounds(ViewGeometry.middleCutout(root), settings.clockMiddleCutoutGapPx), clockCutoutBounds(root, settings),
previousPlan != null ? previousPlan.clockPlacement : null); previousPlan != null ? previousPlan.clockPlacement : null);
ClockPlacement updatedClock = clockPlacementWithUpdatedText( ClockPlacement updatedClock = clockPlacementWithUpdatedText(
previousPlan != null ? previousPlan.clockPlacement : null, previousPlan != null ? previousPlan.clockPlacement : null,
@@ -193,7 +193,7 @@ public final class UnlockedIconSnapshotRenderController {
root, root,
currentClockLayout, currentClockLayout,
settings, settings,
UnlockedLayoutPlanner.cutoutBounds(ViewGeometry.middleCutout(root), settings.clockMiddleCutoutGapPx)); clockCutoutBounds(root, settings));
} }
if (scene.isLockscreen() && clockEnabledForScene(settings, scene) && currentClockLayout == null) { if (scene.isLockscreen() && clockEnabledForScene(settings, scene) && currentClockLayout == null) {
clearLockscreenHosts(clockHost, carrierHost, chipHost, statusHost, notificationHost); clearLockscreenHosts(clockHost, carrierHost, chipHost, statusHost, notificationHost);
@@ -724,7 +724,13 @@ public final class UnlockedIconSnapshotRenderController {
anchors, anchors,
settings, settings,
scene, 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( private ClockLayout buildClockLayout(
@@ -68,13 +68,14 @@ final class UnlockedLayoutPlanner {
aodInitialStatusbarHeightByRoot.remove(root); aodInitialStatusbarHeightByRoot.remove(root);
} }
ArrayList<UnlockedLayoutBand> bands = new ArrayList<>(); ArrayList<UnlockedLayoutBand> 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() Bounds layoutCutoutBounds = scene != null && scene.isAod()
? aodCutoutBounds(root, cutout, settings.layoutPaddingCutoutPx) ? aodCutoutBounds(root, layoutCutout, settings.layoutPaddingCutoutPx)
: cutoutBounds(cutout, settings.layoutPaddingCutoutPx); : cutoutBounds(layoutCutout, settings.layoutPaddingCutoutPx);
Bounds clockCutoutBounds = scene != null && scene.isAod() Bounds clockCutoutBounds = scene != null && scene.isAod()
? aodCutoutBounds(root, cutout, settings.clockMiddleCutoutGapPx) ? aodCutoutBounds(root, clockCutout, settings.clockMiddleCutoutGapPx)
: cutoutBounds(cutout, settings.clockMiddleCutoutGapPx); : cutoutBounds(clockCutout, settings.clockMiddleCutoutGapPx);
Bounds aodStatusAnchorBounds = scene != null && scene.isAod() Bounds aodStatusAnchorBounds = scene != null && scene.isAod()
? aodStatusAnchorBounds(root, anchors, scene) ? aodStatusAnchorBounds(root, anchors, scene)
: null; : null;
@@ -1626,6 +1626,7 @@ final class UnlockedStockSourceCollector {
settings.clockCustomFormatEnabled, settings.clockCustomFormatEnabled,
settings.clockCustomFormat, settings.clockCustomFormat,
settings.clockIgnoreUnderDisplayCameras, settings.clockIgnoreUnderDisplayCameras,
settings.clockCameraTypeOverrides,
settings.layoutPaddingCutoutPx, settings.layoutPaddingCutoutPx,
settings.layoutPaddingLeftPx, settings.layoutPaddingLeftPx,
settings.layoutPaddingRightPx, settings.layoutPaddingRightPx,
@@ -29,6 +29,9 @@ public final class ClockCameraTypeSupport {
public static final class CameraInfo { public static final class CameraInfo {
public int semDisplayDeviceType = Integer.MIN_VALUE; public int semDisplayDeviceType = Integer.MIN_VALUE;
public int fillUdcDisplayCutout = 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 boolean isUdcMainDisplay;
public String cutoutType; public String cutoutType;
public String cutoutString; public String cutoutString;
@@ -59,8 +62,15 @@ public final class ClockCameraTypeSupport {
: "blocking cutout"; : "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 + "|" String descriptor = semDisplayDeviceType + "|"
+ rootWidth + "x" + rootHeight + "|"
+ rotation + "|"
+ safe(cutoutType) + "|" + safe(cutoutType) + "|"
+ safe(cutoutString); + safe(cutoutString);
return "cam_" + shortSha1(descriptor); return "cam_" + shortSha1(descriptor);
@@ -78,6 +88,20 @@ public final class ClockCameraTypeSupport {
finalizeCameraInfo(info, settings); finalizeCameraInfo(info, settings);
return info; 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.semDisplayDeviceType = readSemDisplayDeviceType(context);
info.fillUdcDisplayCutout = readGlobalInt(context, "fill_udc_display_cutout"); info.fillUdcDisplayCutout = readGlobalInt(context, "fill_udc_display_cutout");
Object displayLifecycle = resolveDisplayLifecycle(context); Object displayLifecycle = resolveDisplayLifecycle(context);
@@ -93,15 +117,9 @@ public final class ClockCameraTypeSupport {
finalizeCameraInfo(info, settings); finalizeCameraInfo(info, settings);
return info; return info;
} }
Object input = newInstance(inputClass, context); Object input = newInstance(inputClass, context);
Display display = root.getDisplay();
if (display != null) { if (display != null) {
setIntField(input, "rotation", display.getRotation()); setIntField(input, "rotation", info.rotation);
}
int width = root.getWidth();
if (width <= 0 && root.getRootView() != null) {
width = root.getRootView().getWidth();
} }
if (width > 0) { if (width > 0) {
setIntField(input, "statusBarWidth", width); setIntField(input, "statusBarWidth", width);
@@ -196,6 +214,9 @@ public final class ClockCameraTypeSupport {
} }
info.cameraId = buildCameraId( info.cameraId = buildCameraId(
info.semDisplayDeviceType, info.semDisplayDeviceType,
info.rootWidth,
info.rootHeight,
info.rotation,
info.cutoutType, info.cutoutType,
info.cutoutString); info.cutoutString);
boolean explicitUdc = info.isUdcMainDisplay || "UDC".equals(info.cutoutType); boolean explicitUdc = info.isUdcMainDisplay || "UDC".equals(info.cutoutType);
@@ -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) { private static boolean hasSettingsProvider(Context context) {
if (context == null) { if (context == null) {
return false; return false;
@@ -338,7 +338,8 @@ public class IconsDebugFragment extends Fragment {
if (TextUtils.isEmpty(cameraId)) { if (TextUtils.isEmpty(cameraId)) {
identifiedView.setText(getString(R.string.debug_camera_identified_as, identifiedView.setText(getString(R.string.debug_camera_identified_as,
getString(R.string.debug_camera_unknown))); getString(R.string.debug_camera_unknown),
""));
if (overrideRow != null) { if (overrideRow != null) {
overrideRow.setVisibility(View.GONE); overrideRow.setVisibility(View.GONE);
} }
@@ -352,7 +353,8 @@ public class IconsDebugFragment extends Fragment {
: ClockCameraTypeSupport.normalizeType(effectiveTypeFromReport)); : ClockCameraTypeSupport.normalizeType(effectiveTypeFromReport));
identifiedView.setText(getString( identifiedView.setText(getString(
R.string.debug_camera_identified_as, R.string.debug_camera_identified_as,
readableType(currentAutoType))); readableType(currentAutoType),
cameraId));
if (overrideRow != null && overrideView != null && actionButton != null) { if (overrideRow != null && overrideView != null && actionButton != null) {
overrideRow.setVisibility(View.VISIBLE); overrideRow.setVisibility(View.VISIBLE);
+2 -2
View File
@@ -44,7 +44,7 @@
<string name="debug_notification_permission_required">Notification permission is required for debug icons.</string> <string name="debug_notification_permission_required">Notification permission is required for debug icons.</string>
<string name="debug_camera_current_title">Current camera</string> <string name="debug_camera_current_title">Current camera</string>
<string name="debug_camera_other_title">Other overridden cameras</string> <string name="debug_camera_other_title">Other overridden cameras</string>
<string name="debug_camera_identified_as">Identified as %1$s</string> <string name="debug_camera_identified_as">Identified as %1$s\n%2$s</string>
<string name="debug_camera_overridden_as">Overridden as %1$s</string> <string name="debug_camera_overridden_as">Overridden as %1$s</string>
<string name="debug_camera_action_wrong">This is wrong</string> <string name="debug_camera_action_wrong">This is wrong</string>
<string name="debug_camera_action_undo"></string> <string name="debug_camera_action_undo"></string>
@@ -131,7 +131,7 @@
<string name="status_chips_hide_media_unlocked_label">Hide media chip (unlocked)</string> <string name="status_chips_hide_media_unlocked_label">Hide media chip (unlocked)</string>
<string name="status_chips_hide_navigation_unlocked_label">Hide navigation chip (unlocked)</string> <string name="status_chips_hide_navigation_unlocked_label">Hide navigation chip (unlocked)</string>
<string name="status_chips_hide_call_unlocked_label">Hide call chip (unlocked)</string> <string name="status_chips_hide_call_unlocked_label">Hide call chip (unlocked)</string>
<string name="status_chips_hide_battery_label">Hide charging battery chip</string> <string name="status_chips_hide_battery_label">Hide charging battery chip (unlocked)</string>
<string name="misc_title">Misc</string> <string name="misc_title">Misc</string>
<string name="battery_bar_title">Battery bar</string> <string name="battery_bar_title">Battery bar</string>
<string name="battery_bar_master_toggle_title">Master toggle</string> <string name="battery_bar_master_toggle_title">Master toggle</string>