chromium/base/android/jni_array_unittest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/android/jni_array.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <array>
#include <limits>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/containers/span.h"
#include "base/containers/to_vector.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base::android {

TEST(JniArray, GetLength) {
  const auto bytes = std::to_array<uint8_t>({0, 1, 2, 3});
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jbyteArray> j_bytes = ToJavaByteArray(env, bytes);
  ASSERT_TRUE(j_bytes);
  ASSERT_EQ(4U, SafeGetArrayLength(env, j_bytes));

  ScopedJavaLocalRef<jbyteArray> j_empty_bytes =
      ToJavaByteArray(env, base::span<uint8_t>());
  ASSERT_TRUE(j_empty_bytes);
  ASSERT_EQ(0U, SafeGetArrayLength(env, j_empty_bytes));
}

TEST(JniArray, BasicConversions) {
  const auto bytes = std::to_array<uint8_t>({0, 1, 2, 3});
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jbyteArray> bytes_from_span = ToJavaByteArray(env, bytes);
  ASSERT_TRUE(bytes_from_span);
  ASSERT_EQ(4U, SafeGetArrayLength(env, bytes_from_span));

  auto input_string = std::string(base::as_string_view(bytes));
  ScopedJavaLocalRef<jbyteArray> bytes_from_string =
      ToJavaByteArray(env, input_string);
  ASSERT_TRUE(bytes_from_string);
  ASSERT_EQ(4U, SafeGetArrayLength(env, bytes_from_string));

  ScopedJavaLocalRef<jbyteArray> bytes_from_ptr =
      UNSAFE_BUFFERS(ToJavaByteArray(env, bytes.data(), bytes.size()));
  ASSERT_TRUE(bytes_from_ptr);
  ASSERT_EQ(4U, SafeGetArrayLength(env, bytes_from_ptr));

  std::vector<uint8_t> vector_from_span(5);
  std::vector<uint8_t> vector_from_string(5);
  std::vector<uint8_t> vector_from_ptr(5);
  JavaByteArrayToByteVector(env, bytes_from_span, &vector_from_span);
  JavaByteArrayToByteVector(env, bytes_from_string, &vector_from_string);
  JavaByteArrayToByteVector(env, bytes_from_ptr, &vector_from_ptr);
  EXPECT_EQ(4U, vector_from_span.size());
  EXPECT_EQ(4U, vector_from_string.size());
  EXPECT_EQ(4U, vector_from_ptr.size());
  EXPECT_EQ(bytes, span(vector_from_span));
  EXPECT_EQ(bytes, span(vector_from_string));
  EXPECT_EQ(bytes, span(vector_from_ptr));
}

TEST(JniArray, ByteArrayStringConversions) {
  JNIEnv* env = AttachCurrentThread();
  std::string input_string("hello\0world", 11u);
  ScopedJavaLocalRef<jbyteArray> bytes_from_string =
      ToJavaByteArray(env, input_string);
  ASSERT_TRUE(bytes_from_string);

  std::string string_from_string;
  JavaByteArrayToString(env, bytes_from_string, &string_from_string);
  EXPECT_EQ(input_string, string_from_string);
}

void CheckBoolConversion(JNIEnv* env,
                         span<const bool> bool_array,
                         const ScopedJavaLocalRef<jbooleanArray>& booleans) {
  ASSERT_TRUE(booleans);

  jsize java_array_len = env->GetArrayLength(booleans.obj());
  ASSERT_EQ(checked_cast<jsize>(bool_array.size()), java_array_len);

  jboolean value;
  for (size_t i = 0; i < bool_array.size(); ++i) {
    env->GetBooleanArrayRegion(booleans.obj(), checked_cast<jsize>(i), jsize{1},
                               &value);
    ASSERT_EQ(bool_array[i], value);
  }
}

