android_jni: Add instrumented tests

These tests will run on a real android device or an emulator and
will act as a sanity check for the JNI bindings.
diff --git a/android_jni/README.md b/android_jni/README.md
index 92f0071..9b8cfd9 100644
--- a/android_jni/README.md
+++ b/android_jni/README.md
@@ -52,6 +52,25 @@
 
 The instructions on how to add the AAR package as a dependency to your Android project can be found [here](https://developer.android.com/studio/projects/android-library#psd-add-aar-jar-dependency).
 
+## Running Instrumented Tests
+
+Step 1 - Build the library
+
+Make sure to build the library by following the steps under
+[Generate the AAR package](#generate-the-aar-package) section above.
+
+Step 2 - Set up a device/emulator
+
+Make sure that a device or an emulator has been set up and is available via
+`adb`.
+
+Step 3 - Run the tests
+
+```
+$ cd android_jni
+$ ./gradlew connectedAndroidTest
+```
+
 ## Using Android Studio
 
 The entire android_jni directory can be imported as a project into Android Studio.
diff --git a/android_jni/avifandroidjni/build.gradle b/android_jni/avifandroidjni/build.gradle
index 707a32d..956528c 100644
--- a/android_jni/avifandroidjni/build.gradle
+++ b/android_jni/avifandroidjni/build.gradle
@@ -10,6 +10,7 @@
         targetSdk 30
         versionCode 1
         versionName "1.0"
+        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
     }
 
     buildTypes {
@@ -33,4 +34,8 @@
 }
 
 dependencies {
-}
\ No newline at end of file
+    androidTestImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+    androidTestImplementation 'com.google.truth:truth:1.1.3'
+    androidTestImplementation 'androidx.test:runner:1.5.2'
+}
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/README b/android_jni/avifandroidjni/src/androidTest/assets/README
new file mode 100644
index 0000000..2307913
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/README
@@ -0,0 +1,7 @@
+This file describes the sources of files in this directory.
+
+* Sub-Directory: avif
+  Description: Images used for testing still AVIF decoding.
+  Source: https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Link-U
+  Filter: Only a subset of fox* files.
+  Git Hash: 77bd20d
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.10bpc.yuv420.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.10bpc.yuv420.avif
new file mode 100644
index 0000000..025c185
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.10bpc.yuv420.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.10bpc.yuv420.monochrome.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.10bpc.yuv420.monochrome.avif
new file mode 100644
index 0000000..5b8a127
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.10bpc.yuv420.monochrome.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.8bpc.yuv420.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.8bpc.yuv420.avif
new file mode 100644
index 0000000..cf23e6e
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.8bpc.yuv420.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.8bpc.yuv420.monochrome.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.8bpc.yuv420.monochrome.avif
new file mode 100644
index 0000000..57ca3f1
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile0.8bpc.yuv420.monochrome.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile1.10bpc.yuv444.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile1.10bpc.yuv444.avif
new file mode 100644
index 0000000..132695e
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile1.10bpc.yuv444.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile1.8bpc.yuv444.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile1.8bpc.yuv444.avif
new file mode 100644
index 0000000..ba1c5ff
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile1.8bpc.yuv444.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.10bpc.yuv422.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.10bpc.yuv422.avif
new file mode 100644
index 0000000..6143168
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.10bpc.yuv422.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv420.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv420.avif
new file mode 100644
index 0000000..4299cf7
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv420.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv420.monochrome.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv420.monochrome.avif
new file mode 100644
index 0000000..34293c2
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv420.monochrome.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv422.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv422.avif
new file mode 100644
index 0000000..2e69ec6
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv422.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv444.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv444.avif
new file mode 100644
index 0000000..03c3599
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.12bpc.yuv444.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.8bpc.yuv422.avif b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.8bpc.yuv422.avif
new file mode 100644
index 0000000..7f3a788
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/assets/avif/fox.profile2.8bpc.yuv422.avif
Binary files differ
diff --git a/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/StillImageTest.java b/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/StillImageTest.java
new file mode 100644
index 0000000..6d92493
--- /dev/null
+++ b/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/StillImageTest.java
@@ -0,0 +1,84 @@
+package org.aomedia.avif.android;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import androidx.test.platform.app.InstrumentationRegistry;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.aomedia.avif.android.AvifDecoder.Info;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Instrumentation tests for the libavif JNI API, which will execute on an Android device. */
+@RunWith(Parameterized.class)
+public class StillImageTest {
+
+  private static final String ASSET_DIRECTORY = "avif";
+
+  @Parameters
+  public static List<Object[]> data() throws IOException {
+    ArrayList<Object[]> list = new ArrayList<>();
+    for (String asset : getAssetFiles(ASSET_DIRECTORY)) {
+      String assetPath = Paths.get(ASSET_DIRECTORY, asset).toString();
+      // Test ARGB_8888 for all files.
+      list.add(new Object[] {Config.ARGB_8888, assetPath});
+      // For 8bpc files, test only RGB_565. For other files, test only RGBA_F16.
+      Config testConfig = assetPath.contains("8bpc") ? Config.RGB_565 : Config.RGBA_F16;
+      list.add(new Object[] {testConfig, assetPath});
+    }
+    return list;
+  }
+
+  @Parameter(0)
+  public Bitmap.Config config;
+
+  @Parameter(1)
+  public String assetPath;
+
+  @Test
+  public void testIsAvifImageReturnsTrue() throws IOException {
+    ByteBuffer buffer = getBuffer();
+    assertThat(buffer).isNotNull();
+    assertThat(AvifDecoder.isAvifImage(buffer)).isTrue();
+  }
+
+  @Test
+  public void testAvifDecode() throws IOException {
+    ByteBuffer buffer = getBuffer();
+    assertThat(buffer).isNotNull();
+    Info info = new Info();
+    assertThat(AvifDecoder.getInfo(buffer, buffer.remaining(), info)).isTrue();
+    assertThat(info.width).isGreaterThan(0);
+    assertThat(info.height).isGreaterThan(0);
+    assertThat(info.depth).isAnyOf(8, 10, 12);
+    Bitmap bitmap = Bitmap.createBitmap(info.width, info.height, config);
+    assertThat(bitmap).isNotNull();
+    assertThat(AvifDecoder.decode(buffer, buffer.remaining(), bitmap)).isTrue();
+  }
+
+  private ByteBuffer getBuffer() throws IOException {
+    Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    InputStream is = context.getAssets().open(assetPath);
+    ByteBuffer buffer = ByteBuffer.allocateDirect(is.available());
+    Channels.newChannel(is).read(buffer);
+    buffer.rewind();
+    return buffer;
+  }
+
+  private static List<String> getAssetFiles(String directoryName) throws IOException {
+    Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    return Arrays.asList(context.getAssets().list(directoryName));
+  }
+}