// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:ffi'; import 'dart:io'; import 'package:ffi/ffi.dart'; import 'package:jni/jni.dart'; import 'package:jni/src/jni.dart'; import 'package:jni/src/third_party/generated_bindings.dart'; import 'package:test/test.dart'; import 'test_util/test_util.dart'; const maxLongInJava = 9223372036854775807; void main() { // Running on Android through flutter, this plugin // will bind to Android runtime's JVM. // On other platforms eg Flutter desktop or Dart standalone // You need to manually create a JVM at beginning. // // On flutter desktop, the C wrappers are bundled, and helperPath param // is not required. // // On dart standalone, however, there's no way to bundle the wrappers. // You have to manually pass the path to the `dartjni` dynamic library. if (!Platform.isAndroid) { checkDylibIsUpToDate(); spawnJvm(); } run(testRunner: test); } // We are factoring these tests into a run method which allows us to run same // tests with different environments. Eg: standalone and integration tests. void run({required TestRunnerCallback testRunner}) { // Tests in this file demonstrate how to use `GlobalJniEnv`, a thin // abstraction over JNIEnv in JNI C API. This can be used from multiple // threads, and converts all returned object references to global references, // so that you don't need to worry about whether your Dart code will be // scheduled on another thread. // // GlobalJniEnv wraps all methods of JNIEnv (UpperCamelCase, reflecting the // original name of the method) and provides few more extension methods // (lowerCamelCase). // // For examples of a higher level API, see `jni_object_tests.dart`. final env = Jni.env; testRunner('get JNI Version', () { expect(Jni.env.GetVersion(), isNot(equals(0))); }); testRunner( 'Manually lookup & call Integer.toHexString', () => using((arena) { // Method names on JniEnv* from C JNI API are capitalized // like in original, while other extension methods // follow Dart naming conventions. final integerClass = env.FindClass('java/lang/Integer'.toNativeChars(arena)); // Refer JNI spec on how to construct method signatures // Passing wrong signature leads to a segfault final hexMethod = env.GetStaticMethodID( integerClass, 'toHexString'.toNativeChars(arena), '(I)Ljava/lang/String;'.toNativeChars(arena)); for (var i in [1, 80, 13, 76, 11344]) { // if your argument is int, bool, or JObject (`Pointer`) // it can be directly placed in the list. To convert into // different primitive types, use JValue wrappers. final jres = env.CallStaticObjectMethodA(integerClass, hexMethod, toJValues([JValueInt(i)], allocator: arena)); // use asDartString extension method on Pointer // to convert a String jobject result to string final res = env.toDartString(jres); expect(res, equals(i.toRadixString(16))); // Any object or class result from java is a local reference // and needs to be deleted explicitly. // Note that method and field IDs aren't local references. // But they are valid only until a reference to corresponding // java class exists. env.DeleteGlobalRef(jres); } env.DeleteGlobalRef(integerClass); })); testRunner('asJString extension method', () { const str = 'QWERTY QWERTY'; // convenience method that wraps // converting dart string to native string, // instantiating java string, and freeing the native string final jstr = env.toJStringPtr(str); expect(str, equals(env.toDartString(jstr))); env.DeleteGlobalRef(jstr); }); testRunner( 'GlobalJniEnv should catch exceptions', () => using((arena) { final integerClass = env.FindClass('java/lang/Integer'.toNativeChars(arena)); final parseIntMethod = env.GetStaticMethodID( integerClass, 'parseInt'.toNativeChars(arena), '(Ljava/lang/String;)I'.toNativeChars(arena)); final args = toJValues(['hello'.toJString()..releasedBy(arena)], allocator: arena); expect( () => env.CallStaticIntMethodA( integerClass, parseIntMethod, args), throwsA(isA())); })); testRunner( 'Convert back & forth between Dart & Java strings (UTF-8)', () => using((arena) { const str = 'ABCD EFGH'; final jstr = env.NewStringUTF(str.toNativeChars(arena)); final jchars = env.GetStringUTFChars(jstr, nullptr); final jlen = env.GetStringUTFLength(jstr); final dstr = jchars.cast().toDartString(length: jlen); env.ReleaseStringUTFChars(jstr, jchars); expect(str, equals(dstr)); env.DeleteGlobalRef(jstr); })); testRunner( 'Convert back & forth between Dart & Java strings (UTF-16)', () => using((arena) { const str = 'ABCD EFGH'; final jstr = env.NewString(str.toNativeUtf16().cast(), str.length); final jchars = env.GetStringChars(jstr, nullptr); final jlen = env.GetStringLength(jstr); final dstr = jchars.cast().toDartString(length: jlen); env.ReleaseStringChars(jstr, jchars); expect(str, equals(dstr)); env.DeleteGlobalRef(jstr); })); testRunner( 'Print something from Java', () => using((arena) { final system = env.FindClass('java/lang/System'.toNativeChars(arena)); final field = env.GetStaticFieldID( system, 'out'.toNativeChars(arena), 'Ljava/io/PrintStream;'.toNativeChars(arena)); final out = env.GetStaticObjectField(system, field); final printStream = env.GetObjectClass(out); final println = env.GetMethodID( printStream, 'println'.toNativeChars(arena), '(Ljava/lang/String;)V'.toNativeChars(arena)); const str = '\nHello World from JNI!'; final jstr = env.toJStringPtr(str); env.CallVoidMethodA( out, println, toJValues([jstr], allocator: arena)); env.DeleteGlobalRef(system); env.DeleteGlobalRef(printStream); env.DeleteGlobalRef(jstr); })); testRunner( 'Env create reference methods should retain their default behavior', () { final systemClass = using((arena) { return env.FindClass('java/lang/System'.toNativeChars(arena)); }); final systemOut = using((arena) { final outField = env.GetStaticFieldID( systemClass, 'out'.toNativeChars(arena), 'Ljava/io/PrintStream;'.toNativeChars(arena)); return env.GetStaticObjectField(systemClass, outField); }); var refType = env.GetObjectRefType(systemOut); expect(refType, JObjectRefType.JNIGlobalRefType); final localRef = env.NewLocalRef(systemOut); refType = env.GetObjectRefType(localRef); expect(refType, JObjectRefType.JNILocalRefType); final weakRef = env.NewWeakGlobalRef(systemOut); refType = env.GetObjectRefType(weakRef); expect(refType, JObjectRefType.JNIWeakGlobalRefType); final globalRef = env.NewGlobalRef(localRef); refType = env.GetObjectRefType(globalRef); expect(refType, JObjectRefType.JNIGlobalRefType); env.DeleteGlobalRef(globalRef); env.DeleteWeakGlobalRef(weakRef); env.DeleteLocalRef(localRef); env.DeleteGlobalRef(systemOut); env.DeleteGlobalRef(systemClass); }); testRunner('long methods return long int without loss of precision', () { using((arena) { final longClass = env.FindClass('java/lang/Long'.toNativeChars(arena)); final maxField = env.GetStaticFieldID( longClass, 'MAX_VALUE'.toNativeChars(arena), 'J'.toNativeChars(arena), ); final maxValue = env.GetStaticLongField(longClass, maxField); expect(maxValue, equals(maxLongInJava)); env.DeleteGlobalRef(longClass); }); }); testRunner('class <-> object methods', () { using((arena) { final systemClass = env.FindClass('java/lang/System'.toNativeChars(arena)); final outField = env.GetStaticFieldID( systemClass, 'out'.toNativeChars(arena), 'Ljava/io/PrintStream;'.toNativeChars(arena)); final errField = env.GetStaticFieldID( systemClass, 'err'.toNativeChars(arena), 'Ljava/io/PrintStream;'.toNativeChars(arena)); final systemOut = env.GetStaticObjectField(systemClass, outField); final systemErr = env.GetStaticObjectField(systemClass, errField); final outClass = env.GetObjectClass(systemOut); expect(env.IsInstanceOf(systemOut, outClass), isTrue); expect(env.IsInstanceOf(systemErr, outClass), isTrue); final errClass = env.GetObjectClass(systemErr); expect(env.IsSameObject(outClass, errClass), isTrue); env.DeleteGlobalRef(systemClass); env.DeleteGlobalRef(systemOut); env.DeleteGlobalRef(systemErr); env.DeleteGlobalRef(outClass); env.DeleteGlobalRef(errClass); }); }); }