TEST(JniArray, BoolConversions) {
  const bool kBools[] = {false, true, false};

  JNIEnv* env = AttachCurrentThread();
  CheckBoolConversion(env, kBools, ToJavaBooleanArray(env, kBools));
}

TEST(JniArray, BoolVectorConversions) {
  const auto kBools = std::to_array<bool>({false, true, false});
  const auto kBoolVector = std::vector<bool>({false, true, false});

  JNIEnv* env = AttachCurrentThread();
  CheckBoolConversion(env, kBools, ToJavaBooleanArray(env, kBoolVector));
}

void CheckIntConversion(JNIEnv* env,
                        span<const int> in,
                        const ScopedJavaLocalRef<jintArray>& ints) {
  ASSERT_TRUE(ints);

  jsize java_array_len = env->GetArrayLength(ints.obj());
  ASSERT_EQ(checked_cast<jsize>(in.size()), java_array_len);

  jint value;
  for (size_t i = 0; i < in.size(); ++i) {
    env->GetIntArrayRegion(ints.obj(), i, 1, &value);
    ASSERT_EQ(in[i], value);
  }
}

TEST(JniArray, IntConversions) {
  const int kInts[] = {0, 1, -1, std::numeric_limits<int32_t>::min(),
                       std::numeric_limits<int32_t>::max()};

  JNIEnv* env = AttachCurrentThread();
  CheckIntConversion(env, kInts, ToJavaIntArray(env, kInts));
}

void CheckLongConversion(JNIEnv* env,
                         span<const int64_t> in,
                         const ScopedJavaLocalRef<jlongArray>& longs) {
  ASSERT_TRUE(longs);

  jsize java_array_len = env->GetArrayLength(longs.obj());
  ASSERT_EQ(checked_cast<jsize>(in.size()), java_array_len);

  jlong value;
  for (size_t i = 0; i < in.size(); ++i) {
    env->GetLongArrayRegion(longs.obj(), i, 1, &value);
    ASSERT_EQ(in[i], value);
  }
}

TEST(JniArray, LongConversions) {
  const int64_t kLongs[] = {0, 1, -1, std::numeric_limits<int64_t>::min(),
                            std::numeric_limits<int64_t>::max()};

  JNIEnv* env = AttachCurrentThread();
  CheckLongConversion(env, kLongs, ToJavaLongArray(env, kLongs));
}

void CheckFloatConversion(JNIEnv* env,
                          span<const float> in,
                          const ScopedJavaLocalRef<jfloatArray>& floats) {
  ASSERT_TRUE(floats);

  jsize java_array_len = env->GetArrayLength(floats.obj());
  ASSERT_EQ(checked_cast<jsize>(in.size()), java_array_len);

  jfloat value;
  for (size_t i = 0; i < in.size(); ++i) {
    env->GetFloatArrayRegion(floats.obj(), i, 1, &value);
    ASSERT_EQ(in[i], value);
  }
}

TEST(JniArray, FloatConversions) {
  const float kFloats[] = {0.0f, 1.0f, -10.0f};

  JNIEnv* env = AttachCurrentThread();
  CheckFloatConversion(env, kFloats, ToJavaFloatArray(env, kFloats));
}

void CheckDoubleConversion(JNIEnv* env,
                           span<const double> in,
                           const ScopedJavaLocalRef<jdoubleArray>& doubles) {
  ASSERT_TRUE(doubles);

  jsize java_array_len = env->GetArrayLength(doubles.obj());
  ASSERT_EQ(checked_cast<jsize>(in.size()), java_array_len);

  jdouble value;
  for (size_t i = 0; i < in.size(); ++i) {
    env->GetDoubleArrayRegion(doubles.obj(), i, 1, &value);
    ASSERT_EQ(in[i], value);
  }
}

TEST(JniArray, DoubleConversions) {
  const double kDoubles[] = {0.0, 1.0, -10.0};

  JNIEnv* env = AttachCurrentThread();
  CheckDoubleConversion(env, kDoubles, ToJavaDoubleArray(env, kDoubles));
}

