Add stalled transaction message to input ANRs

Bug: 287577707
Test: presubmit
Change-Id: I569465d22174090ce524bad9ef950075d868fac8
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c11f497..e673676 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -47,6 +47,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.gui.DropInputMode;
+import android.gui.StalledTransactionInfo;
 import android.hardware.DataSpace;
 import android.hardware.HardwareBuffer;
 import android.hardware.OverlayProperties;
@@ -292,6 +293,7 @@
             long nativeObject, long nativeTpc, TrustedPresentationThresholds thresholds);
     private static native void nativeClearTrustedPresentationCallback(long transactionObj,
             long nativeObject);
+    private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -4363,4 +4365,11 @@
         callback.accept(fence);
     }
 
+    /**
+     * @hide
+     */
+    public static StalledTransactionInfo getStalledTransactionInfo(int pid) {
+        return nativeGetStalledTransactionInfo(pid);
+    }
+
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4249253..dbe0338 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -257,6 +257,14 @@
     jmethodID onTrustedPresentationChanged;
 } gTrustedPresentationCallbackClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+    jfieldID layerName;
+    jfieldID bufferId;
+    jfieldID frameNumber;
+} gStalledTransactionInfoClassInfo;
+
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
         case ui::ColorMode::DISPLAY_P3:
@@ -2032,6 +2040,29 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeTpc));
 }
 
+static jobject nativeGetStalledTransactionInfo(JNIEnv* env, jclass clazz, jint pid) {
+    std::optional<gui::StalledTransactionInfo> stalledTransactionInfo =
+            SurfaceComposerClient::getStalledTransactionInfo(pid);
+    if (!stalledTransactionInfo) {
+        return nullptr;
+    }
+
+    jobject jStalledTransactionInfo = env->NewObject(gStalledTransactionInfoClassInfo.clazz,
+                                                     gStalledTransactionInfoClassInfo.ctor);
+    if (!jStalledTransactionInfo) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+        return nullptr;
+    }
+
+    env->SetObjectField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.layerName,
+                        env->NewStringUTF(String8{stalledTransactionInfo->layerName}));
+    env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.bufferId,
+                      static_cast<jlong>(stalledTransactionInfo->bufferId));
+    env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.frameNumber,
+                      static_cast<jlong>(stalledTransactionInfo->frameNumber));
+    return jStalledTransactionInfo;
+}
+
 // ----------------------------------------------------------------------------
 
 SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
@@ -2281,6 +2312,8 @@
     {"nativeCreateTpc", "(Landroid/view/SurfaceControl$TrustedPresentationCallback;)J",
             (void*)nativeCreateTpc},
     {"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
+    {"nativeGetStalledTransactionInfo", "(I)Landroid/gui/StalledTransactionInfo;",
+            (void*) nativeGetStalledTransactionInfo },
         // clang-format on
 };
 
@@ -2524,6 +2557,18 @@
     gTrustedPresentationCallbackClassInfo.onTrustedPresentationChanged =
             GetMethodIDOrDie(env, trustedPresentationCallbackClazz, "onTrustedPresentationChanged",
                              "(Z)V");
+
+    jclass stalledTransactionInfoClazz = FindClassOrDie(env, "android/gui/StalledTransactionInfo");
+    gStalledTransactionInfoClassInfo.clazz = MakeGlobalRefOrDie(env, stalledTransactionInfoClazz);
+    gStalledTransactionInfoClassInfo.ctor =
+            GetMethodIDOrDie(env, stalledTransactionInfoClazz, "<init>", "()V");
+    gStalledTransactionInfoClassInfo.layerName =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "layerName", "Ljava/lang/String;");
+    gStalledTransactionInfoClassInfo.bufferId =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "bufferId", "J");
+    gStalledTransactionInfoClassInfo.frameNumber =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "frameNumber", "J");
+
     return err;
 }
 
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 20595ea..73fdfe0 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -25,6 +25,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.gui.StalledTransactionInfo;
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
@@ -96,7 +97,7 @@
     @Override
     public void notifyNoFocusedWindowAnr(@NonNull InputApplicationHandle applicationHandle) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(
-                timeoutMessage("Application does not have a focused window"));
+                timeoutMessage(OptionalInt.empty(), "Application does not have a focused window"));
         mService.mAnrController.notifyAppUnresponsive(applicationHandle, timeoutRecord);
     }
 
@@ -104,7 +105,7 @@
     public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
             String reason) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
-                timeoutMessage(reason));
+                timeoutMessage(pid, reason));
         mService.mAnrController.notifyWindowUnresponsive(token, pid, timeoutRecord);
     }
 
@@ -354,11 +355,21 @@
         mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
     }
 
-    private String timeoutMessage(String reason) {
-        if (reason == null) {
-            return "Input dispatching timed out";
+    private String timeoutMessage(OptionalInt pid, String reason) {
+        String message = (reason == null) ? "Input dispatching timed out."
+                : String.format("Input dispatching timed out (%s).", reason);
+        if (pid.isEmpty()) {
+            return message;
         }
-        return "Input dispatching timed out (" + reason + ")";
+        StalledTransactionInfo stalledTransactionInfo =
+                SurfaceControl.getStalledTransactionInfo(pid.getAsInt());
+        if (stalledTransactionInfo == null) {
+            return message;
+        }
+        return String.format("%s Buffer processing for the associated surface is stuck due to an "
+                + "unsignaled fence (window=%s, bufferId=0x%016X, frameNumber=%s). This "
+                + "potentially indicates a GPU hang.", message, stalledTransactionInfo.layerName,
+                stalledTransactionInfo.bufferId, stalledTransactionInfo.frameNumber);
     }
 
     void dump(PrintWriter pw, String prefix) {