From 51d86e8ad1f05f3f5889f925e333a1936607208f Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Sat, 8 Aug 2026 04:41:52 +0000 Subject: [PATCH] Clear ended call chips from status layout --- .../layout/StockLayoutCanvasController.java | 74 +++--------------- .../runtime/render/StatusChipCallSupport.java | 76 +++++++++++++++++++ .../render/UnlockedStockSourceCollector.java | 7 ++ 3 files changed, 95 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusChipCallSupport.java diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java index c4a0acf..2ffb13e 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java @@ -1,11 +1,9 @@ package se.ajpanton.statusbartweak.runtime.layout; -import android.Manifest; import android.annotation.SuppressLint; import android.app.Notification; import android.app.KeyguardManager; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -13,7 +11,6 @@ import android.graphics.drawable.Drawable; import android.os.Looper; import android.os.PowerManager; import android.service.notification.StatusBarNotification; -import android.telecom.TelecomManager; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; @@ -56,6 +53,7 @@ import se.ajpanton.statusbartweak.runtime.render.LockscreenCardsNotificationStri import se.ajpanton.statusbartweak.runtime.notifications.NotificationActiveKeyStore; import se.ajpanton.statusbartweak.runtime.notifications.NotificationUnreadTracker; import se.ajpanton.statusbartweak.runtime.render.OwnedIconHostManager; +import se.ajpanton.statusbartweak.runtime.render.StatusChipCallSupport; import se.ajpanton.statusbartweak.runtime.render.UnlockedIconSnapshotRenderController; import se.ajpanton.statusbartweak.runtime.render.UnlockedIconSnapshotRenderController.RenderResult; import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; @@ -2812,8 +2810,9 @@ final class StockLayoutCanvasController { staleViews.add(view); continue; } - if (!isTrackableStatusChipSurface(view) - && !isTrackedHiddenStatusChipSource(root, view)) { + boolean trackable = isTrackableStatusChipSurface(view); + boolean retainedHidden = !trackable && isTrackedHiddenStatusChipSource(root, view); + if (!trackable && !retainedHidden) { forgetTrackedStatusChipState(view); staleViews.add(view); continue; @@ -3365,7 +3364,7 @@ final class StockLayoutCanvasController { return visibility != View.VISIBLE && isTrackedStatusChipWriteTarget(view) && hasVisibleStatusChipContent(view) - && !isCallStatusChip(view); + && !StatusChipCallSupport.isCallChip(view); } private boolean isTrackedStatusChipWriteTarget(View view) { @@ -3868,6 +3867,9 @@ final class StockLayoutCanvasController { if (ReflectionSupport.getBooleanField(view, "sbtStatusChipForcedHidden", false)) { return false; } + if (StatusChipCallSupport.isInactiveCallChip(view)) { + return false; + } int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth(); int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight(); if (width <= 0 || height <= 0) { @@ -3904,6 +3906,9 @@ final class StockLayoutCanvasController { if (view.getVisibility() != View.VISIBLE) { return false; } + if (StatusChipCallSupport.isInactiveCallChip(view)) { + return false; + } String idName = ViewIdNames.idName(view); if (!"ongoing_activity_capsule".equals(idName)) { return false; @@ -3932,31 +3937,12 @@ final class StockLayoutCanvasController { || !isStatusChipCandidate(view)) { return false; } - if (isCallStatusChip(view) - && view.getVisibility() != View.VISIBLE - && !isPhoneCallActive(view)) { + if (StatusChipCallSupport.isInactiveCallChip(view)) { return false; } return hasVisibleStatusChipContent(view); } - @SuppressLint("MissingPermission") // The SystemUI process owns READ_PHONE_STATE; the explicit check below handles other hosts. - private boolean isPhoneCallActive(View view) { - if (view == null || view.getContext() == null) { - return false; - } - if (view.getContext().checkSelfPermission(Manifest.permission.READ_PHONE_STATE) - != PackageManager.PERMISSION_GRANTED) { - return false; - } - try { - TelecomManager telecomManager = view.getContext().getSystemService(TelecomManager.class); - return telecomManager != null && telecomManager.isInCall(); - } catch (RuntimeException ignored) { - return false; - } - } - private boolean hasVisibleStatusChipContent(View source) { if (!(source instanceof ViewGroup group)) { return false; @@ -3986,42 +3972,6 @@ final class StockLayoutCanvasController { return false; } - private boolean isCallStatusChip(View source) { - if (source == null) { - return false; - } - ArrayDeque queue = new ArrayDeque<>(); - queue.add(source); - int scanned = 0; - while (!queue.isEmpty() && scanned++ < 48) { - View view = queue.removeFirst(); - String className = view.getClass().getName().toLowerCase(Locale.ROOT); - String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); - if (className.contains("call") || idName.contains("call")) { - return true; - } - if (containsCallToken(view.getContentDescription())) { - return true; - } - if (view instanceof TextView textView && containsCallToken(textView.getText())) { - return true; - } - if (view instanceof ViewGroup childGroup) { - for (int i = 0; i < childGroup.getChildCount(); i++) { - queue.addLast(childGroup.getChildAt(i)); - } - } - } - return false; - } - - private boolean containsCallToken(CharSequence value) { - if (value == null || value.length() == 0) { - return false; - } - String text = value.toString().toLowerCase(Locale.ROOT); - return text.contains("call") || text.contains("phone"); - } private boolean isStatusChipCandidate(View view) { String className = view.getClass().getName().toLowerCase(Locale.ROOT); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusChipCallSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusChipCallSupport.java new file mode 100644 index 0000000..85906ba --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusChipCallSupport.java @@ -0,0 +1,76 @@ +package se.ajpanton.statusbartweak.runtime.render; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.pm.PackageManager; +import android.telecom.TelecomManager; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import java.util.ArrayDeque; +import java.util.Locale; + +import se.ajpanton.statusbartweak.runtime.ViewIdNames; + +/** Shared call-chip detection for source suppression and snapshot collection. */ +public final class StatusChipCallSupport { + private StatusChipCallSupport() { + } + + public static boolean isInactiveCallChip(View source) { + return isCallChip(source) && !isPhoneCallActive(source); + } + + public static boolean isCallChip(View source) { + if (source == null) { + return false; + } + ArrayDeque queue = new ArrayDeque<>(); + queue.add(source); + int scanned = 0; + while (!queue.isEmpty() && scanned++ < 48) { + View view = queue.removeFirst(); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); + if (className.contains("call") || idName.contains("call")) { + return true; + } + if (containsCallToken(view.getContentDescription())) { + return true; + } + if (view instanceof TextView textView && containsCallToken(textView.getText())) { + return true; + } + if (view instanceof ViewGroup childGroup) { + for (int i = 0; i < childGroup.getChildCount(); i++) { + queue.addLast(childGroup.getChildAt(i)); + } + } + } + return false; + } + + @SuppressLint("MissingPermission") + public static boolean isPhoneCallActive(View view) { + if (view == null || view.getContext() == null + || view.getContext().checkSelfPermission(Manifest.permission.READ_PHONE_STATE) + != PackageManager.PERMISSION_GRANTED) { + return false; + } + try { + TelecomManager telecomManager = view.getContext().getSystemService(TelecomManager.class); + return telecomManager != null && telecomManager.isInCall(); + } catch (RuntimeException ignored) { + return false; + } + } + + private static boolean containsCallToken(CharSequence value) { + if (value == null || value.length() == 0) { + return false; + } + String text = value.toString().toLowerCase(Locale.ROOT); + return text.contains("call") || text.contains("phone"); + } +} 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 3cb9b48..e199cf0 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 @@ -1220,6 +1220,7 @@ final class UnlockedStockSourceCollector { || !view.isAttachedToWindow() || !ViewGeometry.isDescendantOf(view, root) || ReflectionSupport.getBooleanField(view, "sbtStatusChipForcedHidden", false) + || StatusChipCallSupport.isInactiveCallChip(view) || !isStatusChipCandidate(view) || !hasRenderableStatusChipContent(view)) { return false; @@ -1245,6 +1246,9 @@ final class UnlockedStockSourceCollector { if (ReflectionSupport.getBooleanField(view, "sbtStatusChipForcedHidden", false)) { return false; } + if (StatusChipCallSupport.isInactiveCallChip(view)) { + return false; + } int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth(); int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight(); if (width <= 0 || height <= 0 || root == null || width >= root.getWidth() / 2) { @@ -1336,6 +1340,9 @@ final class UnlockedStockSourceCollector { if (!allowHidden && view.getAlpha() <= 0f && !hasRenderableStatusChipContent(view)) { return false; } + if (StatusChipCallSupport.isInactiveCallChip(view)) { + return false; + } int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth(); int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight(); if (width <= 0 || height <= 0 || width >= root.getWidth() / 2) {