TEST(JniArray, ArrayOfStringArrayConversionUTF8) {
  std::vector<std::vector<std::string>> kArrays = {
      {"a", "f"}, {"a", ""}, {}, {""}, {"今日は"}};

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobjectArray> joa = ToJavaArrayOfStringArray(env, kArrays);

  std::vector<std::vector<std::string>> out;
  Java2dStringArrayTo2dStringVector(env, joa, &out);
  ASSERT_TRUE(kArrays == out);
}

TEST(JniArray, ArrayOfStringArrayConversionUTF16) {
  std::vector<std::vector<std::u16string>> kArrays = {
      {u"a", u"f"}, {u"a", u""}, {}, {u""}};

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobjectArray> joa = ToJavaArrayOfStringArray(env, kArrays);

  std::vector<std::vector<std::u16string>> out;
  Java2dStringArrayTo2dStringVector(env, joa, &out);
  ASSERT_TRUE(kArrays == out);
}

void CheckBoolArrayConversion(JNIEnv* env,
                              ScopedJavaLocalRef<jbooleanArray> jbooleans,
                              std::vector<bool> bool_vector) {
  jboolean value;
  for (size_t i = 0; i < bool_vector.size(); ++i) {
    env->GetBooleanArrayRegion(jbooleans.obj(), i, 1, &value);
    ASSERT_EQ(bool_vector[i], value);
  }
}

TEST(JniArray, JavaBooleanArrayToBoolVector) {
  const auto kBools = std::to_array<bool>({false, true, false});

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jbooleanArray> jbooleans(
      env, env->NewBooleanArray(kBools.size()));
  ASSERT_TRUE(jbooleans);

  for (size_t i = 0; i < kBools.size(); ++i) {
    jboolean j = static_cast<jboolean>(kBools[i]);
    env->SetBooleanArrayRegion(jbooleans.obj(), i, 1, &j);
    ASSERT_FALSE(HasException(env));
  }

  std::vector<bool> bools;
  JavaBooleanArrayToBoolVector(env, jbooleans, &bools);

  ASSERT_EQ(checked_cast<jsize>(bools.size()),
            env->GetArrayLength(jbooleans.obj()));
  ASSERT_EQ(bools.size(), kBools.size());

  CheckBoolArrayConversion(env, jbooleans, bools);
}

void CheckIntArrayConversion(JNIEnv* env,
                             ScopedJavaLocalRef<jintArray> jints,
                             std::vector<int> int_vector) {
  jint value;
  for (size_t i = 0; i < int_vector.size(); ++i) {
    env->GetIntArrayRegion(jints.obj(), i, 1, &value);
    ASSERT_EQ(int_vector[i], value);
  }
}

TEST(JniArray, JavaIntArrayToIntVector) {
  const auto kInts = std::to_array<int>({0, 1, -1});

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jintArray> jints(env, env->NewIntArray(kInts.size()));
  ASSERT_TRUE(jints);

  for (size_t i = 0; i < kInts.size(); ++i) {
    jint j = static_cast<jint>(kInts[i]);
    env->SetIntArrayRegion(jints.obj(), i, 1, &j);
    ASSERT_FALSE(HasException(env));
  }

  std::vector<int> ints;
  JavaIntArrayToIntVector(env, jints, &ints);

  ASSERT_EQ(checked_cast<jsize>(ints.size()), env->GetArrayLength(jints.obj()));
  ASSERT_EQ(ints.size(), kInts.size());

  CheckIntArrayConversion(env, jints, ints);
}

