Merge "Add mechanism to trace specific sf tx changes without rebuilding fw" into main
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2f3d73a..42a0c9a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -70,6 +70,7 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -796,7 +797,7 @@
         if (nativeObject != 0) {
             // Only add valid surface controls to the registry. This is called at the end of this
             // method since its information is dumped if the process threshold is reached.
-            addToRegistry();
+            SurfaceControlRegistry.getProcessInstance().add(this);
         }
     }
 
@@ -1501,7 +1502,7 @@
             if (mCloseGuard != null) {
                 mCloseGuard.warnIfOpen();
             }
-            removeFromRegistry();
+            SurfaceControlRegistry.getProcessInstance().remove(this);
         } finally {
             super.finalize();
         }
@@ -1519,6 +1520,10 @@
      */
     public void release() {
         if (mNativeObject != 0) {
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "release", null, this, null);
+            }
             mFreeNativeResources.run();
             mNativeObject = 0;
             mNativeHandle = 0;
@@ -1532,7 +1537,7 @@
                     mChoreographer = null;
                 }
             }
-            removeFromRegistry();
+            SurfaceControlRegistry.getProcessInstance().remove(this);
         }
     }
 
@@ -2765,8 +2770,10 @@
 
         private Transaction(long nativeObject) {
             mNativeObject = nativeObject;
-            mFreeNativeResources =
-                    sRegistry.registerNativeAllocation(this, mNativeObject);
+            mFreeNativeResources = sRegistry.registerNativeAllocation(this, mNativeObject);
+            if (!SurfaceControlRegistry.sCallStackDebuggingInitialized) {
+                SurfaceControlRegistry.initializeCallStackDebugging();
+            }
         }
 
         private Transaction(Parcel in) {
@@ -2845,6 +2852,11 @@
             applyResizedSurfaces();
             notifyReparentedSurfaces();
             nativeApplyTransaction(mNativeObject, sync, oneWay);
+
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "apply", this, null, null);
+            }
         }
 
         /**
@@ -2920,6 +2932,10 @@
         @UnsupportedAppUsage
         public Transaction show(SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "show", this, sc, null);
+            }
             nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
             return this;
         }
@@ -2934,6 +2950,10 @@
         @UnsupportedAppUsage
         public Transaction hide(SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "hide", this, sc, null);
+            }
             nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
             return this;
         }
@@ -2950,6 +2970,10 @@
         @NonNull
         public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setPosition", this, sc, "x=" + x + " y=" + y);
+            }
             nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
             return this;
         }
@@ -2968,6 +2992,10 @@
             checkPreconditions(sc);
             Preconditions.checkArgument(scaleX >= 0, "Negative value passed in for scaleX");
             Preconditions.checkArgument(scaleY >= 0, "Negative value passed in for scaleY");
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setScale", this, sc, "sx=" + scaleX + " sy=" + scaleY);
+            }
             nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY);
             return this;
         }
@@ -2985,6 +3013,10 @@
         public Transaction setBufferSize(@NonNull SurfaceControl sc,
                 @IntRange(from = 0) int w, @IntRange(from = 0) int h) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setBufferSize", this, sc, "w=" + w + " h=" + h);
+            }
             mResizedSurfaces.put(sc, new Point(w, h));
             return this;
         }
@@ -3005,6 +3037,10 @@
         public Transaction setFixedTransformHint(@NonNull SurfaceControl sc,
                        @Surface.Rotation int transformHint) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setFixedTransformHint", this, sc, "hint=" + transformHint);
+            }
             nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, transformHint);
             return this;
         }
@@ -3018,6 +3054,10 @@
         @NonNull
         public Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "unsetFixedTransformHint", this, sc, null);
+            }
             nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, -1/* INVALID_ROTATION */);
             return this;
         }
@@ -3035,6 +3075,10 @@
         public Transaction setLayer(@NonNull SurfaceControl sc,
                 @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setLayer", this, sc, "z=" + z);
+            }
             nativeSetLayer(mNativeObject, sc.mNativeObject, z);
             return this;
         }
