android_jni: Add method for getting nth frame

GOOGLE_INTERNAL_CL: 525466766
diff --git a/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java b/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java
index c588403..4ab23c3 100644
--- a/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java
+++ b/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java
@@ -104,6 +104,19 @@
       assertThat(decoder.nextFrame(bitmap)).isTrue();
       assertThat(frameDurations[i]).isWithin(1.0e-2).of(image.frameDuration);
     }
+    // Fetch the first frame again.
+    assertThat(decoder.nthFrame(0, bitmap)).isTrue();
+    // Now nextFrame will return the second frame.
+    assertThat(decoder.nextFrame(bitmap)).isTrue();
+    // Fetch the (frameCount/2)th frame.
+    assertThat(decoder.nthFrame(image.frameCount / 2, bitmap)).isTrue();
+    // Fetch the last frame.
+    assertThat(decoder.nthFrame(image.frameCount - 1, bitmap)).isTrue();
+    // Now nextFrame should return false.
+    assertThat(decoder.nextFrame(bitmap)).isFalse();
+    // Passing out of bound values for n should fail.
+    assertThat(decoder.nthFrame(-1, bitmap)).isFalse();
+    assertThat(decoder.nthFrame(image.frameCount, bitmap)).isFalse();
     decoder.release();
   }
 
diff --git a/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java b/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java
index d76c42a..55633fe 100644
--- a/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java
+++ b/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java
@@ -189,6 +189,22 @@
 
   private native boolean nextFrame(long decoder, Bitmap bitmap);
 
+  /**
+   * Decodes the nth frame of the animated AVIF into the bitmap.
+   *
+   * <p>Note that calling this method will change the behavior of subsequent calls to {@link
+   * nextFrame}. {@link nextFrame} will start outputting the frame after this one.
+   *
+   * @param bitmap The decoded pixels will be copied into the bitmap.
+   * @param n The zero-based index of the frame to be decoded.
+   * @return true on success and false on failure.
+   */
+  public boolean nthFrame(int n, Bitmap bitmap) {
+    return nthFrame(decoder, n, bitmap);
+  }
+
+  private native boolean nthFrame(long decoder, int n, Bitmap bitmap);
+
   private native long createDecoder(ByteBuffer encoded, int length);
 
   private native void destroyDecoder(long decoder);
diff --git a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
index 0090cf1..a3f887e 100644
--- a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
+++ b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
@@ -76,13 +76,8 @@
   return true;
 }
 
-bool DecodeNextImage(JNIEnv* const env, AvifDecoderWrapper* const decoder,
-                     jobject bitmap) {
-  avifResult res = avifDecoderNextImage(decoder->decoder);
-  if (res != AVIF_RESULT_OK) {
-    LOGE("Failed to decode AVIF image. Status: %d", res);
-    return false;
-  }
+bool AvifImageToBitmap(JNIEnv* const env, AvifDecoderWrapper* const decoder,
+                       jobject bitmap) {
   AndroidBitmapInfo bitmap_info;
   if (AndroidBitmap_getInfo(env, bitmap, &bitmap_info) < 0) {
     LOGE("AndroidBitmap_getInfo failed.");
@@ -128,7 +123,7 @@
   // them:
   // https://developer.android.com/reference/android/graphics/Bitmap#setPremultiplied(boolean)
   rgb_image.alphaPremultiplied = AVIF_TRUE;
-  res = avifImageYUVToRGB(decoder->decoder->image, &rgb_image);
+  const avifResult res = avifImageYUVToRGB(decoder->decoder->image, &rgb_image);
   AndroidBitmap_unlockPixels(env, bitmap);
   if (res != AVIF_RESULT_OK) {
     LOGE("Failed to convert YUV Pixels to RGB. Status: %d", res);
@@ -137,6 +132,26 @@
   return true;
 }
 
+bool DecodeNextImage(JNIEnv* const env, AvifDecoderWrapper* const decoder,
+                     jobject bitmap) {
+  avifResult res = avifDecoderNextImage(decoder->decoder);
+  if (res != AVIF_RESULT_OK) {
+    LOGE("Failed to decode AVIF image. Status: %d", res);
+    return false;
+  }
+  return AvifImageToBitmap(env, decoder, bitmap);
+}
+
+bool DecodeNthImage(JNIEnv* const env, AvifDecoderWrapper* const decoder,
+                    uint32_t n, jobject bitmap) {
+  avifResult res = avifDecoderNthImage(decoder->decoder, n);
+  if (res != AVIF_RESULT_OK) {
+    LOGE("Failed to decode AVIF image. Status: %d", res);
+    return false;
+  }
+  return AvifImageToBitmap(env, decoder, bitmap);
+}
+
 }  // namespace
 
 jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
@@ -256,6 +271,12 @@
   return DecodeNextImage(env, decoder, bitmap);
 }
 
+FUNC(jboolean, nthFrame, jlong jdecoder, jint n, jobject bitmap) {
+  AvifDecoderWrapper* const decoder =
+      reinterpret_cast<AvifDecoderWrapper*>(jdecoder);
+  return DecodeNthImage(env, decoder, n, bitmap);
+}
+
 FUNC(void, destroyDecoder, jlong jdecoder) {
   AvifDecoderWrapper* const decoder =
       reinterpret_cast<AvifDecoderWrapper*>(jdecoder);