TEST(JniArray, JavaLongArrayToInt64Vector) {
  const auto kInt64s = std::to_array<int64_t>({0LL, 1LL, -1LL});

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jlongArray> jlongs(env, env->NewLongArray(kInt64s.size()));
  ASSERT_TRUE(jlongs);

  for (size_t i = 0; i < kInt64s.size(); ++i) {
    jlong j = static_cast<jlong>(kInt64s[i]);
    env->SetLongArrayRegion(jlongs.obj(), i, 1, &j);
    ASSERT_FALSE(HasException(env));
  }

  std::vector<int64_t> int64s;
  JavaLongArrayToInt64Vector(env, jlongs, &int64s);

  ASSERT_EQ(checked_cast<jsize>(int64s.size()),
            env->GetArrayLength(jlongs.obj()));
  ASSERT_EQ(int64s.size(), kInt64s.size());

  jlong value;
  for (size_t i = 0; i < kInt64s.size(); ++i) {
    env->GetLongArrayRegion(jlongs.obj(), i, 1, &value);
    ASSERT_EQ(int64s[i], value);
    ASSERT_EQ(kInt64s[i], int64s[i]);
  }
}

TEST(JniArray, JavaLongArrayToLongVector) {
  const auto kInt64s = std::to_array<int64_t>({0LL, 1LL, -1LL});

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jlongArray> jlongs(env, env->NewLongArray(kInt64s.size()));
  ASSERT_TRUE(jlongs);

  for (size_t i = 0; i < kInt64s.size(); ++i) {
    jlong j = static_cast<jlong>(kInt64s[i]);
    env->SetLongArrayRegion(jlongs.obj(), i, 1, &j);
    ASSERT_FALSE(HasException(env));
  }

  std::vector<jlong> jlongs_vector;
  JavaLongArrayToLongVector(env, jlongs, &jlongs_vector);

  ASSERT_EQ(checked_cast<jsize>(jlongs_vector.size()),
            env->GetArrayLength(jlongs.obj()));
  ASSERT_EQ(jlongs_vector.size(), kInt64s.size());

  jlong value;
  for (size_t i = 0; i < kInt64s.size(); ++i) {
    env->GetLongArrayRegion(jlongs.obj(), i, 1, &value);
    ASSERT_EQ(jlongs_vector[i], value);
  }
}

TEST(JniArray, JavaFloatArrayToFloatVector) {
  const auto kFloats = std::to_array<float>({0.0, 0.5, -0.5});

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jfloatArray> jfloats(env,
                                          env->NewFloatArray(kFloats.size()));
  ASSERT_TRUE(jfloats);

  for (size_t i = 0; i < kFloats.size(); ++i) {
    jfloat j = static_cast<jfloat>(kFloats[i]);
    env->SetFloatArrayRegion(jfloats.obj(), i, 1, &j);
    ASSERT_FALSE(HasException(env));
  }

  std::vector<float> floats;
  JavaFloatArrayToFloatVector(env, jfloats, &floats);

  ASSERT_EQ(checked_cast<jsize>(floats.size()),
            env->GetArrayLength(jfloats.obj()));
  ASSERT_EQ(floats.size(), kFloats.size());

  jfloat value;
  for (size_t i = 0; i < kFloats.size(); ++i) {
    env->GetFloatArrayRegion(jfloats.obj(), i, 1, &value);
    ASSERT_EQ(floats[i], value);
  }
}

TEST(JniArray, JavaDoubleArrayToDoubleVector) {
  const auto kDoubles = std::to_array<double>(
      {0.0, 0.5, -0.5, std::numeric_limits<double>::min()});
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jdoubleArray> jdoubles(
      env, env->NewDoubleArray(kDoubles.size()));
  ASSERT_TRUE(jdoubles);

  env->SetDoubleArrayRegion(jdoubles.obj(), 0, kDoubles.size(),
                            reinterpret_cast<const jdouble*>(kDoubles.data()));
  ASSERT_FALSE(HasException(env));

  std::vector<double> doubles;
  JavaDoubleArrayToDoubleVector(env, jdoubles, &doubles);
  ASSERT_EQ(kDoubles, base::span(doubles));
}