@@ -3044,6 +3088,10 @@
          */
         public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setRelativeLayer", this, sc, "relTo=" + relativeTo + " z=" + z);
+            }
             nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, relativeTo.mNativeObject, z);
             return this;
         }
@@ -3053,6 +3101,10 @@
          */
         public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "unsetFixedTransformHint", this, sc, "region=" + transparentRegion);
+            }
             nativeSetTransparentRegionHint(mNativeObject,
                     sc.mNativeObject, transparentRegion);
             return this;
@@ -3069,6 +3121,10 @@
         public Transaction setAlpha(@NonNull SurfaceControl sc,
                 @FloatRange(from = 0.0, to = 1.0) float alpha) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setAlpha", this, sc, "alpha=" + alpha);
+            }
             nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
             return this;
         }
@@ -3124,6 +3180,11 @@
         public Transaction setMatrix(SurfaceControl sc,
                 float dsdx, float dtdx, float dtdy, float dsdy) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setMatrix", this, sc,
+                        "dsdx=" + dsdx + " dtdx=" + dtdx + " dtdy=" + dtdy + " dsdy=" + dsdy);
+            }
             nativeSetMatrix(mNativeObject, sc.mNativeObject,
                     dsdx, dtdx, dtdy, dsdy);
             return this;
@@ -3189,6 +3250,10 @@
         @UnsupportedAppUsage
         public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setWindowCrop", this, sc, "crop=" + crop);
+            }
             if (crop != null) {
                 nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
                         crop.left, crop.top, crop.right, crop.bottom);
@@ -3211,6 +3276,10 @@
          */
         public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCrop", this, sc, "crop=" + crop);
+            }
             if (crop != null) {
                 Preconditions.checkArgument(crop.isValid(), "Crop isn't valid.");
                 nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
@@ -3233,6 +3302,10 @@
          */
         public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCornerRadius", this, sc, "w=" + width + " h=" + height);
+            }
             nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height);
             return this;
         }
@@ -3247,6 +3320,10 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCornerRadius", this, sc, "cornerRadius=" + cornerRadius);
+            }
             nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius);
 
             return this;
@@ -3262,6 +3339,10 @@
          */
         public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setBackgroundBlurRadius", this, sc, "radius=" + radius);
+            }
             nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius);
             return this;
         }
@@ -3318,6 +3399,10 @@
         public Transaction reparent(@NonNull SurfaceControl sc,
                 @Nullable SurfaceControl newParent) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "reparent", this, sc, "newParent=" + newParent);
+            }
             long otherObject = 0;
             if (newParent != null) {
                 newParent.checkNotReleased();
@@ -3337,6 +3422,11 @@
         @UnsupportedAppUsage
         public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "reparent", this, sc,
+                        "r=" + color[0] + " g=" + color[1] + " b=" + color[2]);
+            }
             nativeSetColor(mNativeObject, sc.mNativeObject, color);
             return this;
         }
@@ -3347,6 +3437,10 @@
         */
         public Transaction unsetColor(SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "unsetColor", this, sc, null);
+            }
             nativeSetColor(mNativeObject, sc.mNativeObject, INVALID_COLOR);
             return this;
         }
@@ -3358,6 +3452,10 @@
          */
         public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setSecure", this, sc, "secure=" + isSecure);
+            }
             if (isSecure) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
             } else {
@@ -3411,6 +3509,10 @@
         @NonNull
         public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setOpaque", this, sc, "opaque=" + isOpaque);
+            }
             if (isOpaque) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
             } else {
@@ -3580,6 +3682,10 @@
           */
         public Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setShadowRadius", this, sc, "radius=" + shadowRadius);
+            }
             nativeSetShadowRadius(mNativeObject, sc.mNativeObject, shadowRadius);
             return this;
         }
@@ -4463,26 +4569,6 @@
         return -1;
     }
 
