Expose peer connection's `getStats` by RtpSender/Receiver in Android SDK

Currently if you want to obtain the stats for a specific sender/receiver
in Android, you need to call peerConnection.getStats() and filter
manually the result by sender.

pc.getStats(receiver/sender) exists in c++ and ios but was not exposed
in Android

Bug: webrtc:14547
Change-Id: I9954434880f0f93821fcd2e2de24a875e8d136ae
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/275880
Reviewed-by: Xavier Lepaul‎ <xalep@webrtc.org>
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38428}
diff --git a/AUTHORS b/AUTHORS
index 2fdd9fd..a0632d2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -141,6 +141,7 @@
 Facebook Inc. <*@fb.com>
 Google Inc. <*@google.com>
 Highfive, Inc. <*@highfive.com>
+Hopin Ltd. <*@hopin.to>
 HyperConnect Inc. <*@hpcnt.com>
 Intel Corporation <*@intel.com>
 LG Electronics, Inc. <*@lge.com>
diff --git a/sdk/android/api/org/webrtc/PeerConnection.java b/sdk/android/api/org/webrtc/PeerConnection.java
index 724f704..2d592a5 100644
--- a/sdk/android/api/org/webrtc/PeerConnection.java
+++ b/sdk/android/api/org/webrtc/PeerConnection.java
@@ -1177,6 +1177,22 @@
   }
 
   /**
+   * Gets stats using the new stats collection API, see webrtc/api/stats/. These
+   * will replace old stats collection API when the new API has matured enough.
+   */
+  public void getStats(RtpSender sender, RTCStatsCollectorCallback callback) {
+    nativeNewGetStatsSender(sender.getNativeRtpSender(), callback);
+  }
+
+  /**
+   * Gets stats using the new stats collection API, see webrtc/api/stats/. These
+   * will replace old stats collection API when the new API has matured enough.
+   */
+  public void getStats(RtpReceiver receiver, RTCStatsCollectorCallback callback) {
+    nativeNewGetStatsReceiver(receiver.getNativeRtpReceiver(), callback);
+  }
+
+  /**
    * Limits the bandwidth allocated for all RTP streams sent by this
    * PeerConnection. Pass null to leave a value unchanged.
    */
@@ -1310,6 +1326,8 @@
   private native void nativeRemoveLocalStream(long stream);
   private native boolean nativeOldGetStats(StatsObserver observer, long nativeTrack);
   private native void nativeNewGetStats(RTCStatsCollectorCallback callback);
+  private native void nativeNewGetStatsSender(long sender, RTCStatsCollectorCallback callback);
+  private native void nativeNewGetStatsReceiver(long receiver, RTCStatsCollectorCallback callback);
   private native RtpSender nativeCreateSender(String kind, String stream_id);
   private native List<RtpSender> nativeGetSenders();
   private native List<RtpReceiver> nativeGetReceivers();
diff --git a/sdk/android/api/org/webrtc/RtpReceiver.java b/sdk/android/api/org/webrtc/RtpReceiver.java
index a5710f9..c3cff3d 100644
--- a/sdk/android/api/org/webrtc/RtpReceiver.java
+++ b/sdk/android/api/org/webrtc/RtpReceiver.java
@@ -49,6 +49,12 @@
     return nativeGetId(nativeRtpReceiver);
   }
 
+  /** Returns a pointer to webrtc::RtpReceiverInterface. */
+  long getNativeRtpReceiver() {
+    checkRtpReceiverExists();
+    return nativeRtpReceiver;
+  }
+
   @CalledByNative
   public void dispose() {
     checkRtpReceiverExists();
diff --git a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionEndToEndTest.java b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionEndToEndTest.java
index f71bd36..d763ff2 100644
--- a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionEndToEndTest.java
+++ b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionEndToEndTest.java
@@ -931,6 +931,23 @@
     assertTrue(offeringPC.setBitrate(100000, 5000000, 500000000));
     assertFalse(offeringPC.setBitrate(3, 2, 1));
 
+    // Test getStats by Sender interface
+    offeringExpectations.expectNewStatsCallback();
+    offeringPC.getStats(videoSender, offeringExpectations);
+    assertTrue(offeringExpectations.waitForAllExpectationsToBeSatisfied(DEFAULT_TIMEOUT_SECONDS));
+
+    // Test getStats by Receiver interface
+    RtpReceiver videoReceiver = null;
+    for (RtpReceiver receiver : answeringPC.getReceivers()) {
+      if (receiver.track().kind().equals("video")) {
+        videoReceiver = receiver;
+      }
+    }
+    assertNotNull(videoReceiver);
+    answeringExpectations.expectNewStatsCallback();
+    answeringPC.getStats(videoReceiver, answeringExpectations);
+    assertTrue(answeringExpectations.waitForAllExpectationsToBeSatisfied(DEFAULT_TIMEOUT_SECONDS));
+
     // Free the Java-land objects and collect them.
     shutdownPC(offeringPC, offeringExpectations);
     offeringPC = null;
diff --git a/sdk/android/src/jni/pc/peer_connection.cc b/sdk/android/src/jni/pc/peer_connection.cc
index 03321a5..8c76b42 100644
--- a/sdk/android/src/jni/pc/peer_connection.cc
+++ b/sdk/android/src/jni/pc/peer_connection.cc
@@ -842,6 +842,32 @@
   ExtractNativePC(jni, j_pc)->GetStats(callback.get());
 }
 
+static void JNI_PeerConnection_NewGetStatsSender(
+    JNIEnv* jni,
+    const JavaParamRef<jobject>& j_pc,
+    jlong native_sender,
+    const JavaParamRef<jobject>& j_callback) {
+  auto callback =
+      rtc::make_ref_counted<RTCStatsCollectorCallbackWrapper>(jni, j_callback);
+  ExtractNativePC(jni, j_pc)->GetStats(
+      rtc::scoped_refptr<RtpSenderInterface>(
+          reinterpret_cast<RtpSenderInterface*>(native_sender)),
+      rtc::scoped_refptr<RTCStatsCollectorCallbackWrapper>(callback.get()));
+}
+
+static void JNI_PeerConnection_NewGetStatsReceiver(
+    JNIEnv* jni,
+    const JavaParamRef<jobject>& j_pc,
+    jlong native_receiver,
+    const JavaParamRef<jobject>& j_callback) {
+  auto callback =
+      rtc::make_ref_counted<RTCStatsCollectorCallbackWrapper>(jni, j_callback);
+  ExtractNativePC(jni, j_pc)->GetStats(
+      rtc::scoped_refptr<RtpReceiverInterface>(
+          reinterpret_cast<RtpReceiverInterface*>(native_receiver)),
+      rtc::scoped_refptr<RTCStatsCollectorCallbackWrapper>(callback.get()));
+}
+
 static jboolean JNI_PeerConnection_SetBitrate(
     JNIEnv* jni,
     const JavaParamRef<jobject>& j_pc,