PatchPanel: Add latency information for software patches

Test: audioflinger dumpsys when patches present
Bug: 80546849
Change-Id: Ib5b26e065ab20b5f8d530db57398e7b4a59ed1f1
diff --git a/services/audioflinger/PatchPanel.cpp b/services/audioflinger/PatchPanel.cpp
index 3d688fb..c2927ba 100644
--- a/services/audioflinger/PatchPanel.cpp
+++ b/services/audioflinger/PatchPanel.cpp
@@ -475,11 +475,48 @@
     mPlayback.closeConnections(panel);
 }
 
+status_t AudioFlinger::PatchPanel::Patch::getLatencyMs(double *latencyMs) const
+{
+    if (!isSoftware()) return INVALID_OPERATION;
+
+    auto recordTrack = mRecord.const_track();
+    if (recordTrack.get() == nullptr) return INVALID_OPERATION;
+
+    auto playbackTrack = mPlayback.const_track();
+    if (playbackTrack.get() == nullptr) return INVALID_OPERATION;
+
+    // Latency information for tracks may be called without obtaining
+    // the underlying thread lock.
+    //
+    // We use record server latency + playback track latency (generally smaller than the
+    // reverse due to internal biases).
+    //
+    // TODO: is this stable enough? Consider a PatchTrack synchronized version of this.
+    double recordServerLatencyMs;
+    if (recordTrack->getServerLatencyMs(&recordServerLatencyMs) != OK) return INVALID_OPERATION;
+
+    double playbackTrackLatencyMs;
+    if (playbackTrack->getTrackLatencyMs(&playbackTrackLatencyMs) != OK) return INVALID_OPERATION;
+
+    *latencyMs = recordServerLatencyMs + playbackTrackLatencyMs;
+    return OK;
+}
+
 String8 AudioFlinger::PatchPanel::Patch::dump(audio_patch_handle_t myHandle)
 {
     String8 result;
-    result.appendFormat("Patch %d: thread %p => thread %p\n",
+
+    // TODO: Consider table dump form for patches, just like tracks.
+    result.appendFormat("Patch %d: thread %p => thread %p",
             myHandle, mRecord.thread().get(), mPlayback.thread().get());
+
+    // add latency if it exists
+    double latencyMs;
+    if (getLatencyMs(&latencyMs) == OK) {
+        result.appendFormat("  latency: %.2lf", latencyMs);
+    }
+
+    result.append("\n");
     return result;
 }
 
diff --git a/services/audioflinger/PatchPanel.h b/services/audioflinger/PatchPanel.h
index dff8ad2..5d6bf00 100644
--- a/services/audioflinger/PatchPanel.h
+++ b/services/audioflinger/PatchPanel.h
@@ -65,6 +65,7 @@
         audio_patch_handle_t handle() const { return mHandle; }
         sp<ThreadType> thread() { return mThread; }
         sp<TrackType> track() { return mTrack; }
+        sp<const TrackType> const_track() const { return mTrack; }
 
         void closeConnections(PatchPanel *panel) {
             if (mHandle != AUDIO_PATCH_HANDLE_NONE) {
@@ -118,6 +119,9 @@
             return mRecord.handle() != AUDIO_PATCH_HANDLE_NONE ||
                     mPlayback.handle() != AUDIO_PATCH_HANDLE_NONE; }
 
+        // returns the latency of the patch (from record to playback).
+        status_t getLatencyMs(double *latencyMs) const;
+
         String8 dump(audio_patch_handle_t myHandle);
 
         // Note that audio_patch::id is only unique within a HAL module