-    /**
-     * Adds this surface control to the registry for this process if it is created.
-     */
-    private void addToRegistry() {
-        final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
-        if (registry != null) {
-            registry.add(this);
-        }
-    }
-
-    /**
-     * Removes this surface control from the registry for this process.
-     */
-    private void removeFromRegistry() {
-        final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
-        if (registry != null) {
-            registry.remove(this);
-        }
-    }
-
     // Called by native
     private static void invokeReleaseCallback(Consumer<SyncFence> callback, long nativeFencePtr) {
         SyncFence fence = new SyncFence(nativeFencePtr);
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 0f35048..52be8f6 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -23,7 +23,9 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.os.Build;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -104,6 +106,9 @@
     // Number of surface controls to dump when the max threshold is exceeded
     private static final int DUMP_LIMIT = 256;
 
+    // An instance of a registry that is a no-op
+    private static final SurfaceControlRegistry NO_OP_REGISTRY = new NoOpRegistry();
+
     // Static lock, must be held for all registry operations
     private static final Object sLock = new Object();
 
@@ -113,6 +118,22 @@
     // The registry for a given process
     private static volatile SurfaceControlRegistry sProcessRegistry;
 
+    // Whether call stack debugging has been initialized. This is evaluated only once per process
+    // instance when the first SurfaceControl.Transaction object is created
+    static boolean sCallStackDebuggingInitialized;
+
+    // Whether call stack debugging is currently enabled, ie. whether there is a valid match string
+    // for either a specific surface control name or surface control transaction method
+    static boolean sCallStackDebuggingEnabled;
+
+    // The name of the surface control to log stack traces for.  Always non-null if
+    // sCallStackDebuggingEnabled is true.  Can be combined with the match call.
+    private static String sCallStackDebuggingMatchName;
+
+    // The surface control transaction method name to log stack traces for.  Always non-null if
+    // sCallStackDebuggingEnabled is true.  Can be combined with the match name.
+    private static String sCallStackDebuggingMatchCall;
+
     // Mapping of the active SurfaceControls to the elapsed time when they were registered
     @GuardedBy("sLock")
     private final WeakHashMap<SurfaceControl, Long> mSurfaceControls;
@@ -160,6 +181,12 @@
         }
     }
 
+    @VisibleForTesting
+    public void setCallStackDebuggingParams(String matchName, String matchCall) {
+        sCallStackDebuggingMatchName = matchName.toLowerCase();
+        sCallStackDebuggingMatchCall = matchCall.toLowerCase();
+    }
+
     /**
      * Creates and initializes the registry for all SurfaceControls in this process. The caller must
      * hold the READ_FRAME_BUFFER permission.
@@ -196,11 +223,9 @@
      * createProcessInstance(Context) was previously called from a valid caller.
      * @hide
      */
-    @Nullable
-    @VisibleForTesting
     public static SurfaceControlRegistry getProcessInstance() {
         synchronized (sLock) {
-            return sProcessRegistry;
+            return sProcessRegistry != null ? sProcessRegistry : NO_OP_REGISTRY;
         }
     }
 