TEST(JniArray, JavaArrayOfByteArrayToStringVector) {
  const int kMaxItems = 50;
  JNIEnv* env = AttachCurrentThread();

  // Create a byte[][] object.
  ScopedJavaLocalRef<jclass> byte_array_clazz(env, env->FindClass("[B"));
  ASSERT_TRUE(byte_array_clazz);

  ScopedJavaLocalRef<jobjectArray> array(
      env, env->NewObjectArray(kMaxItems, byte_array_clazz.obj(), NULL));
  ASSERT_TRUE(array);

  // Create kMaxItems byte buffers.
  char text[16];
  for (int i = 0; i < kMaxItems; ++i) {
    snprintf(text, std::ranges::size(text), "%d", i);
    ScopedJavaLocalRef<jbyteArray> byte_array =
        ToJavaByteArray(env, base::as_byte_span(text));
    ASSERT_TRUE(byte_array);

    env->SetObjectArrayElement(array.obj(), i, byte_array.obj());
    ASSERT_FALSE(HasException(env));
  }

  // Convert to std::vector<std::string>, check the content.
  std::vector<std::string> vec;
  JavaArrayOfByteArrayToStringVector(env, array, &vec);

  EXPECT_EQ(static_cast<size_t>(kMaxItems), vec.size());
  for (int i = 0; i < kMaxItems; ++i) {
    snprintf(text, sizeof text, "%d", i);
    EXPECT_STREQ(text, vec[i].c_str());
  }
}

TEST(JniArray, JavaArrayOfByteArrayToBytesVector) {
  const size_t kMaxItems = 50;
  const uint8_t kStep = 37;
  JNIEnv* env = AttachCurrentThread();

  // Create a byte[][] object.
  ScopedJavaLocalRef<jclass> byte_array_clazz(env, env->FindClass("[B"));
  ASSERT_TRUE(byte_array_clazz);

  ScopedJavaLocalRef<jobjectArray> array(
      env, env->NewObjectArray(kMaxItems, byte_array_clazz.obj(), nullptr));
  ASSERT_TRUE(array);

  // Create kMaxItems byte buffers with size |i|+1 on each step;
  std::vector<std::vector<uint8_t>> input_bytes;
  input_bytes.reserve(kMaxItems);
  for (size_t i = 0; i < kMaxItems; ++i) {
    std::vector<uint8_t> cur_bytes(i + 1);
    for (size_t j = 0; j < cur_bytes.size(); ++j)
      cur_bytes[j] = static_cast<uint8_t>(i + j * kStep);
    ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray(env, cur_bytes);
    ASSERT_TRUE(byte_array);

    env->SetObjectArrayElement(array.obj(), i, byte_array.obj());
    ASSERT_FALSE(HasException(env));

    input_bytes.push_back(std::move(cur_bytes));
  }
  ASSERT_EQ(kMaxItems, input_bytes.size());

  // Convert to std::vector<std::vector<uint8_t>>, check the content.
  std::vector<std::vector<uint8_t>> result;
  JavaArrayOfByteArrayToBytesVector(env, array, &result);

  EXPECT_EQ(input_bytes.size(), result.size());
  for (size_t i = 0; i < kMaxItems; ++i)
    EXPECT_THAT(result[i], ::testing::ElementsAreArray(input_bytes.at(i)));
}