@@ -248,6 +273,91 @@
     }
 
     /**
+     * Initializes global call stack debugging if this is a debug build and a filter is specified.
+     * This is a no-op if
+     *
+     * Usage:
+     *   adb shell setprop persist.wm.debug.sc.tx.log_match_call <call or \"\" to unset>
+     *   adb shell setprop persist.wm.debug.sc.tx.log_match_name <name or \"\" to unset>
+     *   adb reboot
+     */
+    final static void initializeCallStackDebugging() {
+        if (sCallStackDebuggingInitialized || !Build.IS_DEBUGGABLE) {
+            // Return early if already initialized or this is not a debug build
+            return;
+        }
+
+        sCallStackDebuggingInitialized = true;
+        sCallStackDebuggingMatchCall =
+                SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
+                        .toLowerCase();
+        sCallStackDebuggingMatchName =
+                SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
+                        .toLowerCase();
+        // Only enable stack debugging if any of the match filters are set
+        sCallStackDebuggingEnabled = (!sCallStackDebuggingMatchCall.isEmpty()
+                || !sCallStackDebuggingMatchName.isEmpty());
+        if (sCallStackDebuggingEnabled) {
+            Log.d(TAG, "Enabling transaction call stack debugging:"
+                    + " matchCall=" + sCallStackDebuggingMatchCall
+                    + " matchName=" + sCallStackDebuggingMatchName);
+        }
+    }
+
+    /**
+     * Dumps the callstack if it matches the global debug properties. Caller should first verify
+     * {@link #sCallStackDebuggingEnabled} is true.
+     *
+     * @param call the name of the call
+     * @param tx (optional) the transaction associated with this call
+     * @param sc the affected surface
+     * @param details additional details to print with the stack track
+     */
+    final void checkCallStackDebugging(@NonNull String call,
+            @Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc,
+            @Nullable String details) {
+        if (!sCallStackDebuggingEnabled) {
+            return;
+        }
+        if (!matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) {
+            return;
+        }
+        final String txMsg = tx != null ? "tx=" + tx.getId() + " ": "";
+        final String scMsg = sc != null ? " sc=" + sc.getName() + "": "";
+        final String msg = details != null
+                ? call + " (" + txMsg + scMsg + ") " + details
+                : call + " (" + txMsg + scMsg + ")";
+        Log.e(TAG, msg, new Throwable());
+    }
+
+    /**
+     * Tests whether the given surface control name/method call matches the filters set for the
+     * call stack debugging.
+     */
+    @VisibleForTesting
+    public final boolean matchesForCallStackDebugging(@Nullable String name, @NonNull String call) {
+        final boolean matchCall = !sCallStackDebuggingMatchCall.isEmpty();
+        if (matchCall && !call.toLowerCase().contains(sCallStackDebuggingMatchCall)) {
+            // Skip if target call doesn't match requested caller
+            return false;
+        }
+        final boolean matchName = !sCallStackDebuggingMatchName.isEmpty();
+        if (matchName && (name == null
+                || !name.toLowerCase().contains(sCallStackDebuggingMatchName))) {
+            // Skip if target surface doesn't match requested surface
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns whether call stack debugging is enabled for this process.
+     */
+    final static boolean isCallStackDebuggingEnabled() {
+        return sCallStackDebuggingEnabled;
+    }
+
+    /**
      * Forces the gc and finalizers to run, used prior to dumping to ensure we only dump strongly
      * referenced surface controls.
      */
@@ -272,7 +382,27 @@
         synchronized (sLock) {
             if (sProcessRegistry != null) {
                 sDefaultReporter.onMaxLayersExceeded(sProcessRegistry.mSurfaceControls, limit, pw);
+                pw.println("sCallStackDebuggingInitialized=" + sCallStackDebuggingInitialized);
+                pw.println("sCallStackDebuggingEnabled=" + sCallStackDebuggingEnabled);
+                pw.println("sCallStackDebuggingMatchName=" + sCallStackDebuggingMatchName);
+                pw.println("sCallStackDebuggingMatchCall=" + sCallStackDebuggingMatchCall);
             }
         }
     }
+
+    /**
+     * A no-op implementation of the registry.
+     */
+    private static class NoOpRegistry extends SurfaceControlRegistry {
+
+        @Override
+        public void setReportingThresholds(int maxLayersReportingThreshold,
+                int resetReportingThreshold, Reporter reporter) {}
+
+        @Override
+        void add(SurfaceControl sc) {}
+
+        @Override
+        void remove(SurfaceControl sc) {}
+    }
 }
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index e117051..71bdce4 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
@@ -23,6 +23,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.eq;
@@ -148,6 +149,28 @@
         reporter.assertLastReportedSetEquals(sc5, sc6, sc7, sc8);
     }
 
+    @Test
+    public void testCallStackDebugging_matchesFilters() {
+        SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
+
+        // Specific name, any call
+        registry.setCallStackDebuggingParams("com.android.app1", "");
+        assertFalse(registry.matchesForCallStackDebugging("com.android.noMatchApp", "setAlpha"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+
+        // Any name, specific call
+        registry.setCallStackDebuggingParams("", "setAlpha");
+        assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+
+        // Specific name, specific call
+        registry.setCallStackDebuggingParams("com.android.app1", "setAlpha");
+        assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+        assertFalse(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+    }
+
     private SurfaceControl buildTestSurface() {
         return new SurfaceControl.Builder()
                 .setContainerLayer()