TEST(JniArray, JavaArrayOfStringArrayToVectorOfStringVector) {
  const std::vector<std::vector<std::u16string>> kArrays = {
      {u"a", u"f"}, {u"a", u""}, {}, {u""}};

  JNIEnv* env = AttachCurrentThread();

  ScopedJavaLocalRef<jobjectArray> array(
      env, env->NewObjectArray(kArrays.size(),
                               env->FindClass("[Ljava/lang/String;"), NULL));
  ASSERT_TRUE(array);

  ScopedJavaLocalRef<jclass> string_clazz(env,
                                          env->FindClass("java/lang/String"));
  ASSERT_TRUE(string_clazz);

  for (size_t i = 0; i < kArrays.size(); ++i) {
    const std::vector<std::u16string>& child_data = kArrays[i];

    ScopedJavaLocalRef<jobjectArray> child_array(
        env, env->NewObjectArray(child_data.size(), string_clazz.obj(), NULL));
    ASSERT_TRUE(child_array);

    for (size_t j = 0; j < child_data.size(); ++j) {
      ScopedJavaLocalRef<jstring> item =
          base::android::ConvertUTF16ToJavaString(env, child_data[j]);
      env->SetObjectArrayElement(child_array.obj(), j, item.obj());
      ASSERT_FALSE(HasException(env));
    }
    env->SetObjectArrayElement(array.obj(), i, child_array.obj());
  }

  std::vector<std::vector<std::u16string>> vec;
  Java2dStringArrayTo2dStringVector(env, array, &vec);

  ASSERT_EQ(kArrays, vec);
}

TEST(JniArray, JavaArrayOfIntArrayToIntVector) {
  const size_t kNumItems = 4;
  JNIEnv* env = AttachCurrentThread();

  // Create an int[][] object.
  ScopedJavaLocalRef<jclass> int_array_clazz(env, env->FindClass("[I"));
  ASSERT_TRUE(int_array_clazz);

  ScopedJavaLocalRef<jobjectArray> array(
      env, env->NewObjectArray(kNumItems, int_array_clazz.obj(), nullptr));
  ASSERT_TRUE(array);

  // Populate int[][] object.
  const auto kInts0 =
      std::to_array<int>({0, 1, -1, std::numeric_limits<int32_t>::min(),
                          std::numeric_limits<int32_t>::max()});
  ScopedJavaLocalRef<jintArray> int_array0 = ToJavaIntArray(env, kInts0);
  env->SetObjectArrayElement(array.obj(), 0, int_array0.obj());

  const auto kInts1 = std::to_array<int>({3, 4, 5});
  ScopedJavaLocalRef<jintArray> int_array1 = ToJavaIntArray(env, kInts1);
  env->SetObjectArrayElement(array.obj(), 1, int_array1.obj());

  const auto kInts2 = std::array<int, 0>();
  ScopedJavaLocalRef<jintArray> int_array2 = ToJavaIntArray(env, kInts2);
  env->SetObjectArrayElement(array.obj(), 2, int_array2.obj());

  const auto kInts3 = std::to_array<int>({16});
  ScopedJavaLocalRef<jintArray> int_array3 = ToJavaIntArray(env, kInts3);
  env->SetObjectArrayElement(array.obj(), 3, int_array3.obj());

  // Convert to std::vector<std::vector<int>>, check the content.
  std::vector<std::vector<int>> out;
  JavaArrayOfIntArrayToIntVector(env, array, &out);

  EXPECT_EQ(kNumItems, out.size());
  EXPECT_EQ(kInts0.size(), out[0].size());
  EXPECT_EQ(kInts1.size(), out[1].size());
  EXPECT_EQ(kInts2.size(), out[2].size());
  EXPECT_EQ(kInts3.size(), out[3].size());
  CheckIntArrayConversion(env, int_array0, out[0]);
  CheckIntArrayConversion(env, int_array1, out[1]);
  CheckIntArrayConversion(env, int_array2, out[2]);
  CheckIntArrayConversion(env, int_array3, out[3]);
}

TEST(JniArray, ToJavaArrayOfObjectsOfClass) {
  JNIEnv* env = AttachCurrentThread();

  std::vector<ScopedJavaLocalRef<jobject>> objects = {
      ScopedJavaLocalRef<jobject>(ConvertUTF8ToJavaString(env, "one")),
      ScopedJavaLocalRef<jobject>(ConvertUTF8ToJavaString(env, "two")),
      ScopedJavaLocalRef<jobject>(ConvertUTF8ToJavaString(env, "three")),
  };

  ScopedJavaLocalRef<jobjectArray> j_array =
      ToJavaArrayOfObjects(env, jni_zero::g_string_class, objects);
  ASSERT_TRUE(j_array);

  EXPECT_EQ("one",
            ConvertJavaStringToUTF8(
                env, ScopedJavaLocalRef<jstring>(
                         env, static_cast<jstring>(env->GetObjectArrayElement(
                                  j_array.obj(), 0)))));
  EXPECT_EQ("two",
            ConvertJavaStringToUTF8(
                env, ScopedJavaLocalRef<jstring>(
                         env, static_cast<jstring>(env->GetObjectArrayElement(
                                  j_array.obj(), 1)))));
  EXPECT_EQ("three",
            ConvertJavaStringToUTF8(
                env, ScopedJavaLocalRef<jstring>(
                         env, static_cast<jstring>(env->GetObjectArrayElement(
                                  j_array.obj(), 2)))));
}

TEST(JniArray, ToJavaArrayOfObjectLocalRef) {
  JNIEnv* env = AttachCurrentThread();

  std::vector<ScopedJavaLocalRef<jobject>> objects = {
      ScopedJavaLocalRef<jobject>(ConvertUTF8ToJavaString(env, "one")),
      ScopedJavaLocalRef<jobject>(ConvertUTF8ToJavaString(env, "two")),
      ScopedJavaLocalRef<jobject>(ConvertUTF8ToJavaString(env, "three")),
  };

  ScopedJavaLocalRef<jobjectArray> j_array = ToJavaArrayOfObjects(env, objects);
  ASSERT_TRUE(j_array);

  EXPECT_EQ("one",
            ConvertJavaStringToUTF8(
                env, ScopedJavaLocalRef<jstring>(
                         env, static_cast<jstring>(env->GetObjectArrayElement(
                                  j_array.obj(), 0)))));
  EXPECT_EQ("two",
            ConvertJavaStringToUTF8(
                env, ScopedJavaLocalRef<jstring>(
                         env, static_cast<jstring>(env->GetObjectArrayElement(
                                  j_array.obj(), 1)))));
  EXPECT_EQ("three",
            ConvertJavaStringToUTF8(
                env, ScopedJavaLocalRef<jstring>(
                         env, static_cast<jstring>(env->GetObjectArrayElement(
                                  j_array.obj(), 2)))));
}

TEST(JniArray, ToJavaArrayOfObjectGlobalRef) {
  JNIEnv* env = AttachCurrentThread();

  std::vector<ScopedJavaGlobalRef<jobject>> objects = {
      ScopedJavaGlobalRef<jobject>(ConvertUTF8ToJavaString(env, "one")),
      ScopedJavaGlobalRef<jobject>(ConvertUTF8ToJavaString(env, "two")),
      ScopedJavaGlobalRef<jobject>(ConvertUTF8ToJavaString(env, "three")),
  };

  ScopedJavaLocalRef<jobjectArray> j_array = ToJavaArrayOfObjects(env, objects);
  ASSERT_TRUE(j_array);

  EXPECT_EQ("one",
            ConvertJavaStringToUTF8(
                env, ScopedJavaLocalRef<jstring>(
                         env, static_cast<jstring>(env->GetObjectArrayElement(
                                  j_array.obj(), 0)))));
  EXPECT_EQ("two",
            ConvertJavaStringToUTF8(
                env, ScopedJavaLocalRef<jstring>(
                         env, static_cast<jstring>(env->GetObjectArrayElement(
                                  j_array.obj(), 1)))));
  EXPECT_EQ("three",
            ConvertJavaStringToUTF8(
                env, ScopedJavaLocalRef<jstring>(
                         env, static_cast<jstring>(env->GetObjectArrayElement(
                                  j_array.obj(), 2)))));
}
}  // namespace base::android